Alternative NTFS-.NET Data Streams

How do I create / delete / read / write / alternative NTFS data streams from .NET?

If there is no native .NET support, which Win32 API would I use? Also, how will I use them as I don't think this is documented?

+42
c # ntfs alternate-data-stream
Mar 03 '09 at 3:11
source share
5 answers

Not in .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); } 
+4
Mar 03 '09 at 3:14
source share

Here is the version for 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; } 
+30
Mar 03 '09 at 3:29
source share

This nuget CodeFluent Runtime Client package has (among other utilities) the NtfsAlternateStream class , which supports creating / reading / updating / deleting / listing operations.

+14
Jan 23 '13 at 8:59
source share

There is no built-in .NET support for them. You must use P / Invoke to call your own Win32 methods.

To create them, call CreateFile using a path such as filename.txt:streamname . If you use an interop call that returns SafeFileHandle, you can use it to create a FileStream that you can then read and write.

To list the streams existing in the file, use FindFirstStreamW and FindNextStreamW (which exist only on server 2003 and later, and not on XP).

I do not believe that you can delete a stream, except that you copy the rest of the file and leave one of the streams. Setting the length to 0 may also work, but I have not tried it.

You may also have alternate data streams in the directory. You access them in the same way as the files - C:\some\directory:streamname .

Streams can have compression, encryption, and sparseness installed on them, regardless of the default thread.

+13
Mar 03 '09 at 5:04
source share

A First, nothing in the Microsoft® .NET Framework provides this functionality. If you want this, simple and simple, you will need to do some kind of interop, either directly or using a third-party library.

If you are using Windows Server ™ 2003 or later, Kernel32.dll provides FindFirstFile and FindNextFile mappings that provide the exact functionality you are looking for. FindFirstStreamW and FindNextStreamW allow you to find and list all alternative data streams in a specific file, receiving information about each, including its name and its length. The code for using these functions from managed code is very similar to the one I showed in my December column, and is shown in Figure 1.

Figure 1 Using FindFirstStreamW and 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(); } } } 

You simply call FindFirstStreamW, passing it the full path to the target file. The second parameter FindFirstStreamW determines the level of detail that you want in the returned data; currently there is only one level (FindStreamInfoStandard), which has a numerical value 0. The third parameter of the function is a pointer to the structure WIN32_FIND_STREAM_DATA (technically, which indicates the third parameter, it is determined by the value of the second parameter in detail describing the level of information, but since currently there is only one level, for all purposes and tasks it is WIN32_FIND_STREAM_DATA). I announced that the structure manages the analogy as a class, and in the interop signature I noted that it is marshaled as a pointer to the structure. The last parameter is reserved for future use and should be 0. If the correct handle is returned from FindFirstStreamW, the WIN32_FIND_STREAM_DATA instance contains information about the stream found, and its value cStreamName can be returned to the caller as the first stream name. FindNextStreamW accepts the handle returned from FindFirstStreamW and populates the WIN32_FIND_STREAM_DATA provided with information about the next available stream, if one exists. FindNextStreamW returns true if another stream is available, or false if not. As a result, I constantly call FindNextStreamW and give the resulting stream name until FindNextStreamW returns false. When this happens, I double-check the last error value to make sure the iteration is stopped, because FindNextStreamW has run out of threads, and not for some unexpected reason. Unfortunately, if you are using Windows® XP or Windows 2000 Server, these features are not available to you, but there are several alternatives. The first solution includes an undocumented function, which is currently exported from Kernel32.dll, NTQueryInformationFile. However, undocumented functions are undocumented for any reason, and you can change or even delete them at any time in the future. Better not use them. If you want to use this feature, search the web and you will find many links and sample source code. But do it at your own peril and risk. Another solution that I demonstrated in Figure 2 relies on two functions exported from Kernel32.dll, and they are documented. As their names imply, BackupRead and BackupSeek are part of the Win32 API for backup support:

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

Figure 2 Using BackupRead and 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(); } } } } 

The idea behind BackupRead is that it can be used to read data from a file into a buffer, which can then be written to backup media. However, BackupRead is also very convenient for obtaining information about each of the alternative data streams that make up the target file. It treats all the data in the file as a sequence of discrete byte streams (each alternate data stream is one of these byte streams), and each of the streams is preceded by a WIN32_STREAM_ID structure. Thus, to list all the streams, you just need to read all these WIN32_STREAM_ID structures from the beginning of each stream (here BackupSeek becomes very convenient, since it can be used to go from stream to stream without reading all the data in the file). To get started, you first need to create a managed instance for the unmanaged WIN32_STREAM_ID structure:

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

