Page 1 of 1

Reading and Writing Alternate Streams in C#

#1 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 04 March 2009 - 07:54 PM

Reading and writing NTFS alternate file streams is pretty simple if you first write the code to access the Win32 functions required.

Here are the steps to get create and build the the tutorial:
1. create a new "Console Application" project in Visual Studio: File->New->Project, name it something like "AltStreamTutorial."
2. add the PInvokeWin32Api class: in "Solution Explorer" right mouse click on the project, Add->New Item, name it PInvokeWin32Api and add the following code
Here is the class I call PInvokeWin32.api that has the code that makes it possible to call these functions from C#. All it does is make the Win32 functions declared in the kernel.dll available to C# code. For more information you can visit the PInvoke.net web site.

I've added comments inline to explain what is happening.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace AlternateStreamsTutorial
{
	class PInvokeWin32Api
	{
		#region constants
//
// these are constants used by the Win32 api functions.  They can be found in the documentation and header files.
//
		public const UInt32 GENERIC_READ = 0x80000000;
		public const UInt32 GENERIC_WRITE = 0x40000000;
		public const UInt32 FILE_SHARE_READ = 0x00000001;
		public const UInt32 FILE_SHARE_WRITE = 0x00000002;
		public const UInt32 FILE_ATTRIBUTE_DIRECTORY = 0x00000010;

		public const UInt32 CREATE_NEW = 1;
		public const UInt32 CREATE_ALWAYS = 2;
		public const UInt32 OPEN_EXISTING = 3;
		public const UInt32 OPEN_ALWAYS = 4;
		public const UInt32 TRUNCATE_EXISTING = 5;
		#endregion
		
		#region dll imports
//
// DllImport statements identify specific functions and declare their C# function signature
// 
		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr CreateFile(
			string lpFileName,
			uint dwDesiredAccess,
			uint dwShareMode,
			IntPtr lpSecurityAttributes,
			uint dwCreationDisposition,
			uint dwFlagsAndAttributes,
			IntPtr hTemplateFile);

		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool CloseHandle(
			IntPtr hObject);

		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool GetFileInformationByHandle(
			IntPtr hFile,
			out BY_HANDLE_FILE_INFORMATION lpFileInformation);

		[DllImport("kernel32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool DeleteFile(
			string fileName);

		[DllImport("kernel32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool ReadFile(
			IntPtr hFile,
			IntPtr lpBuffer,
			uint nNumberOfBytesToRead,
			out uint lpNumberOfBytesRead,
			IntPtr lpOverlapped);

		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool WriteFile(
			IntPtr hFile,
			IntPtr bytes,
			uint nNumberOfBytesToWrite,
			out uint lpNumberOfBytesWritten,
			int overlapped);

		[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool WriteFile(
			IntPtr hFile,
			byte[] lpBuffer,
			uint nNumberOfBytesToWrite,
			out uint lpNumberOfBytesWritten,
			int overlapped);

		[DllImport("kernel32.dll")]
		public static extern void ZeroMemory(IntPtr ptr, int size);
		#endregion

		#region structures
//
// This section declares the structures used by the Win32 functions so that the information can be accessed by C# code
//
		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		public struct BY_HANDLE_FILE_INFORMATION
		{
			public uint FileAttributes;
			public FILETIME CreationTime;
			public FILETIME LastAccessTime;
			public FILETIME LastWriteTime;
			public uint VolumeSerialNumber;
			public uint FileSizeHigh;
			public uint FileSizeLow;
			public uint NumberOfLinks;
			public uint FileIndexHigh;
			public uint FileIndexLow;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 1)]
		public struct FILETIME
		{
			public uint DateTimeLow;
			public uint DateTimeHigh;
		}
		#endregion

		#region functions
//
// These are the functions in C# that wrap the Win32 functions
//

		//
		// this functions writes the string text to the alternate stream named altStreamName of the file whose path is currentfile
		//
		public static void WriteAlternateStream(string currentfile, string altStreamName, string text)
		{
			string altStream = currentfile + ":" + altStreamName;
			IntPtr txtBuffer = IntPtr.Zero;
			IntPtr hFile = IntPtr.Zero;
			DeleteFile(altStream);
			try
			{
				//
				// call CreateFile
				// 
				hFile = CreateFile(altStream, GENERIC_WRITE, 0, IntPtr.Zero,
									   CREATE_ALWAYS, 0, IntPtr.Zero);
				if (-1 != hFile.ToInt32())  // check the return code for success
				{
					txtBuffer = Marshal.StringToHGlobalUni(text);
					uint nBytes, count;
					nBytes = (uint)text.Length;
					bool bRtn = WriteFile(hFile, txtBuffer, sizeof(char) * nBytes, out count, 0);
					if (!bRtn)
					{
						if ((sizeof(char) * nBytes) != count)
						{
							throw new Exception(string.Format("Bytes written {0} should be {1} for file {2}.",
								count, sizeof(char) * nBytes, altStream));
						}
						else
						{
							throw new Exception("WriteFile() returned false");
						}
					}
				}
				else
				{
					throw new Win32Exception(Marshal.GetLastWin32Error());
				}
			}
			catch (Exception exception)
			{
				string msg = string.Format("Exception caught in WriteAlternateStream()\n  '{0}'\n  for file '{1}'.",
					exception.Message, altStream);
			}
			finally
			{
				CloseHandle(hFile);
				hFile = IntPtr.Zero;
				Marshal.FreeHGlobal(txtBuffer);
				GC.Collect();
			}
		}

		//
		// this function reads the alternate stream named altStreamName of the file whose path is currentfile and returns the contents as a string
		//
		public static string ReadAlternateStream(string currentfile, string altStreamName)
		{
			IntPtr hFile = IntPtr.Zero;
			string returnstring = string.Empty;
			string altStream = currentfile + ":" + altStreamName;

			IntPtr buffer = IntPtr.Zero;
			try
			{
				hFile = CreateFile(altStream, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
				if (-1 != hFile.ToInt32())
				{
					buffer = Marshal.AllocHGlobal(1000 * sizeof(char));
					ZeroMemory(buffer, 1000 * sizeof(char));
					uint nBytes;
					bool bRtn = ReadFile(hFile, buffer, 1000 * sizeof(char), out nBytes, IntPtr.Zero);
					if (bRtn)
					{
						if (nBytes > 0)
						{
							returnstring = Marshal.PtrToStringAuto(buffer);
						}
						else
						{
							throw new Exception("ReadFile() returned true but read zero bytes");
						}
					}
					else
					{
						if (nBytes <= 0)
						{
							throw new Exception("ReadFile() read zero bytes.");
						}
						else
						{
							throw new Exception("ReadFile() returned false");
						}
					}
				}
				else
				{
					Exception excptn = new Win32Exception(Marshal.GetLastWin32Error());
					if (!excptn.Message.Contains("cannot find the file"))
					{
						throw excptn;
					}
				}
			}
			catch (Exception exception)
			{
				string msg = string.Format("Exception caught in ReadAlternateStream(), '{0}'\n  for file '{1}'.",
					exception.Message, currentfile);
			}
			finally
			{
				CloseHandle(hFile);
				hFile = IntPtr.Zero;
				if (buffer != IntPtr.Zero)
				{
					Marshal.FreeHGlobal(buffer);
				}
				GC.Collect();
			}
			return returnstring;
		}
		#endregion
	}
}




3. edit Program.cs file to look like the following code:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace AlternateStreamsTutorial
{
	class Program
	{
		static void Main(string[] args)
		{
			string fileName = string.Empty;
			string altStreamName = string.Empty;
			List<string> lowercaseArgs = null;

			//
			// get commmandline argument values for fileName and altStreamName
			//
			if (args != null && args.Length > 0)
			{
				lowercaseArgs = new List<string>();
				for(int i=0; i<args.Length; i++)
				{
					lowercaseArgs.Add(args[i].ToLower());
				}
				for (int i = 0; i < lowercaseArgs.Count; i++ )
				{
					if (lowercaseArgs[i].StartsWith("/file"))
					{
						fileName = lowercaseArgs[++i];
					}
					else if (lowercaseArgs[i].StartsWith("/altstream"))
					{
						altStreamName = lowercaseArgs[++i];
					}
				}
			}

			//
			// test for valid commandline
			//
			if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(altStreamName))
			{
				//
				// bad commandline
				//
				DisplayUsage();
				return;
			}

			//
			// if the file doesn't exist, create it
			//
			if (!File.Exists(fileName))
			{
				// 
				// check to see if the directory exists before attempting to create the file
				//
				string directory = Path.GetDirectoryName(fileName);
				if (Directory.Exists(directory))
				{
					File.Create(fileName);
				}
				else
				{
					//
					// write error message and display usage to standard out.
					//
					Console.WriteLine("Directory '{0}' does not exist", directory);
					DisplayUsage();
					return;
				}
			}

			// 
			// ask for some text from user
			//
			Console.WriteLine("Enter a line of text");
			string someText = Console.ReadLine();

			//
			// write alternate stream
			//
			PInvokeWin32Api.WriteAlternateStream(fileName, altStreamName, someText);

			//
			// read alternate stream
			//
			string altStreamText = PInvokeWin32Api.ReadAlternateStream(fileName, altStreamName);

			//
			// display alternate stream text
			//
			Console.WriteLine();
			Console.WriteLine("Text read from alternate stream '{0}'", altStreamName);
			Console.WriteLine(altStreamText);

			Console.WriteLine("Enter any key to exit");
			Console.Read();
		}

		//
		// Display usage information
		//
		private static void DisplayUsage()
		{
			Console.WriteLine("Usage:  AlternateStreamsTutorial /file filename /altstream altstreamname");
			Console.WriteLine("  filename	   full path of file to play with");
			Console.WriteLine("  altstreamname  name of alternate stream to read and write");

			Console.WriteLine("Enter any character to exit");
			Console.Read();
		}
	}
}



So, why would I want to read and write alternate file streams? The possibilities are endless. Many internet files contain 'zone' information stored in the alternate stream of the file. I may want to associate various files like a document and the spreadsheet xls document that contains the data from which the document was generated. I can use multiple named streams on any file to accomplish many things.

Is This A Good Question/Topic? 0
  • +

Replies To: Reading and Writing Alternate Streams in C#

#2 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 06 March 2009 - 09:56 AM

If there is any interest, I can do a tutorial much like this on using Win32 function DeviceIoControl to read the NTFS Master File Table or access other device information. Let me know if there is interest.
Was This Post Helpful? 0
  • +
  • -

#3 Guest_Veer*


Reputation:

Posted 06 February 2010 - 12:18 PM

View PostStCroixSkipper, on 06 March 2009 - 08:56 AM, said:

If there is any interest, I can do a tutorial much like this on using Win32 function DeviceIoControl to read the NTFS Master File Table or access other device information. Let me know if there is interest.



Can you please explain the code to all the c# beginners like me, to read the mft in detail? Or as you said can you do a tutorial? It would be of much help

Thanks,
Veer
Was This Post Helpful? 0

#4 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 06 February 2010 - 12:43 PM

I have contributed code a couple of times for reading and writing the MFT. If you search on 'StCroixSkipper' you should find it. It is on one of the MSDN forums.

Let me know if this helps.
Was This Post Helpful? 0
  • +
  • -

#5 Guest_Veer*


Reputation:

Posted 06 February 2010 - 01:31 PM

I didn't come to this website by chance. Actually StCroixSkipper has become a very familiar name in our group after seeing the code snippets in msdn forum. You really are a great man, helped the managed community when nobody was ready to bring up the c++ codes to this level. Thanks a lot. I'm working on this assignment of reading the MFT for around a week. Your code works!!! Anyways wanted some detailed explanation on the codes. That's why replied you in this thread.
Was This Post Helpful? -1

#6 Guest_Veer*


Reputation:

Posted 07 February 2010 - 11:41 PM

There are some difference in the total files retrieved using the MFT code snippet you gave and the those retrieved using the DirectoryInfo.GetFiles() Method. They are 180K and 218K respectively. Can you help me here?
Was This Post Helpful? 0

#7 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 08 February 2010 - 04:06 PM

I have seen several entries that I find enumerating the MFT that I don't see when I enumerate using FindFirst() and FindNext() or GetFiles(). I assume that NTFS uses these entries for its own purpose. I usually ignore them since I've only been interested in entries available to the user.

On the explanation of the code, I've found an article by Jeffrey Cooperstein and Charles Petzold that was really interesting. You might find it helpful. Here is the link:

http://technet.micro...y/bb742450.aspx

Hope it helps.

This post has been edited by StCroixSkipper: 08 February 2010 - 04:09 PM

Was This Post Helpful? 0
  • +
  • -

#8 Guest_Veer*


Reputation:

Posted 09 February 2010 - 03:36 AM

View PostStCroixSkipper, on 08 February 2010 - 03:06 PM, said:

I have seen several entries that I find enumerating the MFT that I don't see when I enumerate using FindFirst() and FindNext() or GetFiles().


I experienced the other way around. The GetFiles() method gives me more files than the MFT. Around 40,000 files are missing, if i use the latter. One possible cause could be that my journaling may be disabled in some drives. Do you've any idea how to enable journaling using code?
Was This Post Helpful? 0

#9 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 09 February 2010 - 06:45 PM

I've thoroughly tested the code the enumerates the Master File Table, not once but several times to ensure that I did actually find all the files returned from FindFirst()/FindNext(). I built a SortedList of both entries with full paths both ways and found each entry in the FindFirst()/FindNext() list in the MFT list.

As I said, there were a few other entries in the MFT list but not the other way around.

Here is a code clip that creates the USN Journal. If the journal exists, it looks at the maximum size and allocation delta to see if they need to be increased. Otherwise, it is a noop.

		unsafe private void CreateUsnJournal() 
		{
            Log.DebugFormat("CreateUsnJournal() function entered for drive '{0}'", _driveInfo.Name);

			// This function creates a journal on the volume. If a journal already
			// exists this function will adjust the MaximumSize and AllocationDelta
			// parameters of the journal
			UInt64 MaximumSize = 0x10000000;
			UInt64 AllocationDelta = 0x100000;
			UInt32 cb;
			Win32Api.CREATE_USN_JOURNAL_DATA cujd;
			cujd.MaximumSize = MaximumSize;
			cujd.AllocationDelta = AllocationDelta;

			int sizeCujd = Marshal.SizeOf(cujd);
			IntPtr cujdBuffer = Marshal.AllocHGlobal(sizeCujd);
			Win32Api.ZeroMemory(cujdBuffer, sizeCujd);
			Marshal.StructureToPtr(cujd, cujdBuffer, true);

            if (_changeJournalRootHandle.ToInt32() == 0)
            {
                GetRootHandle();
            }

			bool fOk = Win32Api.DeviceIoControl(
				_changeJournalRootHandle, 
				Win32Api.FSCTL_CREATE_USN_JOURNAL,
				cujdBuffer, 
				sizeCujd, 
				IntPtr.Zero, 
				0, 
				out cb, 
				IntPtr.Zero);
			if (!fOk)
			{
				throw new IOException("DeviceIoControl() returned false", 
					new Win32Exception(Marshal.GetLastWin32Error()));
			}
			
			int sizeUjd = sizeof(Win32Api.USN_JOURNAL_DATA);
			fOk = Win32Api.DeviceIoControl(
				_changeJournalRootHandle, 
				Win32Api.FSCTL_QUERY_USN_JOURNAL, 
				IntPtr.Zero,
				0,
				out _currentUsnState,
				sizeUjd,
				out cb,
				IntPtr.Zero);
			if (fOk)
			{
				//Log.Info("Query Change Journal");
				//Log.InfoFormat("  Journal ID:	'{0}'", ujd.UsnJournalID);
				//Log.InfoFormat("   First USN:	'{0}'", ujd.FirstUsn);
				//Log.InfoFormat("    Next USN:   '{0}'", ujd.NextUsn);
				//Log.InfoFormat("     Max USN:	'{0}'", ujd.MaxUsn);
				Log.Info("Current usn journal state");
                Log.InfoFormat("     Vol Ser: '{0}'", _volumeSerialNumber);
				Log.InfoFormat("  Journal ID: '{0}'", _currentUsnState.UsnJournalID);
				Log.InfoFormat("   First USN: '{0}'", _currentUsnState.FirstUsn);
				Log.InfoFormat("    Next USN: '{0}'", _currentUsnState.NextUsn);
				Log.InfoFormat("     Max USN: '{0}'", _currentUsnState.MaxUsn);
			}
			else
			{
				throw new IOException("DeviceIocontrol() returned false for Query Usn Journal",
					new Win32Exception(Marshal.GetLastWin32Error()));
			}
		}



Hope this helps.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1