2009-03-03 34 views
42

.NETからNTFS代替データストリームを作成、削除、読み取り、書き込み、および作成するにはどうすればよいですか?NTFS代替データストリーム - .NET

ネイティブの.NETサポートがない場合は、どのWin32 APIを使用しますか?また、私はこれが文書化されているとは思わないので、どのように使用するのですか? .NETで

+0

標準のファイルコピーの進行状況ダイアログでファイルをコピーしたい場合は、:: SHFileOperation()を使用することはできません。AltDataStreamではまったく動作しません(Windows 7でチェックされています)。 :: CopyFileEx()については、いくつかのケースで機能します(たとえば、進行状況コールバックを呼び出している間にAltDataStreamにファイルをコピーできますが、他のアプリケーションでは機能しません)。 – Nishi

答えて

4

ない:ここで

http://support.microsoft.com/kb/105763

#include <windows.h> 
    #include <stdio.h> 

    void main() 
    { 
     HANDLE hFile, hStream; 
     DWORD dwRet; 

     hFile = CreateFile("testfile", 
         GENERIC_WRITE, 
        FILE_SHARE_WRITE, 
           NULL, 
         OPEN_ALWAYS, 
            0, 
           NULL); 
     if(hFile == INVALID_HANDLE_VALUE) 
     printf("Cannot open testfile\n"); 
     else 
      WriteFile(hFile, "This is testfile", 16, &dwRet, NULL); 

     hStream = CreateFile("testfile:stream", 
           GENERIC_WRITE, 
          FILE_SHARE_WRITE, 
             NULL, 
            OPEN_ALWAYS, 
              0, 
             NULL); 
     if(hStream == INVALID_HANDLE_VALUE) 
     printf("Cannot open testfile:stream\n"); 
     else 
     WriteFile(hStream, "This is testfile:stream", 23, &dwRet, NULL); 
    } 
+8

2つの欠落したCloseHandle呼び出し... OSはクリーンアップされますが、実際のアプリケーションでは問題になります。 – Richard

+3

@リチャード - MSのサポートサイトからコピーされました... –

+1

ávioC#からこれらの関数にP/Invokeできます。 –

30

は彼らのためにネイティブの.NETサポートはありませんC#

using System.Runtime.InteropServices; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var mainStream = NativeMethods.CreateFileW(
      "testfile", 
      NativeConstants.GENERIC_WRITE, 
      NativeConstants.FILE_SHARE_WRITE, 
      IntPtr.Zero, 
      NativeConstants.OPEN_ALWAYS, 
      0, 
      IntPtr.Zero); 

     var stream = NativeMethods.CreateFileW(
      "testfile:stream", 
      NativeConstants.GENERIC_WRITE, 
      NativeConstants.FILE_SHARE_WRITE, 
      IntPtr.Zero, 
      NativeConstants.OPEN_ALWAYS, 
      0, 
      IntPtr.Zero); 
    } 
} 

public partial class NativeMethods 
{ 

