2009-03-03 6 views
42

¿Cómo crearía/eliminaría/leería/escribiría/NTFS secuencias de datos alternativas de .NET?NTFS Alternate Data Streams - .NET

Si no hay soporte para .NET nativo, ¿qué API de Win32 usaría? Además, ¿cómo los usaría, ya que no creo que esto esté documentado?

+0

BTW, si desea copiar archivos con el cuadro de diálogo de progreso de copiado de archivos estándar, no puede usar :: SHFileOperation() - no funciona con AltDataStreams en absoluto (marcada en Windows 7). En cuanto a :: CopyFileEx(), funciona en algunos casos (por ejemplo, puede copiar un archivo en AltDataStream mientras llama a la devolución de llamada de progreso), pero no funciona en otros. – Nishi

Respuesta

4
No

en .NET:

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

Dos llamadas CloseHandle perdidas ... El sistema operativo se limpiará, pero sería un problema en una aplicación real. – Richard

+3

@Richard - simplemente copiado del sitio de soporte de MS ... –

+1

ávio Puede P/invocar a esas funciones desde C#. –

30

Aquí es una versión para 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

Debería utilizar un tipo derivado de SafeHandle aquí, para garantizar que limpia esos identificadores de archivo. – Richard

+7

Mostró cómo usar las API nativas, pero no cómo utilizar el puntero devuelto por 'CreateFileW'. Me gustaría * realmente * ver una muestra más completa que escribe en las propiedades comunes disponibles en la pestaña Resumen de las propiedades del archivo en el Explorador de Windows. –

13

No hay soporte nativo para .NET ellos. Tienes que usar P/Invoke para llamar a los métodos nativos de Win32.

Para crearlos, llame al CreateFile con una ruta como filename.txt:streamname. Si usa la llamada de interoperabilidad que devuelve un SafeFileHandle, puede usarlo para construir un FileStream que luego puede leer escribiendo en &.

Para enumerar las transmisiones que existen en un archivo, use FindFirstStreamW y FindNextStreamW (que existen solo en Server 2003 y posterior, no en XP).

No creo que pueda eliminar una secuencia, excepto copiando el resto del archivo y dejando fuera una de las secuencias. Establecer la longitud en 0 también puede funcionar, pero no lo he intentado.

También puede tener secuencias de datos alternativas en un directorio. Usted accede a ellos de la misma manera que con los archivos: C:\some\directory:streamname.

Las secuencias pueden tener compresión, encriptación y dispersión establecidas en ellas independientemente de la secuencia predeterminada.

+7

Usted * puede * eliminar una secuencia: simplemente llame a DeleteFile API con "filename: streamname". Aparentemente, puede hacer con un ADS todo lo que puede hacer con un archivo normal. La única razón por la cual FileStream no lo maneja es porque valida la ruta y falla si contiene ":" ... –

6

A Primero, nada en Microsoft® .NET Framework proporciona esta funcionalidad. Si lo desea, simple y llanamente necesitará hacer algún tipo de interoperabilidad, ya sea directamente o usando una biblioteca de terceros.

Si está utilizando Windows Server ™ 2003 o posterior, Kernel32.dll expone las contrapartes de FindFirstFile y FindNextFile que proporcionan la funcionalidad exacta que está buscando. FindFirstStreamW y FindNextStreamW le permiten buscar y enumerar todos los flujos de datos alternativos dentro de un archivo particular, recuperando información sobre cada uno, incluidos su nombre y su longitud. El código para el uso de estas funciones desde el código administrado es muy similar a la que he mostrado en mi columna de diciembre, y se muestra en la Figura 1.

Figura 1 Usando FindFirstStreamW y FindNextStreamW

[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(); 
     } 
    } 
} 

