Click here to Skip to main content
Click here to Skip to main content

Accessing alternative data-streams of files on an NTFS volume

By , 28 Jul 2010
 

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 six years ago, 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);

// List the additional streams for a file:
foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
{
    Console.WriteLine("{0} - {1} bytes", s.Name, s.Size);
}

// Read the "Zone.Identifier" stream, if it exists:
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());
    }
    
    // Delete the stream:
    s.Delete();
}
else
{
    Console.WriteLine("No zone identifier stream found.");
}

// Alternative method to delete the stream:
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Richard Deeming
Software Developer Nevalee Business Solutions
United Kingdom United Kingdom
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionNot showing all datamemberledtech33 Jan '13 - 9:29 
When I use this to list ADS for C:\ProgramData\TEMP I can list all 29 Data streams names but if I try to read each data value also and build with a string builder it will only return the first three even though the counter shows it is going thru all 29.
 
Can you only get data for at most 3 at a time ?
 
I may have to do like one program does and only let you get data for one at a time.
GeneralWow :)memberVitaly Tomilov25 Jun '12 - 10:47 
This article is a survivor... 2002 and still maintained? There wasn't much to start with Smile | :)
QuestionSerializationmemberJohn Vonesh20 Apr '12 - 7:22 
Having implemented this code in our project I must say I am very impressed with how well this works. One catch I have run into (not sure if I'm just not using it right) is that I cannot serialize/deserialize the streams because they are not actual StreamReader/StreamWriter objects. Is there a known workaround for this?
AnswerRe: SerializationmemberRichard Deeming20 Apr '12 - 7:37 
The AlternateDataStreamInfo class exposes the Open method, with equivalent overloads to the File.Open / FileInfo.Open methods. It also exposes the OpenRead and OpenWrite methods, which mirror the equivalent static methods on the File class and instance methods on the FileInfo class.
 
All of these methods return a FileStream instance. If you need a StreamReader or StreamWriter for the stream, you can simply pass the FileStream object to the constructor:
 
AlternateDataStreamInfo ads = FileSystem.GetAlternateDataStream(filePath, streamName);
using (FileStream stream = ads.Open(FileMode.OpenOrCreate, FileAccess.Write))
using (StreamWriter writer = new StreamWriter(stream))
{
   // And then the text-based magic happens! :)
}
 
Similarly, if you need a BinaryReader or BinaryWriter for the stream, just pass the FileStream object to the constructor:
AlternateDataStreamInfo ads = FileSystem.GetAlternateDataStream(filePath, streamName);
using (FileStream stream = ads.Open(FileMode.OpenOrCreate, FileAccess.Write))
using (BinaryWriter writer = new BinaryWriter(stream))
{
   // And then the low-level binary magic happens! :)
}



"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer


GeneralRe: SerializationmemberJohn Vonesh20 Apr '12 - 9:02 
Interestingly, your code samples didn't work, but the ones I thought I was trying to write previous to my post are now working. Here is what I have working:
                BinaryFormatter bfReader = new BinaryFormatter();
                using (AlternateDataStreamInfo ads = Storage.GetAlternateDataStream(streamName, FileMode.Open))
                using (FileStream stream = ads.Open(FileMode.OpenOrCreate, FileAccess.Read))
                    metaData = metaData.FromString(bfReader.Deserialize(stream).ToString());

GeneralRe: Serialization [modified]memberJohn Vonesh20 Apr '12 - 9:57 
So in working with the various examples I have been able to glean from this post, I have found that you do not need a BinaryWriter object in order to save data to the alternate stream. In fact, I get an "The best overloaded method match for 'System.Runtime.Serialization.Binary.BinaryFormatter.Serialize(System.IO.Stream, object)' has some invalid arguments" exception when I use this block of code:
    BinaryFormatter bfSettings = new BinaryFormatter();
    AlternateDataStreamInfo ads = Storage.GetAlternateDataStream(_altStreamName);
    using (FileStream fsSettings = ads.Open(FileMode.OpenOrCreate, FileAccess.Write))
    using (BinaryWriter bwSettings = new BinaryWriter(fsSettings))
        bfSettings.Serialize(bwSettings, Settings.ToString());
 
But if I do not use the BinaryWriter like this:
    BinaryFormatter bfSettings = new BinaryFormatter();
    AlternateDataStreamInfo ads = Storage.GetAlternateDataStream(_altStreamName);
    using (FileStream fsSettings = ads.Open(FileMode.OpenOrCreate, FileAccess.Write))
        bfSettings.Serialize(fsSettings, Settings.ToString());
it works fine.
 
Just posting this in case anyone else encounters the same issue.

modified 3 May '12 - 7:33.

QuestionerrormemberPrince iraq16 Mar '12 - 0:11 
Error in code
AnswerRe: errormemberRichard Deeming16 Mar '12 - 3:04 
Prince iraq wrote:
Error in code

Thanks for that highly-informative error report. I'll get right on fixing that. WTF | :WTF:



"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer


GeneralRe: errormemberPrince iraq16 Mar '12 - 7:40 
pleas can you upload source code in my Email
I need your programs
my email
alovenoor@gmail.com
Question2Tb Virtual Diskmembernhchmg27 Jun '11 - 20:16 
I test on 2TB Virtual Disk,it simulate a 2TB disk,1024,2048,4096 bytes/secotr,this app can not work.I found it on http://www.2tdisk.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 28 Jul 2010
Article Copyright 2002 by Richard Deeming
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid