Click here to Skip to main content
15,891,431 members
Articles / Programming Languages / C#

Accessing alternative data-streams of files on an NTFS volume

Rate me:
Please Sign up or sign in to vote.
4.85/5 (53 votes)
15 Aug 2016CPOL4 min read 503.3K   5.4K   132  
A pair of classes to encapsulate access to NTFS alternative data streams.
/*
  * Trinet.Core.IO.Ntfs - Utilities for working with alternate data streams on NTFS file systems.
  * Copyright (C) 2002-2010 Richard Deeming
  * 
  * This code is free software: you can redistribute it and/or modify it under the terms of either
  * - the Code Project Open License (CPOL) version 1 or later; or
  * - the GNU General Public License as published by the Free Software Foundation, version 3 or later; or
  * - the BSD 2-Clause License;
  * 
  * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
  * See the license files for details.
  * 
  * You should have received a copy of the licenses along with this code. 
  * If not, see <http://www.codeproject.com/info/cpol10.aspx>, <http://www.gnu.org/licenses/> 
  * and <http://opensource.org/licenses/bsd-license.php>.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Permissions;

namespace Trinet.Core.IO.Ntfs
{
	using Resources = Properties.Resources;

	/// <summary>
	/// File-system utilities.
	/// </summary>
	public static class FileSystem
	{
		#region Create FileSystemInfo

		/// <summary>
		/// Creates a <see cref="FileSystemInfo"/> for the specified path.
		/// </summary>
		/// <param name="path">
		/// The path of the file or directory.
		/// </param>
		/// <returns>
		/// The <see cref="FileSystemInfo"/> representing the file or directory.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="path"/> is <see langword="null"/> or empty.
		/// </exception>
		private static FileSystemInfo CreateInfo(string path)
		{
			if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");

			path = Path.GetFullPath(path);
			if (!File.Exists(path) && Directory.Exists(path)) return new DirectoryInfo(path);
			return new FileInfo(path);
		}

		#endregion

		#region List Streams

		/// <summary>
		/// <span style="font-weight:bold;color:#a00;">(Extension Method)</span><br />
		/// Returns a read-only list of alternate data streams for the specified file.
		/// </summary>
		/// <param name="file">
		/// The <see cref="FileSystemInfo"/> to inspect.
		/// </param>
		/// <returns>
		/// A read-only list of <see cref="AlternateDataStreamInfo"/> objects
		/// representing the alternate data streams for the specified file, if any.
		/// If no streams are found, returns an empty list.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="file"/> is <see langword="null"/>.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="file"/> does not exist.
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission.
		/// </exception>
		public static IList<AlternateDataStreamInfo> ListAlternateDataStreams(this FileSystemInfo file)
		{
			if (null == file) throw new ArgumentNullException("file");
			if (!file.Exists) throw new FileNotFoundException(null, file.FullName);

			string path = file.FullName;
			new FileIOPermission(FileIOPermissionAccess.Read, path).Demand();

			return SafeNativeMethods.ListStreams(path)
				.Select(s => new AlternateDataStreamInfo(path, s))
				.ToList().AsReadOnly();
		}

		/// <summary>
		/// Returns a read-only list of alternate data streams for the specified file.
		/// </summary>
		/// <param name="filePath">
		/// The full path of the file to inspect.
		/// </param>
		/// <returns>
		/// A read-only list of <see cref="AlternateDataStreamInfo"/> objects
		/// representing the alternate data streams for the specified file, if any.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="filePath"/> is <see langword="null"/> or an empty string.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="filePath"/> is not a valid file path.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="filePath"/> does not exist.
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission.
		/// </exception>
		public static IList<AlternateDataStreamInfo> ListAlternateDataStreams(string filePath)
		{
			if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath");
			return CreateInfo(filePath).ListAlternateDataStreams();
		}

		#endregion

		#region Stream Exists

		/// <summary>
		/// <span style="font-weight:bold;color:#a00;">(Extension Method)</span><br />
		/// Returns a flag indicating whether the specified alternate data stream exists.
		/// </summary>
		/// <param name="file">
		/// The <see cref="FileInfo"/> to inspect.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to find.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if the specified stream exists;
		/// otherwise, <see langword="false"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="file"/> is <see langword="null"/>.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="streamName"/> contains invalid characters.
		/// </exception>
		public static bool AlternateDataStreamExists(this FileSystemInfo file, string streamName)
		{
			if (null == file) throw new ArgumentNullException("file");
			SafeNativeMethods.ValidateStreamName(streamName);

			string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName);
			return -1 != SafeNativeMethods.SafeGetFileAttributes(path);
		}

		/// <summary>
		/// Returns a flag indicating whether the specified alternate data stream exists.
		/// </summary>
		/// <param name="filePath">
		/// The path of the file to inspect.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to find.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if the specified stream exists;
		/// otherwise, <see langword="false"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="filePath"/> is <see langword="null"/> or an empty string.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <para><paramref name="filePath"/> is not a valid file path.</para>
		/// <para>-or-</para>
		/// <para><paramref name="streamName"/> contains invalid characters.</para>
		/// </exception>
		public static bool AlternateDataStreamExists(string filePath, string streamName)
		{
			if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath");
			return CreateInfo(filePath).AlternateDataStreamExists(streamName);
		}

		#endregion

		#region Open Stream

		/// <summary>
		/// <span style="font-weight:bold;color:#a00;">(Extension Method)</span><br />
		/// Opens an alternate data stream.
		/// </summary>
		/// <param name="file">
		/// The <see cref="FileInfo"/> which contains the stream.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to open.
		/// </param>
		/// <param name="mode">
		/// One of the <see cref="FileMode"/> values, indicating how the stream is to be opened.
		/// </param>
		/// <returns>
		/// An <see cref="AlternateDataStreamInfo"/> representing the stream.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="file"/> is <see langword="null"/>.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="file"/> was not found.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="streamName"/> contains invalid characters.
		/// </exception>
		/// <exception cref="NotSupportedException">
		/// <paramref name="mode"/> is either <see cref="FileMode.Truncate"/> or <see cref="FileMode.Append"/>.
		/// </exception>
		/// <exception cref="IOException">
		/// <para><paramref name="mode"/> is <see cref="FileMode.Open"/>, and the stream doesn't exist.</para>
		/// <para>-or-</para>
		/// <para><paramref name="mode"/> is <see cref="FileMode.CreateNew"/>, and the stream already exists.</para>
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		public static AlternateDataStreamInfo GetAlternateDataStream(this FileSystemInfo file, string streamName, FileMode mode)
		{
			if (null == file) throw new ArgumentNullException("file");
			if (!file.Exists) throw new FileNotFoundException(null, file.FullName);
			SafeNativeMethods.ValidateStreamName(streamName);

			if (FileMode.Truncate == mode || FileMode.Append == mode)
			{
				throw new NotSupportedException(string.Format(Resources.Culture,
					Resources.Error_InvalidMode, mode));
			}

			FileIOPermissionAccess permAccess = (FileMode.Open == mode) ? FileIOPermissionAccess.Read : FileIOPermissionAccess.Read | FileIOPermissionAccess.Write;
			new FileIOPermission(permAccess, file.FullName).Demand();

			string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName);
			bool exists = -1 != SafeNativeMethods.SafeGetFileAttributes(path);

			if (!exists && FileMode.Open == mode)
			{
				throw new IOException(string.Format(Resources.Culture,
					Resources.Error_StreamNotFound, streamName, file.Name));
			}
			if (exists && FileMode.CreateNew == mode)
			{
				throw new IOException(string.Format(Resources.Culture,
					Resources.Error_StreamExists, streamName, file.Name));
			}

			return new AlternateDataStreamInfo(file.FullName, streamName, path, exists);
		}

		/// <summary>
		/// <span style="font-weight:bold;color:#a00;">(Extension Method)</span><br />
		/// Opens an alternate data stream.
		/// </summary>
		/// <param name="file">
		/// The <see cref="FileInfo"/> which contains the stream.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to open.
		/// </param>
		/// <returns>
		/// An <see cref="AlternateDataStreamInfo"/> representing the stream.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="file"/> is <see langword="null"/>.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="file"/> was not found.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="streamName"/> contains invalid characters.
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		public static AlternateDataStreamInfo GetAlternateDataStream(this FileSystemInfo file, string streamName)
		{
			return file.GetAlternateDataStream(streamName, FileMode.OpenOrCreate);
		}

		/// <summary>
		/// Opens an alternate data stream.
		/// </summary>
		/// <param name="filePath">
		/// The path of the file which contains the stream.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to open.
		/// </param>
		/// <param name="mode">
		/// One of the <see cref="FileMode"/> values, indicating how the stream is to be opened.
		/// </param>
		/// <returns>
		/// An <see cref="AlternateDataStreamInfo"/> representing the stream.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="filePath"/> is <see langword="null"/> or an empty string.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="filePath"/> was not found.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <para><paramref name="filePath"/> is not a valid file path.</para>
		/// <para>-or-</para>
		/// <para><paramref name="streamName"/> contains invalid characters.</para>
		/// </exception>
		/// <exception cref="NotSupportedException">
		/// <paramref name="mode"/> is either <see cref="FileMode.Truncate"/> or <see cref="FileMode.Append"/>.
		/// </exception>
		/// <exception cref="IOException">
		/// <para><paramref name="mode"/> is <see cref="FileMode.Open"/>, and the stream doesn't exist.</para>
		/// <para>-or-</para>
		/// <para><paramref name="mode"/> is <see cref="FileMode.CreateNew"/>, and the stream already exists.</para>
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		public static AlternateDataStreamInfo GetAlternateDataStream(string filePath, string streamName, FileMode mode)
		{
			if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath");
			return CreateInfo(filePath).GetAlternateDataStream(streamName, mode);
		}

		/// <summary>
		/// Opens an alternate data stream.
		/// </summary>
		/// <param name="filePath">
		/// The path of the file which contains the stream.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to open.
		/// </param>
		/// <returns>
		/// An <see cref="AlternateDataStreamInfo"/> representing the stream.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="filePath"/> is <see langword="null"/> or an empty string.
		/// </exception>
		/// <exception cref="FileNotFoundException">
		/// The specified <paramref name="filePath"/> was not found.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <para><paramref name="filePath"/> is not a valid file path.</para>
		/// <para>-or-</para>
		/// <para><paramref name="streamName"/> contains invalid characters.</para>
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		public static AlternateDataStreamInfo GetAlternateDataStream(string filePath, string streamName)
		{
			return GetAlternateDataStream(filePath, streamName, FileMode.OpenOrCreate);
		}

		#endregion

		#region Delete Stream

		/// <summary>
		/// <span style="font-weight:bold;color:#a00;">(Extension Method)</span><br />
		/// Deletes the specified alternate data stream if it exists.
		/// </summary>
		/// <param name="file">
		/// The <see cref="FileInfo"/> to inspect.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to delete.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if the specified stream is deleted;
		/// otherwise, <see langword="false"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="file"/> is <see langword="null"/>.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="streamName"/> contains invalid characters.
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		/// <exception cref="IOException">
		/// The specified file is in use. 
		/// </exception>
		public static bool DeleteAlternateDataStream(this FileSystemInfo file, string streamName)
		{
			if (null == file) throw new ArgumentNullException("file");
			SafeNativeMethods.ValidateStreamName(streamName);

			const FileIOPermissionAccess permAccess = FileIOPermissionAccess.Write;
			new FileIOPermission(permAccess, file.FullName).Demand();

			var result = false;
			if (file.Exists)
			{
				string path = SafeNativeMethods.BuildStreamPath(file.FullName, streamName);
				if (-1 != SafeNativeMethods.SafeGetFileAttributes(path))
				{
					result = SafeNativeMethods.SafeDeleteFile(path);
				}
			}

			return result;
		}

		/// <summary>
		/// Deletes the specified alternate data stream if it exists.
		/// </summary>
		/// <param name="filePath">
		/// The path of the file to inspect.
		/// </param>
		/// <param name="streamName">
		/// The name of the stream to find.
		/// </param>
		/// <returns>
		/// <see langword="true"/> if the specified stream is deleted;
		/// otherwise, <see langword="false"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// <paramref name="filePath"/> is <see langword="null"/> or an empty string.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// <para><paramref name="filePath"/> is not a valid file path.</para>
		/// <para>-or-</para>
		/// <para><paramref name="streamName"/> contains invalid characters.</para>
		/// </exception>
		/// <exception cref="SecurityException">
		/// The caller does not have the required permission. 
		/// </exception>
		/// <exception cref="UnauthorizedAccessException">
		/// The caller does not have the required permission, or the file is read-only.
		/// </exception>
		/// <exception cref="IOException">
		/// The specified file is in use. 
		/// </exception>
		public static bool DeleteAlternateDataStream(string filePath, string streamName)
		{
			if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException("filePath");
			return CreateInfo(filePath).DeleteAlternateDataStream(streamName);
		}

		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer CodeProject
United Kingdom United Kingdom
I started writing code when I was 8, with my trusty ZX Spectrum and a subscription to "Input" magazine. Spent many a happy hour in the school's computer labs with the BBC Micros and our two DOS PCs.

After a brief detour into the world of Maths, I found my way back into programming during my degree via free copies of Delphi and Visual C++ given away with computing magazines.

I went straight from my degree into my first programming job, at Trinet Ltd. Eleven years later, the company merged to become ArcomIT. Three years after that, our project manager left to set up Nevalee Business Solutions, and took me with him. Since then, we've taken on four more members of staff, and more work than you can shake a stick at. Smile | :)

Between writing custom code to integrate with Visma Business, developing web portals to streamline operations for a large multi-national customer, and maintaining RedAtlas, our general aviation airport management system, there's certainly never a dull day in the office!

Outside of work, I enjoy real ale and decent books, and when I get the chance I "tinkle the ivories" on my Technics organ.

Comments and Discussions