Simplemente llame a FindFirstStreamW, pasando a la ruta completa al archivo de destino. El segundo parámetro para FindFirstStreamW dicta el nivel de detalle que desea en los datos devueltos; actualmente, solo hay un nivel (FindStreamInfoStandard), que tiene un valor numérico de 0. El tercer parámetro de la función es un puntero a una estructura WIN32_FIND_STREAM_DATA (técnicamente, lo que el tercer parámetro señala está dictado por el valor del segundo parámetro detallando el nivel de información, pero como actualmente solo hay un nivel, para todos los efectos este es un WIN32_FIND_STREAM_DATA). He declarado esa contraparte administrada de la estructura como una clase, y en la firma de interoperabilidad que he marcado para que se marque como un puntero a una estructura. El último parámetro está reservado para uso futuro y debe ser 0. Si se devuelve un identificador válido de FindFirstStreamW, la instancia WIN32_FIND_STREAM_DATA contiene información sobre la secuencia encontrada y su valor cStreamName se puede devolver al llamador como el primer nombre de secuencia disponible. FindNextStreamW acepta el identificador devuelto por FindFirstStreamW y rellena el WIN32_FIND_STREAM_DATA proporcionado con información sobre la siguiente secuencia disponible, si existe. FindNextStreamW devuelve verdadero si hay otra secuencia disponible, o falso si no. Como resultado, continuamente llamo a FindNextStreamW y obtengo el nombre de la secuencia resultante hasta que FindNextStreamW devuelve falso. Cuando eso sucede, compruebo dos veces el último valor de error para asegurarme de que la iteración se detuvo porque FindNextStreamW se quedó sin transmisiones, y no por algún motivo inesperado. Desafortunadamente, si está usando Windows® XP o Windows 2000 Server, estas funciones no están disponibles para usted, pero hay un par de alternativas. La primera solución implica una función no documentada actualmente exportada desde Kernel32.dll, NTQueryInformationFile. Sin embargo, las funciones no documentadas no están documentadas por algún motivo, y se pueden cambiar o incluso eliminar en cualquier momento en el futuro. Lo mejor es no usarlos. Si desea utilizar esta función, busque en la Web y encontrará muchas referencias y ejemplos de código fuente. Pero hazlo bajo tu propio riesgo. Otra solución, y una que he demostrado en La figura 2, se basa en dos funciones exportadas de Kernel32.dll, y están documentadas. Como sus nombres lo indican, BackupRead y BackupSeek son parte de la API de Win32 para el soporte de copia de seguridad:

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); 

Figura 2 Usando BackupRead y 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(); 
      } 
     } 
    } 
} 

La idea detrás de BackupRead es que puede ser utilizado para leer datos de un archivo en un búfer, que luego se puede escribir en el medio de almacenamiento de copia de seguridad. Sin embargo, BackupRead también es muy útil para buscar información sobre cada uno de los flujos de datos alternativos que componen el archivo de destino. Procesa todos los datos del archivo como una serie de flujos de bytes discretos (cada flujo de datos alternativo es uno de estos flujos de bytes), y cada una de las secuencias va precedida por una estructura WIN32_STREAM_ID. Por lo tanto, para enumerar todas las secuencias, simplemente necesita leer todas estas estructuras WIN32_STREAM_ID desde el comienzo de cada secuencia (aquí es donde BackupSeek se vuelve muy útil, ya que puede usarse para saltar de una secuencia a otra sin tener que para leer todos los datos en el archivo). Para empezar, primero tiene que crear una contraparte gestionado para la estructura WIN32_STREAM_ID no administrado:

typedef struct _WIN32_STREAM_ID { 
    DWORD dwStreamId; DWORD dwStreamAttributes; 
    LARGE_INTEGER Size; 
    DWORD dwStreamNameSize; 
    WCHAR cStreamName[ANYSIZE_ARRAY]; 
} WIN32_STREAM_ID; 

En su mayor parte, esto es como cualquier otra estructura que le Mariscal través de P/Invoke. Sin embargo, hay algunas complicaciones. En primer lugar, WIN32_STREAM_ID es una estructura de tamaño variable. Su último miembro, cStreamName, es una matriz con longitud ANYSIZE_ARRAY. Mientras que ANYSIZE_ARRAY se define como 1, cStreamName es solo la dirección del resto de los datos en la estructura después de los cuatro campos anteriores, lo que significa que si la estructura se asigna para ser mayor que sizeof (WIN32_STREAM_ID) bytes, ese espacio adicional de hecho, ser parte de la matriz cStreamName. El campo anterior, dwStreamNameSize, especifica exactamente cuánto dura la matriz. Si bien esto es ideal para el desarrollo de Win32, causa estragos en un contador de referencias que necesita copiar estos datos de la memoria no administrada a la memoria administrada como parte de la llamada de interoperabilidad a BackupRead. ¿Cómo sabe el apuntador qué tan grande es en realidad la estructura WIN32_STREAM_ID dado que es de tamaño variable? No es así El segundo problema tiene que ver con el empaque y la alineación. Haciendo caso omiso de cStreamName por un momento, considere la siguiente posibilidad de que su contraparte WIN32_STREAM_ID administrado:

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

int32 es de 4 bytes de tamaño y Int64 es de 8 bytes. Por lo tanto, es de esperar que esta estructura tenga 20 bytes.Sin embargo, si ejecuta el código siguiente, usted encontrará que ambos valores son 24, no 20:

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