    /// Return Type: HANDLE->void* 
    ///lpFileName: LPCWSTR->WCHAR* 
    ///dwDesiredAccess: DWORD->unsigned int 
    ///dwShareMode: DWORD->unsigned int 
    ///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES* 
    ///dwCreationDisposition: DWORD->unsigned int 
    ///dwFlagsAndAttributes: DWORD->unsigned int 
    ///hTemplateFile: HANDLE->void* 
    [DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")] 
    public static extern System.IntPtr CreateFileW(
     [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName, 
     uint dwDesiredAccess, 
     uint dwShareMode, 
     [InAttribute()] System.IntPtr lpSecurityAttributes, 
     uint dwCreationDisposition, 
     uint dwFlagsAndAttributes, 
     [InAttribute()] System.IntPtr hTemplateFile 
    ); 

} 


public partial class NativeConstants 
{ 

    /// GENERIC_WRITE -> (0x40000000L) 
    public const int GENERIC_WRITE = 1073741824; 

    /// FILE_SHARE_DELETE -> 0x00000004 
    public const int FILE_SHARE_DELETE = 4; 

    /// FILE_SHARE_WRITE -> 0x00000002 
    public const int FILE_SHARE_WRITE = 2; 

    /// FILE_SHARE_READ -> 0x00000001 
    public const int FILE_SHARE_READ = 1; 

    /// OPEN_ALWAYS -> 4 
    public const int OPEN_ALWAYS = 4; 
} 
+8

SafeHandleから派生した型を使用して、これらのファイルハンドルを整理する必要があります。 – Richard

+7

ネイティブAPIの使い方を示しましたが、 'CreateFileW'から返されたポインタの使い方は示されていませんでした。 Windowsエクスプローラのファイルプロパティの[概要]タブで使用できる共通のプロパティに書き込む、より完全なサンプルを見たいと思っています。 –

13

のためのバージョンです。ネイティブのWin32メソッドを呼び出すには、P/Invokeを使用する必要があります。

これらを作成するには、filename.txt:streamnameのようなパスでCreateFileを呼び出します。 SafeFileHandleを返すinterop呼び出しを使用する場合は、それを使用して&に書き込むことができるFileStreamを構築できます。

ファイルに存在するストリームを一覧表示するには、FindFirstStreamWFindNextStreamW(Server 2003以降にのみ存在し、XPでは存在しない)を使用します。

ファイルの残りの部分をコピーしてストリームの1つを離れることを除いて、ストリームを削除できるとは思いません。長さを0に設定することもできますが、試していません。

ディレクトリに代替データストリームを設定することもできます。ファイル-と同じようにアクセスします。

ストリームは、デフォルトのストリームとは独立して、圧縮、暗号化、希薄化を設定できます。

+7

ストリームを削除することができます:DeleteFile APIを "filename:streamname"と呼ぶだけです。どうやら、通常のファイルでできることは何でもADSで行うことができます。 FileStreamがそれを処理しない唯一の理由は、パスを検証し、 ":"が含まれていると失敗するためです。 –

6

まず、Microsoft®.NET Frameworkにはこの機能はありません。あなたがそれを必要とするならば、単純で単純なものとして、直接またはサードパーティ製のライブラリを使って何らかのinteropを行う必要があります。

Windows Server™2003以降を使用している場合、Kernel32.dllは、探している機能を正確に提供するFindFirstFileとFindNextFileに対応しています。 FindFirstStreamWとFindNextStreamWを使用すると、特定のファイル内のすべての代替データストリームを検索して列挙し、それぞれの名前と長さなどの情報を取得できます。マネージコードからこれらの関数を使用するためのコードは、私は12月の欄に示されたものと非常に類似しており、単にあなた

FindFirstStreamWとFindNextStreamW

を使用して1

図1

[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 
public sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid { 

    private SafeFindHandle() : base(true) { } 

    protected override bool ReleaseHandle() { 
     return FindClose(this.handle); 
    } 

    [DllImport("kernel32.dll")] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
    private static extern bool FindClose(IntPtr handle); 

} 

public class FileStreamSearcher { 
    private const int ERROR_HANDLE_EOF = 38; 
    private enum StreamInfoLevels { FindStreamInfoStandard = 0 } 

    [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern SafeFindHandle FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); 

    [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FindNextStreamW(SafeFindHandle hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); 
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
    private class WIN32_FIND_STREAM_DATA { 
     public long StreamSize; 
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] 
     public string cStreamName; 
    } 

    public static IEnumerable<string> GetStreams(FileInfo file) { 
     if (file == null) throw new ArgumentNullException("file"); 
     WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA(); 
     SafeFindHandle handle = FindFirstStreamW(file.FullName, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); 
     if (handle.IsInvalid) throw new Win32Exception(); 
     try { 
      do { 
       yield return findStreamData.cStreamName; 
      } while (FindNextStreamW(handle, findStreamData)); 
      int lastError = Marshal.GetLastWin32Error(); 
      if (lastError != ERROR_HANDLE_EOF) throw new Win32Exception(lastError); 
     } finally { 
      handle.Dispose(); 
     } 
    } 
} 
図に示されていますFindFirstStreamWを呼び出し、ターゲットファイルへのフルパスを渡します。 FindFirstStreamWの2番目のパラメータは、返されるデータの詳細レベルを指定します。現在のところ、数値が0の1つのレベル(FindStreamInfoStandard)しかありません。関数の3番目のパラメータは、WIN32_FIND_STREAM_DATA構造体へのポインタです(技術的には、3番目のパラメータが指しているものは2番目のパラメータの値によって決まります)情報レベルの詳細はありますが、現在のところ1つのレベルしかないため、すべての目的と目的でWIN32_FIND_STREAM_DATAです)。私はその構造体の管理された対応をクラスとして宣言しました。そしてinteropの署名では、構造体へのポインタとしてマーシャリングされるようにマークしました。最後のパラメータは、将来の使用のために予約されており、0にする必要があります。 有効なハンドルがFindFirstStreamWから返された場合、WIN32_FIND_STREAM_DATAインスタンスには検出されたストリームに関する情報が格納され、cStreamName値は使用可能な最初のストリーム名として呼び出し元に返されます。 FindNextStreamWは、FindFirstStreamWから返されたハンドルを受け取り、指定されたWIN32_FIND_STREAM_DATAを、使用可能な次のストリーム(存在する場合)に関する情報で埋めます。 FindNextStreamWは、別のストリームが利用可能な場合はtrue、そうでない場合はfalseを返します。 その結果、私はFindNextStreamWを継続して呼び出し、FindNextStreamWがfalseを返すまで結果のストリーム名を返します。そのようなことが起こると、FindNextStreamWにストリームがなくなったために、最後のエラー値を2回チェックして、繰り返しが停止したことを確認します。 残念ながら、Windows®XPまたはWindows 2000 Serverを使用している場合、これらの機能は利用できませんが、いくつかの選択肢があります。最初の解決策は、Kernel32.dll、NTQueryInformationFileから現在エクスポートされているドキュメント化されていない関数です。しかし、文書化されていない関数は、理由により文書化されておらず、今後いつでも変更または削除することができます。それらを使用しないことをお勧めします。この機能を使用したい場合は、Webを検索すると、豊富なリファレンスとサンプルソースコードが見つかります。しかし、あなた自身の責任においてそうしてください。 もう1つのソリューションと、 図2で示したものは、Kernel32.dllからエクスポートされた2つの関数に依存しており、これらは文書化されています。 BackupReadとBackupSeek

public enum StreamType { 
    Data = 1, 
    ExternalData = 2, 
    SecurityData = 3, 
    AlternateData = 4, 
    Link = 5, 
    PropertyData = 6, 
    ObjectID = 7, 
    ReparseData = 8, 
    SparseDock = 9 
} 

public struct StreamInfo { 
    public StreamInfo(string name, StreamType type, long size) { 
     Name = name; 
     Type = type; 
     Size = size; 
    } 
    readonly string Name; 
    public readonly StreamType Type; 
    public readonly long Size; 
} 

public class FileStreamSearcher { 
    [DllImport("kernel32.dll")] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);[DllImport("kernel32.dll")] 

    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool BackupSeek(SafeFileHandle hFile, uint dwLowBytesToSeek, uint dwHighBytesToSeek, out uint lpdwLowByteSeeked, out uint lpdwHighByteSeeked, ref IntPtr lpContext); public static IEnumerable<StreamInfo> GetStreams(FileInfo file) { 
     const int bufferSize = 4096; 
     using (FileStream fs = file.OpenRead()) { 
      IntPtr context = IntPtr.Zero; 
      IntPtr buffer = Marshal.AllocHGlobal(bufferSize); 
      try { 
       while (true) { 
        uint numRead; 
        if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Marshal.SizeOf(typeof(Win32StreamID)), out numRead, false, true, ref context)) throw new Win32Exception(); 
        if (numRead > 0) { 
         Win32StreamID streamID = (Win32StreamID)Marshal.PtrToStructure(buffer, typeof(Win32StreamID)); 
         string name = null; 
         if (streamID.dwStreamNameSize > 0) { 
          if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Math.Min(bufferSize, streamID.dwStreamNameSize), out numRead, false, true, ref context)) throw new Win32Exception(); name = Marshal.PtrToStringUni(buffer, (int)numRead/2); 
         } 
         yield return new StreamInfo(name, streamID.dwStreamId, streamID.Size); 
         if (streamID.Size > 0) { 
          uint lo, hi; BackupSeek(fs.SafeFileHandle, uint.MaxValue, int.MaxValue, out lo, out hi, ref context); 
         } 
        } else break; 
       } 
      } finally { 
       Marshal.FreeHGlobal(buffer); 
       uint numRead; 
       if (!BackupRead(fs.SafeFileHandle, IntPtr.Zero, 0, out numRead, true, false, ref context)) throw new Win32Exception(); 
      } 
     } 
    } 
} 
BackupReadの背後にある考え方は、それができるということです

