Introduction
Since NT 3.1, the NTFS file system has supported multiple data-streams for files. There has never been built-in support for viewing or manipulating these additional streams, but the Windows API functions include support for them with a special file syntax: Filename.ext:StreamName
. Even Win9x machines can access the alternative data streams of files on any NTFS volume they have access to, e.g., through a mapped drive. Because the Scripting.FileSystemObject
and many other libraries call the CreateFile
API behind the scenes, even scripts have been able to access alternative streams quite easily (although enumerating the existing streams has always been tricky).
In .NET, however, it seems someone decided to add some checking to the format of filenames. If you attempt to open a FileStream
on an alternative stream, you will get a "Path Format not supported" exception. I have been unable to find any class in the CLR that provides support for alternative data streams, so I decided to roll my own.
Update
I originally wrote this code in 2002, targeting v1 of the .NET Framework. Looking at the code now, it seems quite messy, and has several bugs and problems which were mentioned in the comments. I have since completely re-written the code for .NET v3.5, and (hopefully) fixed the bugs.
The new code is not compatible with the original version. However, I have included a sample compatibility wrapper which maps the old API to the new API. You can find these files under the "other/Compatibility wrapper" folder in the download.
Bugs / Issues Fixed
- The code now uses the
FileSystemInfo
class rather than the FileInfo
class. This allows you to access alternate data streams attached to folders as well as files. (Suggested by Dan Elebash.)
- The code now uses
SafeFileHandle
instead of IntPtr
for the file handle. (Suggested by Moomansun.)
- The code is now 64-bit compatible. (Reported by John SMith 5634552745.)
- The code now correctly calls
BackupRead
with the bAbort
parameter set to true
after enumerating the streams. (Reported by scooter_jsm.)
- The number of global memory allocations required to read the streams from a file has been reduced. (Suggested by scooter_jsm.)
- v2.1:
ValidateStreamName
now accepts stream names which contain characters with ASCII codes between 1 and 31. (Reported by Andy Missico.)
- v2.1: The code will now attempt to map standard IO errors to the equivalent .NET
Exception
type. (Suggested by Andy Missico.)
Using the Classes
The AlternateDataStreamInfo
class represents the details of an individual stream, and provides methods to create, open, or delete the stream.
The static FileSystem
class provides methods to retrieve the list of streams for a file, retrieve a specific stream from a file, determine whether a stream exists, and delete a specific stream.
All methods on the FileSystem
class offer overloads which accept either a path or a FileSystemInfo
object. The overloads which accept a FileSystemInfo
object can also be invoked as extension methods.
Example:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Trinet.Core.IO.Ntfs;
...
FileInfo file = new FileInfo(path);
foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
{
Console.WriteLine("{0} - {1} bytes", s.Name, s.Size);
}
if (file.AlternateDataStreamExists("Zone.Identifier"))
{
Console.WriteLine("Found zone identifier stream:");
AlternateDataStreamInfo s =
file.GetAlternateDataStream("Zone.Identifier",
FileMode.Open);
using (TextReader reader = s.OpenText())
{
Console.WriteLine(reader.ReadToEnd());
}
s.Delete();
}
else
{
Console.WriteLine("No zone identifier stream found.");
}
file.DeleteAlternateDataStream("Zone.Identifier");
Files Included
- The Trinet.Core.IO.Ntfs folder contains the source code.
- The doc folder contains the documentation and FxCop project.
- The bin folder contains the compiled assembly.
- The other folder contains the compatibility wrapper, and a sample to recursively delete the "Zone.Identifier" stream from all files in a given path.
References
If you want more information on NTFS programming, or the C++ code I based this on, see Dino Esposito's MSDN article from March 2000: http://msdn.microsoft.com/en-us/library/ms810604.aspx [^].
History
- v1 - 1 August 2002 - Initial release, targeting .NET 1.0.
- v2 - 16 September 2008 - Re-written to target .NET 3.5; major changes include:
- Replaced
FileInfo
with FileSystemInfo
.
- Replaced
IntPtr
with SafeFileHandle
.
- Made the P/Invoke calls 64-bit compatible.
- Made the code abort the
BackupRead
operation after enumerating the streams.
- Reduced the number of global memory allocations required.
- v2.1 - 28 July 2010 - Minor fixes suggested by Andy Missico:
- Changed
ValidateStreamName
to accept stream names containing characters with ASCII codes between 1 and 31 (see msdn.microsoft.com/en-us/library/aa365247).
- Added mapping of standard IO errors to the correct .NET exceptions.
- v2.2 - 15 August 2016 - Fix for bug reported by Member 12683101, where zero-length alternate streams prevented subsequent streams from being enumerated.