La cuestión es que el compilador quiere asegurarse de que los valores dentro de estas estructuras siempre están alineados en el límite apropiado Los valores de cuatro bytes deben estar en direcciones divisibles por 4, los valores de 8 bytes deben estar en los límites divisibles por 8, y así sucesivamente. Ahora imagine lo que sucedería si fuera a crear una matriz de estructuras Win32StreamID. Todos los campos en la primera instancia de la matriz se alinearán correctamente. Por ejemplo, dado que el campo Tamaño sigue dos enteros de 32 bits, serían 8 bytes desde el inicio de la matriz, perfecto para un valor de 8 bytes. Sin embargo, si la estructura tiene un tamaño de 20 bytes, la segunda instancia de la matriz no tendrá todos sus miembros alineados correctamente. Los valores enteros estarían todos bien, pero el valor largo sería 28 bytes desde el inicio de la matriz, un valor no divisible equitativamente por 8. Para solucionar esto, el compilador ajusta la estructura a un tamaño de 24, de modo que todos los campos siempre estarán alineados correctamente (suponiendo que la matriz en sí). Si el compilador está haciendo lo correcto, es posible que se pregunte por qué me preocupa esto. Verá por qué si mira el código de la Figura 2. Para evitar el primer problema de cálculo de referencias que describí, de hecho, dejo el cStreamName fuera de la estructura de Win32StreamID. Utilizo BackupRead para leer en suficientes bytes para llenar mi estructura Win32StreamID, y luego examino el campo dwStreamNameSize de la estructura. Ahora que sé cuánto tiempo dura el nombre, puedo usar BackupRead nuevamente para leer el valor de la cadena desde el archivo. Eso está muy bien, pero si Marshal.SizeOf devuelve 24 para mi estructura Win32StreamID en lugar de 20, trataré de leer demasiados datos. Para evitar esto, necesito asegurarme de que el tamaño de Win32StreamID sea de hecho 20 y no 24. Esto se puede lograr de dos maneras diferentes usando campos en StructLayoutAttribute que adorna la estructura. La primera es utilizar el campo Tamaño, que dicta que el tiempo de ejecución exactamente cuán grande es la estructura debe ser:

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

La segunda opción es utilizar el campo Paquete. El paquete indica el tamaño de embalaje que se debe usar cuando se especifica el valor LayoutKind.Sequential y controla la alineación de los campos dentro de la estructura. El tamaño de embalaje predeterminado para una estructura administrada es 8. Si cambio eso a 4, obtengo la estructura de 20 bytes que estoy buscando (y como en realidad no la estoy usando en una matriz, no pierdo eficacia). o estabilidad que podría resultar de un cambio de este tipo de embalaje):

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

Con este código en su lugar, ahora puedo enumerar todas las corrientes en un archivo, como se muestra aquí:

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); 
     } 
    } 
} 

Usted' Notaré que esta versión de FileStreamSearcher devuelve más información que la versión que usa FindFirstStreamW y FindNextStreamW. BackupRead puede proporcionar datos en más que solo la transmisión principal y los flujos de datos alternativos, que también operan en transmisiones que contienen información de seguridad, datos de análisis y más. Si solo desea ver los flujos de datos alternativos, puede filtrar en función de la propiedad Tipo de StreamInfo, que será StreamType.AlternateData para flujos de datos alternativos. Para probar este código, puede crear un archivo que tiene Alternate Data Streams mediante el comando echo en el símbolo del sistema:

> 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" 

Así pues, ahora usted es capaz de recuperar los nombres de todos los flujos de datos almacenados en Alternos un archivo. Estupendo. Pero, ¿y si realmente quiere manipular los datos en una de esas secuencias? Desafortunadamente, si intenta pasar una ruta para un flujo de datos alternativo a uno de los constructores de FileStream, se lanzará una NotSupportedException: "El formato de la ruta dada no es compatible". Para evitar esto, puede omitir las comprobaciones de canonicalización de rutas de FileStream accediendo directamente a la función CreateFile expuesta desde kernel32.dll (vea Figura 3). He usado una P/Invoke para la función CreateFile para abrir y recuperar un SafeFileHandle para la ruta especificada, sin realizar ninguna de las comprobaciones de permisos administrados en la ruta, por lo que puede incluir identificadores Alternate Data Stream. A continuación, este SafeFileHandle se utiliza para crear un nuevo FileStream administrado, que proporciona el acceso requerido. Con eso en su lugar, es fácil manipular los contenidos de un flujo de datos alternativo utilizando la funcionalidad del espacio de nombres System.IO. El siguiente ejemplo lee e imprime los contenidos de la C: \ test.txt: magStream creado en el ejemplo anterior:

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

Figura 3 Uso de P/Invoke para CreateFile

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 en MSDN Magazine from January 2006.

+4

Un buen ejemplo de por qué las respuestas de solo enlace son malas. –

+0

Todos los enlaces a las revistas de MSDN están rotos, y los enlaces al sitio web de MSDN también se romperán pronto. Por favor incluye más detalles sobre tu respuesta. – AaA

14

Este paquete nuget CodeFluent Runtime Client tiene (entre otras utilidades) un NtfsAlternateStream Class que admite operaciones de creación/lectura/actualización/eliminación/enumeración.

+7

Y aquí tiene algunos ejemplos sobre cómo usarlo http://blog.codefluententities.com/2013/03/14/manipulating-ntfs-alternate-data-streams-inc-c-with-the-codefluent-runtime- cliente/ – polkduran

Cuestiones relacionadas