を使用して

BOOL BackupRead(HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, BOOL bAbort, BOOL bProcessSecurity, LPVOID* lpContext); 
BOOL BackupSeek(HANDLE hFile, DWORD dwLowBytesToSeek, DWORD dwHighBytesToSeek, LPDWORD lpdwLowByteSeeked, LPDWORD lpdwHighByteSeeked, LPVOID* lpContext); 

図2:その名前が示すように、BackupReadとBackupSeekは、バックアップをサポートするためのWin32®のAPIの一部ですファイルからバッファにデータを読み込むために使用され、バッファはバックアップ記憶媒体に書き込まれます。ただし、BackupReadも、ターゲットファイルを構成する各代替データストリームに関する情報を見つけるのに非常に便利です。ファイル内のすべてのデータを一連の離散バイトストリーム(各代替データストリームはこれらのバイトストリームの1つ)として処理し、各ストリームの前にWIN32_STREAM_ID構造体があります。したがって、すべてのストリームを列挙するためには、各ストリームの先頭からこれらのWIN32_STREAM_ID構造体すべてを読み込むだけで済みます(BackupSeekは非常に便利になります。ストリームからストリームへジャンプするために使用できます。ファイル内のすべてのデータを読み取る)。 は、あなたが最初に管理されていないWIN32_STREAM_ID構造のために管理対応を作成する必要があり、開始するには:

ほとんどの部分について
typedef struct _WIN32_STREAM_ID { 
    DWORD dwStreamId; DWORD dwStreamAttributes; 
    LARGE_INTEGER Size; 
    DWORD dwStreamNameSize; 
    WCHAR cStreamName[ANYSIZE_ARRAY]; 
} WIN32_STREAM_ID; 

、これはあなたがP /呼び出してマーシャリングと思い、他の構造のようなものです。しかし、いくつかの合併症があります。まず第一に、WIN32_STREAM_IDは可変サイズの構造体です。その最後のメンバーcStreamNameは、長さがANYSIZE_ARRAYの配列です。 ANYSIZE_ARRAYは1に定義されていますが、cStreamNameは、前の4つのフィールドの後の構造内の残りのデータのアドレスにすぎません。つまり、構造体がsizeof(WIN32_STREAM_ID)バイトよりも大きく割り当てられた場合、実際にはcStreamName配列の一部となります。前のフィールドdwStreamNameSizeは、配列の正確な長さを指定します。 これはWin32開発には最適ですが、マーシャラには、BackupReadへのinterop呼び出しの一部として管理されていないメモリからこのデータをコピーする必要があります。どのようにマーシャラーは、WIN32_STREAM_ID構造体が実際にどの程度大きかったかを知っていますか?それはしません。 2番目の問題は、パッキングとアラインメントです。一瞬cStreamNameを無視すると、管理対象WIN32_STREAM_IDの対応のために、次の可能性を考慮してください。