For the most part, this is similar to any other structure you plan through P / Invoke. However, there are several complications. First of all, WIN32_STREAM_ID is a variable size structure. Its last member, cStreamName, is an array with a length of ANYSIZE_ARRAY. While ANYSIZE_ARRAY is defined as 1, cStreamName is simply the address of the rest of the data in the structure after the previous four fields, which means that if the structure is allocated as size than sizeof (WIN32_STREAM_ID), then this extra space will actually be part of the cStreamName array. The previous field, dwStreamNameSize, determines exactly how long the array will last. Although this is great for Win32 development, it is detrimental to the marshal who needs to copy this data from unmanaged memory to managed memory as part of the interop call in BackupRead. How does the marshaler know how big the WIN32_STREAM_ID structure really is, given that it has the size of a variable? This is not true. The second problem is packaging and alignment. Ignoring cStreamName for a moment, consider the following option for a managed copy of WIN32_STREAM_ID:

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

Int32 is 4 bytes and Int64 is 8 bytes. So you expect this structure to be 20 bytes. However, if you run the following code, you will see that both values ​​are 24, not 20:

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

The problem is that the compiler wants to make sure that the values ​​inside these structures are always aligned at the appropriate boundary. Four byte values ​​must be in addresses divisible by 4, 8-byte values, must be in boundaries divisible by 8, etc. Now imagine what happens if you create an array of Win32StreamID structures. All fields in the first instance of the array will be correctly aligned. For example, since the Size field follows two 32-bit integers, it will be 8 bytes from the beginning of the array, ideal for an 8-byte value. However, if the structure was 20 bytes in size, the second instance in the array would not have all the elements that were correctly aligned. The integer values ​​will be all right, but the long value will be 28 bytes from the beginning of the array, the value will not be evenly divided by 8. To fix this, the compiler imposes a structure on size 24 so that all fields will always be correctly aligned (assuming that the array itself ) If the compiler does the right thing, you might be wondering why I'm worried about this. You will understand why, if you look at the code in Figure 2. To get around the first marshaling problem that I described, I actually leave cStreamName from the Win32StreamID structure. I use BackupRead to read in enough bytes to populate my Win32StreamID structure, and then look at the dwStreamNameSize field of the structure. Now that I know how long this name is, I can again use BackupRead to read in a string value from a file. This is all good and dandy, but if Marshal.SizeOf returns 24 for my Win32StreamID structure instead of 20, I will try to read too much data. To avoid this, I need to make sure that the size of Win32StreamID is actually 20, not 24. This can be done in two different ways using the fields in the StructLayoutAttribute that adorns the structure. The first is to use the Size field, which determines the runtime as large as possible:

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

The second option is to use the Package field. Pack indicates the size of the package to use when LayoutKind.Sequential is set and controls the alignment of the fields within the structure. The default packaging size for the managed structure is 8. If I change this to 4, I get the 20-byte structure that I am looking for (and since I do not use it in the array, I do not lose the efficiency or stability that may result from this packaging changes):

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

With this code, I can now list all the streams in a file, as shown below:

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

You will notice that this version of FileStreamSearcher returns more information than the version using FindFirstStreamW and FindNextStreamW. BackupRead can provide data not only for the main stream and alternative data streams, but also for streams containing security information, data reprocessing, etc. If you want to see alternative data streams, you can filter based on the StreamInfo Type property, which will be StreamType.AlternateData for alternative data streams. To test this code, you can create a file with alternative data streams using the echo command on the command line:

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

So, now you can get the names of all the alternative data streams stored in the file. Fine. But what if you want to actually manipulate data in one of these threads? Unfortunately, if you try to pass the path for an alternative data stream to one of the FileStream constructors, a NotSupportedException will be thrown: "This path format is not supported." To get around this, you can get around the canonicalization check of the FileStream path by directly accessing the CreateFile function open from kernel32.dll (see Figure 3 ). I used the P / Invoke function for CreateFile to open and retrieve SafeFileHandle for the specified path without performing any checks for managed permission on the path, so it may include alternative data flow identifiers. This SafeFileHandle is then used to create a new managed FileStream that provides the necessary access. This makes it easy to manipulate the contents of the alternate data stream using the System.IO namespace functionality. The following example reads and displays the contents of C: \ test.txt: magStream created in the previous example:

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

Figure 3 Using P / Invoke for 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 has been at MSDN Magazine since January 2006 .

+6
Nov 02 2018-11-11T00:
source share