[StructLayout(LayoutKind.Sequential)] 
public struct Win32StreamID { 
    public int dwStreamId; 
    public int dwStreamAttributes; 
    public long Size; 
    public int dwStreamNameSize; 
} 

アンのInt32のサイズは4バイトで、Int64のは8バイトです。したがって、この構造体は20バイトと予想されます。

int size1 = Marshal.SizeOf(typeof(Win32StreamID)); 
int size2 = sizeof(Win32StreamID); // in an unsafe context 

問題はコンパイラがこれらの構造内の値は、常に上に整列されていることを確認したいということです:あなたは、次のコードを実行した場合しかし、あなたは両方の値が24、ない20であることを見つけることができます適切な境界。 4バイトの値は4で割り切れるアドレスに、8バイトの値は8で割り切れる境界になければなりません。今度は、Win32StreamID構造体の配列を作成する場合にどうなるか想像してみましょう。配列の最初のインスタンスのすべてのフィールドが正しく整列されます。たとえば、Sizeフィールドは2つの32ビット整数の後に続くため、配列の先頭から8バイトになり、8バイトの値になります。ただし、構造体のサイズが20バイトの場合、配列の2番目のインスタンスは、そのメンバのすべてが正しく整列されません。整数値はすべて問題ありませんが、long値は配列の先頭から28バイト、つまり8で割り切れる値ではありません。これを修正するために、コンパイラは構造体を24のサイズにパディングし、フィールドは常に正しく整列されます(配列自体があると仮定します)。 コンパイラが正しいことをしているなら、私はなぜこれについて心配しているのだろうかと思うかもしれません。図2のコードを見ればわかります。私が説明した最初のマーシャリングの問題を回避するために、実際にはcStreamNameをWin32StreamID構造体の外に置いています。私はWin32StreamID構造体を埋めるために十分なバイト数で読み込むためにBackupReadを使用し、構造体のdwStreamNameSizeフィールドを調べます。名前の長さを知ったので、BackupReadを使ってファイルから文字列の値を読み取ることができます。それはすべてうまいですが、Marshal.SizeOfが20ではなくWin32StreamID構造体に対して24を返したら、あまりにも多くのデータを読み込もうとしています。 これを避けるには、Win32StreamIDのサイズが24ではなく20であることを確認する必要があります。これは、構造を飾るStructLayoutAttributeのフィールドを使用して2つの異なる方法で行うことができます。

[StructLayout(LayoutKind.Sequential, Size = 20)] 

番目のオプションは、パックのフィールドを使用することです:最初は、構造がどうあるべき正確にどのように大きなランタイムに指示サイズフィールドを、使用することです。パックは、LayoutKind.Sequential値が指定されている場合に使用されるべきパッキングサイズを示し、構造内のフィールドのアライメントを制御します。管理構造体のデフォルトのパッキングサイズは8です。これを4に変更すると、私が探している20バイトの構造体が得られます(そして、これを配列で実際に使用していないため、効率が低下しませんあるいは、そのような梱包の変化に起因するかもしれない安定性):ここに示されているような場所では、このコードで

[StructLayout(LayoutKind.Sequential, Pack = 4)] 
public struct Win32StreamID { 
    public StreamType dwStreamId; 
    public int dwStreamAttributes; 
    public long Size; 
    public int dwStreamNameSize; // WCHAR cStreamName[1]; 
} 

、私は今、ファイル内のストリームのすべてを列挙することができます

static void Main(string[] args) { 
    foreach (string path in args) { 
     Console.WriteLine(path + ":"); 
     foreach (StreamInfo stream in FileStreamSearcher.GetStreams(new FileInfo(path))) { 
      Console.WriteLine("\t{0}\t{1}\t{2}", stream.Name != null ? stream.Name : "(unnamed)", stream.Type, stream.Size); 
     } 
    } 
} 

あなたを」このバージョンのFileStreamSearcherは、FindFirstStreamWとFindNextStreamWを使用するバージョンより多くの情報を返します。 BackupReadはプライマリストリームと代替データストリームだけでなく、セキュリティ情報、再解析データなどを含むストリーム上で動作するデータを提供することもできます。代替データストリームのみを表示する場合は、代替データストリームのStreamType.AlternateDataになるStreamInfoのTypeプロパティに基づいてフィルタリングできます。

> echo ".NET Matters" > C:\test.txt 
> echo "MSDN Magazine" > C:\test.txt:magStream 
> StreamEnumerator.exe C:\test.txt 
test.txt: 
     (unnamed)    SecurityData 164 
     (unnamed)    Data   17 
     :magStream:$DATA  AlternateData 18 
> type C:\test.txt 
".NET Matters" 
> more < C:\test.txt:magStream 
"MSDN Magazine" 

だから、今あなたがに保存されているすべての代替データストリームの名前を取得することができます: このコードをテストするには、コマンドプロンプトでechoコマンドを使用して代替データストリームを持つファイルを作成することができますファイル。すばらしいです。しかし、それらのストリームの1つで実際にデータを操作したいのであればどうでしょうか?残念ながら、代替データストリームのパスをFileStreamコンストラクタの1つに渡そうとすると、NotSupportedExceptionがスローされます。「指定されたパスの形式はサポートされていません。 これを回避するには、kernel32から公開されているCreateFile関数に直接アクセスして、FileStreamのパス正規化チェックをバイパスすることができます。dll(図3参照)。私はCreateFile関数にP/Invokeを使用して、指定されたパスのSafeFileHandleを開き、パス上の管理されたアクセス許可チェックを実行せずに、代替データストリーム識別子を含めることができます。このSafeFileHandleを使用して、新しい管理されたFileStreamを作成し、必要なアクセスを提供します。これを使用すると、System.IO名前空間の機能を使用して代替データストリームの内容を操作するのは簡単です。次の例では、読み出し及びCの内容をプリントアウト:\ test.txtの:前の例で作成magStream:

string path = @"C:\test.txt:magStream"; 
using (StreamReader reader = new StreamReader(CreateFileStream(path, FileAccess.Read, FileMode.Open, FileShare.Read))) { 
    Console.WriteLine(reader.ReadToEnd()); 
} 

図3

private static FileStream CreateFileStream(string path, FileAccess access, FileMode mode, FileShare share) { 
    if (mode == FileMode.Append) mode = FileMode.OpenOrCreate; SafeFileHandle handle = CreateFile(path, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero); 
    if (handle.IsInvalid) throw new IOException("Could not open file stream.", new Win32Exception()); 
    return new FileStream(handle, access); 
} 

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); 

Stephen ToubのCreateFileためのP /呼び出しを使用MSDN Magazine from January 2006にあります。

+4

リンクオンリーの回答が悪い理由の良い例です。 –

+0

MSDN雑誌へのリンクはすべて壊れており、MSDNのWebサイトへのリンクもすぐに破棄されます。あなたの答えをもっと詳しく記入してください。 – AaA

14

このナゲットパッケージCodeFluent Runtime Clientには、作成/読み取り/更新/削除/列挙操作をサポートするNtfsAlternateStream Classがあります(その他のユーティリティもあります)。

+7

ここでは、その使用方法に関するいくつかの例があります。http://blog.codefluententities.com/2013/03/14/manipulating-ntfs-alternate-data-streams-in-c-with-the-codefluent-runtime-クライアント/ – polkduran

関連する問題