/* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; namespace Alphaleonis.Win32.Filesystem { /// Static class providing utility methods for working with Microsoft Windows devices and volumes. public static class Volume { #region DosDevice #region DefineDosDevice /// Defines, redefines, or deletes MS-DOS device names. /// An MS-DOS device name string specifying the device the function is defining, redefining, or deleting. /// An MS-DOS path that will implement this device. [SecurityCritical] public static void DefineDosDevice(string deviceName, string targetPath) { DefineDosDeviceCore(true, deviceName, targetPath, DosDeviceAttributes.None, false); } /// Defines, redefines, or deletes MS-DOS device names. /// /// An MS-DOS device name string specifying the device the function is defining, redefining, or deleting. /// /// /// >An MS-DOS path that will implement this device. If parameter has the /// flag specified, is used as is. /// /// /// The controllable aspects of the DefineDosDevice function, flags which will be combined with the /// default. /// [SecurityCritical] public static void DefineDosDevice(string deviceName, string targetPath, DosDeviceAttributes deviceAttributes) { DefineDosDeviceCore(true, deviceName, targetPath, deviceAttributes, false); } #endregion // DefineDosDevice #region DeleteDosDevice /// Deletes an MS-DOS device name. /// An MS-DOS device name specifying the device to delete. [SecurityCritical] public static void DeleteDosDevice(string deviceName) { DefineDosDeviceCore(false, deviceName, null, DosDeviceAttributes.RemoveDefinition, false); } /// Deletes an MS-DOS device name. /// An MS-DOS device name string specifying the device to delete. /// /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the /// flag is specified, in which case this string is a path string. /// [SecurityCritical] public static void DeleteDosDevice(string deviceName, string targetPath) { DefineDosDeviceCore(false, deviceName, targetPath, DosDeviceAttributes.RemoveDefinition, false); } /// Deletes an MS-DOS device name. /// An MS-DOS device name string specifying the device to delete. /// /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the /// flag is specified, in which case this string is a path string. /// /// /// Only delete MS-DOS device on an exact name match. If is , /// must be the same path used to create the mapping. /// [SecurityCritical] public static void DeleteDosDevice(string deviceName, string targetPath, bool exactMatch) { DefineDosDeviceCore(false, deviceName, targetPath, DosDeviceAttributes.RemoveDefinition, exactMatch); } /// Deletes an MS-DOS device name. /// An MS-DOS device name string specifying the device to delete. /// /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the /// flag is specified, in which case this string is a path string. /// /// /// The controllable aspects of the DefineDosDevice function flags which will be combined with the /// default. /// /// /// Only delete MS-DOS device on an exact name match. If is , /// must be the same path used to create the mapping. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static void DeleteDosDevice(string deviceName, string targetPath, DosDeviceAttributes deviceAttributes, bool exactMatch) { DefineDosDeviceCore(false, deviceName, targetPath, deviceAttributes, exactMatch); } #endregion // DeleteDosDevice #region QueryAllDosDevices /// Retrieves a list of all existing MS-DOS device names. /// An with one or more existing MS-DOS device names. [SecurityCritical] public static IEnumerable QueryAllDosDevices() { return QueryDosDevice(null, null); } /// Retrieves a list of all existing MS-DOS device names. /// /// (Optional, default: ) An MS-DOS device name string specifying the target of the query. This parameter can be /// "sort". In that case a sorted list of all existing MS-DOS device names is returned. This parameter can be . /// In that case, the function will store a list of all existing MS-DOS device names into the buffer. /// /// An with or more existing MS-DOS device names. [SecurityCritical] public static IEnumerable QueryAllDosDevices(string deviceName) { return QueryDosDevice(null, deviceName); } #endregion // QueryAllDosDevices #region QueryDosDevice /// /// Retrieves information about MS-DOS device names. The function can obtain the current mapping for a particular MS-DOS device name. /// The function can also obtain a list of all existing MS-DOS device names. /// /// /// An MS-DOS device name string, or part of, specifying the target of the query. This parameter can be . In that /// case, the QueryDosDevice function will store a list of all existing MS-DOS device names into the buffer. /// /// /// (Optional, default: ) If options[0] = a sorted list will be returned. /// /// An with one or more existing MS-DOS device names. [SecurityCritical] public static IEnumerable QueryDosDevice(string deviceName, params string[] options) { // deviceName is allowed to be null. // The deviceName cannot have a trailing backslash. deviceName = Path.RemoveTrailingDirectorySeparator(deviceName, false); var searchFilter = deviceName != null; // Only process options if a device is supplied. if (searchFilter) { // Check that at least one "options[]" has something to say. If so, rebuild them. options = options != null && options.Any() ? new[] { deviceName, options[0] } : new[] { deviceName, string.Empty }; deviceName = null; } // Choose sorted output. var doSort = options != null && options.Any(s => s != null && s.Equals("sort", StringComparison.OrdinalIgnoreCase)); // Start with a larger buffer when using a searchFilter. var bufferSize = (uint) (searchFilter || doSort || (options == null) ? 8*NativeMethods.DefaultFileBufferSize : 256); uint bufferResult = 0; using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) while (bufferResult == 0) { var cBuffer = new char[bufferSize]; // QueryDosDevice() // In the ANSI version of this function, the name is limited to MAX_PATH characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2014-01-29: MSDN does not confirm LongPath usage but a Unicode version of this function exists. bufferResult = NativeMethods.QueryDosDevice(deviceName, cBuffer, bufferSize); var lastError = Marshal.GetLastWin32Error(); if (bufferResult == 0) switch ((uint) lastError) { case Win32Errors.ERROR_MORE_DATA: case Win32Errors.ERROR_INSUFFICIENT_BUFFER: bufferSize *= 2; continue; default: NativeError.ThrowException(lastError, deviceName); break; } var dosDev = new List(); var buffer = new StringBuilder(); for (var i = 0; i < bufferResult; i++) { if (cBuffer[i] != Path.StringTerminatorChar) buffer.Append(cBuffer[i]); else if (buffer.Length > 0) { dosDev.Add(buffer.ToString()); buffer.Length = 0; } } // Choose the yield back query; filtered or list. var selectQuery = searchFilter ? dosDev.Where(dev => options != null && dev.StartsWith(options[0], StringComparison.OrdinalIgnoreCase)) : dosDev; foreach (var dev in (doSort) ? selectQuery.OrderBy(n => n) : selectQuery) yield return dev; } } #endregion // QueryDosDevice #endregion // DosDevice #region Drive #region GetDriveFormat /// Gets the name of the file system, such as NTFS or FAT32. /// Use DriveFormat to determine what formatting a drive uses. /// /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// The name of the file system on the specified drive or on failure or if not available. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static string GetDriveFormat(string drivePath) { var fsName = new VolumeInfo(drivePath, true, true).FileSystemName; return Utils.IsNullOrWhiteSpace(fsName) ? null : fsName; } #endregion // GetDriveFormat #region GetDriveNameForNtDeviceName /// Gets the drive letter from an MS-DOS device name. For example: "\Device\HarddiskVolume2" returns "C:\". /// An MS-DOS device name. /// The drive letter from an MS-DOS device name. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Nt")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nt")] public static string GetDriveNameForNtDeviceName(string deviceName) { return (from drive in Directory.EnumerateLogicalDrivesCore(false, false) where drive.DosDeviceName.Equals(deviceName, StringComparison.OrdinalIgnoreCase) select drive.Name).FirstOrDefault(); } #endregion // GetDriveNameForNtDeviceName #region GetCurrentDriveType /// /// Determines, based on the root of the current directory, whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network /// drive. /// /// A object. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] [SecurityCritical] public static DriveType GetCurrentDriveType() { return GetDriveType(null); } #endregion // GetCurrentDriveType #region GetDriveType /// Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive. /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\" /// A object. [SecurityCritical] public static DriveType GetDriveType(string drivePath) { // drivePath is allowed to be == null. drivePath = Path.AddTrailingDirectorySeparator(drivePath, false); // ChangeErrorMode is for the Win32 SetThreadErrorMode() method, used to suppress possible pop-ups. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) return NativeMethods.GetDriveType(drivePath); } #endregion // GetDriveType #region GetDiskFreeSpace /// /// Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total /// amount of free space, and the total amount of free space available to the user that is associated with the calling thread. /// /// The calling application must have FILE_LIST_DIRECTORY access rights for this directory. /// /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// A class instance. [SecurityCritical] public static DiskSpaceInfo GetDiskFreeSpace(string drivePath) { return new DiskSpaceInfo(drivePath, null, true, true); } /// /// Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total /// amount of free space, and the total amount of free space available to the user that is associated with the calling thread. /// /// The calling application must have FILE_LIST_DIRECTORY access rights for this directory. /// /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// /// gets both size- and disk cluster information. Get only disk cluster information, /// Get only size information. /// /// A class instance. [SecurityCritical] public static DiskSpaceInfo GetDiskFreeSpace(string drivePath, bool? spaceInfoType) { return new DiskSpaceInfo(drivePath, spaceInfoType, true, true); } #endregion // GetDiskFreeSpace #region IsReady /// Gets a value indicating whether a drive is ready. /// /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// if is ready; otherwise, . [SecurityCritical] public static bool IsReady(string drivePath) { return File.ExistsCore(true, null, drivePath, PathFormat.FullPath); } #endregion // IsReady #endregion // Drive #region Volume #region DeleteCurrentVolumeLabel /// Deletes the label of the file system volume that is the root of the current directory. /// [SecurityCritical] public static void DeleteCurrentVolumeLabel() { SetVolumeLabel(null, null); } #endregion // DeleteCurrentVolumeLabel #region DeleteVolumeLabel /// Deletes the label of a file system volume. /// /// The root directory of a file system volume. This is the volume the function will remove the label. [SecurityCritical] public static void DeleteVolumeLabel(string rootPathName) { if (Utils.IsNullOrWhiteSpace(rootPathName)) throw new ArgumentNullException("rootPathName"); SetVolumeLabel(rootPathName, null); } #endregion // DeleteVolumeLabel #region DeleteVolumeMountPoint /// Deletes a Drive letter or mounted folder. /// Deleting a mounted folder does not cause the underlying directory to be deleted. /// /// If the parameter is a directory that is not a mounted folder, the function does nothing. The /// directory is not deleted. /// /// /// It's not an error to attempt to unmount a volume from a volume mount point when there is no volume actually mounted at that volume /// mount point. /// /// The Drive letter or mounted folder to be deleted. For example, X:\ or Y:\MountX\. [SecurityCritical] public static void DeleteVolumeMountPoint(string volumeMountPoint) { DeleteVolumeMountPointCore(volumeMountPoint, false); } #endregion // DeleteVolumeMountPoint #region EnumerateVolumeMountPoints /// /// Returns an enumerable collection of of all mounted folders (volume mount points) on the specified volume. /// /// /// /// A containing the volume . /// An enumerable collection of of all volume mount points on the specified volume. [SecurityCritical] public static IEnumerable EnumerateVolumeMountPoints(string volumeGuid) { if (Utils.IsNullOrWhiteSpace(volumeGuid)) throw new ArgumentNullException("volumeGuid"); if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid); // A trailing backslash is required. volumeGuid = Path.AddTrailingDirectorySeparator(volumeGuid, false); var buffer = new StringBuilder(NativeMethods.MaxPathUnicode); using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) using (var handle = NativeMethods.FindFirstVolumeMountPoint(volumeGuid, buffer, (uint)buffer.Capacity)) { var lastError = Marshal.GetLastWin32Error(); if (handle.IsInvalid) { handle.Close(); switch ((uint) lastError) { case Win32Errors.ERROR_NO_MORE_FILES: case Win32Errors.ERROR_PATH_NOT_FOUND: // Observed with USB stick, FAT32 formatted. yield break; default: NativeError.ThrowException(lastError, volumeGuid); break; } } yield return buffer.ToString(); while (NativeMethods.FindNextVolumeMountPoint(handle, buffer, (uint)buffer.Capacity)) { lastError = Marshal.GetLastWin32Error(); if (handle.IsInvalid) { handle.Close(); switch ((uint) lastError) { case Win32Errors.ERROR_NO_MORE_FILES: case Win32Errors.ERROR_PATH_NOT_FOUND: // Observed with USB stick, FAT32 formatted. case Win32Errors.ERROR_MORE_DATA: yield break; default: NativeError.ThrowException(lastError, volumeGuid); break; } } yield return buffer.ToString(); } } } #endregion // EnumerateVolumeMountPoints #region EnumerateVolumePathNames /// /// Returns an enumerable collection of drive letters and mounted folder paths for the specified volume. /// /// /// /// A volume path: \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\. /// An enumerable collection of containing the path names for the specified volume. [SecurityCritical] public static IEnumerable EnumerateVolumePathNames(string volumeGuid) { if (Utils.IsNullOrWhiteSpace(volumeGuid)) throw new ArgumentNullException("volumeGuid"); if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid); var volName = Path.AddTrailingDirectorySeparator(volumeGuid, false); uint requiredLength = 10; var cBuffer = new char[requiredLength]; using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) while (!NativeMethods.GetVolumePathNamesForVolumeName(volName, cBuffer, (uint)cBuffer.Length, out requiredLength)) { var lastError = Marshal.GetLastWin32Error(); switch ((uint)lastError) { case Win32Errors.ERROR_MORE_DATA: case Win32Errors.ERROR_INSUFFICIENT_BUFFER: cBuffer = new char[requiredLength]; break; default: NativeError.ThrowException(lastError, volumeGuid); break; } } var buffer = new StringBuilder(cBuffer.Length); foreach (var c in cBuffer) { if (c != Path.StringTerminatorChar) buffer.Append(c); else { if (buffer.Length > 0) { yield return buffer.ToString(); buffer.Length = 0; } } } } #endregion // EnumerateVolumePathNames #region EnumerateVolumes /// Returns an enumerable collection of volumes on the computer. /// An enumerable collection of volume names on the computer. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] [SecurityCritical] public static IEnumerable EnumerateVolumes() { var buffer = new StringBuilder(NativeMethods.MaxPathUnicode); using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) using (var handle = NativeMethods.FindFirstVolume(buffer, (uint)buffer.Capacity)) { while (handle != null && !handle.IsInvalid) { if (NativeMethods.FindNextVolume(handle, buffer, (uint)buffer.Capacity)) yield return buffer.ToString(); else { var lastError = Marshal.GetLastWin32Error(); handle.Close(); if (lastError == Win32Errors.ERROR_NO_MORE_FILES) yield break; NativeError.ThrowException(lastError); } } } } #endregion // EnumerateVolumes #region GetUniqueVolumeNameForPath /// /// Get the unique volume name for the given path. /// /// /// /// A path string. Both absolute and relative file and directory names, for example "..", is acceptable in this path. If you specify a /// relative file or directory name without a volume qualifier, GetUniqueVolumeNameForPath returns the Drive letter of the current /// volume. /// /// /// Returns the unique volume name in the form: "\\?\Volume{GUID}\", /// or on error or if unavailable. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static string GetUniqueVolumeNameForPath(string volumePathName) { if (Utils.IsNullOrWhiteSpace(volumePathName)) throw new ArgumentNullException("volumePathName"); try { return GetVolumeGuid(GetVolumePathName(volumePathName)); } catch { return null; } } #endregion // GetUniqueVolumeNameForPath #region GetVolumeDeviceName /// Retrieves the Win32 Device name from the Volume name. /// /// Name of the Volume. /// /// The Win32 Device name from the Volume name (for example: "\Device\HarddiskVolume2"), or on error or if /// unavailable. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static string GetVolumeDeviceName(string volumeName) { if (Utils.IsNullOrWhiteSpace(volumeName)) throw new ArgumentNullException("volumeName"); volumeName = Path.RemoveTrailingDirectorySeparator(volumeName, false); #region GlobalRoot if (volumeName.StartsWith(Path.GlobalRootPrefix, StringComparison.OrdinalIgnoreCase)) return volumeName.Substring(Path.GlobalRootPrefix.Length); #endregion // GlobalRoot bool doQueryDos; #region Volume if (volumeName.StartsWith(Path.VolumePrefix, StringComparison.OrdinalIgnoreCase)) { // Isolate the DOS Device from the Volume name, in the format: Volume{GUID} volumeName = volumeName.Substring(Path.LongPathPrefix.Length); doQueryDos = true; } #endregion // Volume #region Logical Drive // Check for Logical Drives: C:, D:, ... else { // Don't use char.IsLetter() here as that can be misleading. // The only valid drive letters are: a-z and A-Z. var c = volumeName[0]; doQueryDos = (volumeName[1] == Path.VolumeSeparatorChar && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))); } #endregion // Logical Drive if (doQueryDos) { try { // Get the real Device underneath. var dev = QueryDosDevice(volumeName).FirstOrDefault(); return !Utils.IsNullOrWhiteSpace(dev) ? dev : null; } catch { } } return null; } #endregion // GetVolumeDeviceName #region GetVolumeDisplayName /// Gets the shortest display name for the specified . /// This method basically returns the shortest string returned by /// A volume path: \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\. /// /// The shortest display name for the specified volume found, or if no display names were found. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static string GetVolumeDisplayName(string volumeName) { string[] smallestMountPoint = { new string(Path.WildcardStarMatchAllChar, NativeMethods.MaxPathUnicode) }; try { foreach (var m in EnumerateVolumePathNames(volumeName).Where(m => !Utils.IsNullOrWhiteSpace(m) && m.Length < smallestMountPoint[0].Length)) smallestMountPoint[0] = m; } catch { } var result = smallestMountPoint[0][0] == Path.WildcardStarMatchAllChar ? null : smallestMountPoint[0]; return Utils.IsNullOrWhiteSpace(result) ? null : result; } #endregion // GetVolumeDisplayName #region GetVolumeGuid /// /// Retrieves a volume path for the volume that is associated with the specified volume mount point (drive letter, /// volume GUID path, or mounted folder). /// /// /// /// The path of a mounted folder (for example, "Y:\MountX\") or a drive letter (for example, "X:\"). /// /// The unique volume name of the form: "\\?\Volume{GUID}\". [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke", Justification = "Marshal.GetLastWin32Error() is manipulated.")] [SecurityCritical] public static string GetVolumeGuid(string volumeMountPoint) { if (Utils.IsNullOrWhiteSpace(volumeMountPoint)) throw new ArgumentNullException("volumeMountPoint"); // The string must end with a trailing backslash ('\'). volumeMountPoint = Path.GetFullPathCore(null, volumeMountPoint, GetFullPathOptions.AsLongPath | GetFullPathOptions.AddTrailingDirectorySeparator | GetFullPathOptions.FullCheck); var volumeGuid = new StringBuilder(100); var uniqueName = new StringBuilder(100); try { using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) { // GetVolumeNameForVolumeMountPoint() // In the ANSI version of this function, the name is limited to 248 characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2013-07-18: MSDN does not confirm LongPath usage but a Unicode version of this function exists. return NativeMethods.GetVolumeNameForVolumeMountPoint(volumeMountPoint, volumeGuid, (uint) volumeGuid.Capacity) ? NativeMethods.GetVolumeNameForVolumeMountPoint(Path.AddTrailingDirectorySeparator(volumeGuid.ToString(), false), uniqueName, (uint) uniqueName.Capacity) ? uniqueName.ToString() : null : null; } } finally { var lastError = (uint) Marshal.GetLastWin32Error(); switch (lastError) { case Win32Errors.ERROR_INVALID_NAME: NativeError.ThrowException(lastError, volumeMountPoint); break; case Win32Errors.ERROR_MORE_DATA: // (1) When GetVolumeNameForVolumeMountPoint() succeeds, lastError is set to Win32Errors.ERROR_MORE_DATA. break; default: // (2) When volumeMountPoint is a network drive mapping or UNC path, lastError is set to Win32Errors.ERROR_INVALID_PARAMETER. // Throw IOException. NativeError.ThrowException(lastError, volumeMountPoint); break; } } } #endregion // GetVolumeGuid #region GetVolumeGuidForNtDeviceName /// /// Tranlates DosDevicePath to a Volume GUID. For example: "\Device\HarddiskVolumeX\path\filename.ext" can translate to: "\path\ /// filename.ext" or: "\\?\Volume{GUID}\path\filename.ext". /// /// A DosDevicePath, for example: \Device\HarddiskVolumeX\path\filename.ext. /// A translated dos path. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Nt")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nt")] public static string GetVolumeGuidForNtDeviceName(string dosDevice) { return (from drive in Directory.EnumerateLogicalDrivesCore(false, false) where drive.DosDeviceName.Equals(dosDevice, StringComparison.OrdinalIgnoreCase) select drive.VolumeInfo.Guid).FirstOrDefault(); } #endregion // GetVolumeGuidForNtDeviceName #region GetVolumeInfo /// Retrieves information about the file system and volume associated with the specified root file or directorystream. /// A path that contains the root directory. /// A instance describing the volume associatied with the specified root directory. [SecurityCritical] public static VolumeInfo GetVolumeInfo(string volumePath) { return new VolumeInfo(volumePath, true, false); } /// Retrieves information about the file system and volume associated with the specified root file or directorystream. /// An instance to a handle. /// A instance describing the volume associatied with the specified root directory. [SecurityCritical] public static VolumeInfo GetVolumeInfo(SafeFileHandle volumeHandle) { return new VolumeInfo(volumeHandle, true, true); } #endregion // GetVolumeInfo #region GetVolumeLabel /// Retrieve the label of a file system volume. /// /// A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// /// The the label of the file system volume. This function can return string.Empty since a volume label is generally not /// mandatory. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static string GetVolumeLabel(string volumePath) { return new VolumeInfo(volumePath, true, true).Name; } #endregion // GetVolumeLabel #region GetVolumePathName /// Retrieves the volume mount point where the specified path is mounted. /// /// The path to the volume, for example: "C:\Windows". /// /// Returns the nearest volume root path for a given directory. /// The volume path name, for example: "C:\Windows" returns: "C:\". /// [SecurityCritical] public static string GetVolumePathName(string path) { if (Utils.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) { var volumeRootPath = new StringBuilder(NativeMethods.MaxPathUnicode / 32); var pathLp = Path.GetFullPathCore(null, path, GetFullPathOptions.AsLongPath | GetFullPathOptions.FullCheck); // GetVolumePathName() // In the ANSI version of this function, the name is limited to 248 characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2013-07-18: MSDN does not confirm LongPath usage but a Unicode version of this function exists. var getOk = NativeMethods.GetVolumePathName(pathLp, volumeRootPath, (uint) volumeRootPath.Capacity); var lastError = Marshal.GetLastWin32Error(); if (getOk) return Path.GetRegularPathCore(volumeRootPath.ToString(), GetFullPathOptions.None, false); switch ((uint) lastError) { // Don't throw exception on these errors. case Win32Errors.ERROR_NO_MORE_FILES: case Win32Errors.ERROR_INVALID_PARAMETER: case Win32Errors.ERROR_INVALID_NAME: break; default: NativeError.ThrowException(lastError, path); break; } // Return original path. return path; } } #endregion // GetVolumePathName #region IsSameVolume /// Determines whether the volume of two file system objects is the same. /// The first filesystem ojbect with full path information. /// The second file system object with full path information. /// if both filesytem objects reside on the same volume, otherwise. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SecurityCritical] public static bool IsSameVolume(string path1, string path2) { try { var volInfo1 = new VolumeInfo(GetVolumePathName(path1), true, true); var volInfo2 = new VolumeInfo(GetVolumePathName(path2), true, true); return volInfo1.SerialNumber == volInfo2.SerialNumber; } catch { } return false; } #endregion // IsSameVolume #region IsVolume /// Determines whether the specified volume name is a defined volume on the current computer. /// /// A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\". /// /// on success, otherwise. [SecurityCritical] public static bool IsVolume(string volumeMountPoint) { return !Utils.IsNullOrWhiteSpace(GetVolumeGuid(volumeMountPoint)); } #endregion // IsVolume #region SetCurrentVolumeLabel /// Sets the label of the file system volume that is the root of the current directory. /// /// A name for the volume. [SecurityCritical] public static void SetCurrentVolumeLabel(string volumeName) { if (Utils.IsNullOrWhiteSpace(volumeName)) throw new ArgumentNullException("volumeName"); if (!NativeMethods.SetVolumeLabel(null, volumeName)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } #endregion // SetCurrentVolumeLabel #region SetVolumeLabel /// Sets the label of a file system volume. /// /// A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\" /// If this parameter is , the function uses the current drive. /// /// /// A name for the volume. /// If this parameter is , the function deletes any existing label /// from the specified volume and does not assign a new label. /// [SecurityCritical] public static void SetVolumeLabel(string volumePath, string volumeName) { // rootPathName == null is allowed, means current drive. // Setting volume label only applies to Logical Drives pointing to local resources. //if (!Path.IsLocalPath(rootPathName)) //return false; volumePath = Path.AddTrailingDirectorySeparator(volumePath, false); // NTFS uses a limit of 32 characters for the volume label as of Windows Server 2003. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) if (!NativeMethods.SetVolumeLabel(volumePath, volumeName)) NativeError.ThrowException(volumePath, volumeName); } #endregion // SetVolumeLabel #region SetVolumeMountPoint /// Associates a volume with a Drive letter or a directory on another volume. /// /// /// /// The user-mode path to be associated with the volume. This may be a Drive letter (for example, "X:\") /// or a directory on another volume (for example, "Y:\MountX\"). /// /// A containing the volume . [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Utils.IsNullOrWhiteSpace validates arguments.")] [SecurityCritical] public static void SetVolumeMountPoint(string volumeMountPoint, string volumeGuid) { if (Utils.IsNullOrWhiteSpace(volumeMountPoint)) throw new ArgumentNullException("volumeMountPoint"); if (Utils.IsNullOrWhiteSpace(volumeGuid)) throw new ArgumentNullException("volumeGuid"); if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid); volumeMountPoint = Path.GetFullPathCore(null, volumeMountPoint, GetFullPathOptions.AsLongPath | GetFullPathOptions.AddTrailingDirectorySeparator | GetFullPathOptions.FullCheck); // This string must be of the form "\\?\Volume{GUID}\" volumeGuid = Path.AddTrailingDirectorySeparator(volumeGuid, false); // ChangeErrorMode is for the Win32 SetThreadErrorMode() method, used to suppress possible pop-ups. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) // SetVolumeMountPoint() // In the ANSI version of this function, the name is limited to MAX_PATH characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2014-01-29: MSDN does not confirm LongPath usage but a Unicode version of this function exists. if (!NativeMethods.SetVolumeMountPoint(volumeMountPoint, volumeGuid)) { var lastError = Marshal.GetLastWin32Error(); // If the lpszVolumeMountPoint parameter contains a path to a mounted folder, // GetLastError returns ERROR_DIR_NOT_EMPTY, even if the directory is empty. if (lastError != Win32Errors.ERROR_DIR_NOT_EMPTY) NativeError.ThrowException(lastError, volumeGuid); } } #endregion // SetVolumeMountPoint #endregion // Volume #region Internal Methods /// Defines, redefines, or deletes MS-DOS device names. /// /// /// defines a new MS-DOS device. deletes a previously defined MS-DOS device. /// /// /// An MS-DOS device name string specifying the device the function is defining, redefining, or deleting. /// /// /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the /// flag is specified, in which case this string is a path string. /// /// /// The controllable aspects of the DefineDosDevice function, flags which will be combined with the /// default. /// /// /// Only delete MS-DOS device on an exact name match. If is , /// must be the same path used to create the mapping. /// /// /// on success, otherwise. [SecurityCritical] internal static void DefineDosDeviceCore(bool isDefine, string deviceName, string targetPath, DosDeviceAttributes deviceAttributes, bool exactMatch) { if (Utils.IsNullOrWhiteSpace(deviceName)) throw new ArgumentNullException("deviceName"); if (isDefine) { // targetPath is allowed to be null. // In no case is a trailing backslash ("\") allowed. deviceName = Path.GetRegularPathCore(deviceName, GetFullPathOptions.RemoveTrailingDirectorySeparator | GetFullPathOptions.CheckInvalidPathChars, false); using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) if (!NativeMethods.DefineDosDevice(deviceAttributes, deviceName, targetPath)) NativeError.ThrowException(deviceName, targetPath); } else { // A pointer to a path string that will implement this device. // The string is an MS-DOS path string unless the DDD_RAW_TARGET_PATH flag is specified, in which case this string is a path string. if (exactMatch && !Utils.IsNullOrWhiteSpace(targetPath)) deviceAttributes = deviceAttributes | DosDeviceAttributes.ExactMatchOnRemove | DosDeviceAttributes.RawTargetPath; // Remove the MS-DOS device name. First, get the name of the Windows NT device // from the symbolic link and then delete the symbolic link from the namespace. DefineDosDevice(deviceName, targetPath, deviceAttributes); } } /// Deletes a Drive letter or mounted folder. /// /// It's not an error to attempt to unmount a volume from a volume mount point when there is no volume actually mounted at that volume mount point. /// Deleting a mounted folder does not cause the underlying directory to be deleted. /// /// /// The Drive letter or mounted folder to be deleted. For example, X:\ or Y:\MountX\. /// /// suppress any Exception that might be thrown as a result from a failure, such as unavailable resources. /// /// If completed successfully returns , otherwise the last error number. [SecurityCritical] internal static int DeleteVolumeMountPointCore(string volumeMountPoint, bool continueOnException) { if (Utils.IsNullOrWhiteSpace(volumeMountPoint)) throw new ArgumentNullException("volumeMountPoint"); var lastError = (int) Win32Errors.ERROR_SUCCESS; using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors)) { // DeleteVolumeMountPoint() // In the ANSI version of this function, the name is limited to MAX_PATH characters. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. // 2013-01-13: MSDN does not confirm LongPath usage but a Unicode version of this function exists. if (!NativeMethods.DeleteVolumeMountPoint(Path.AddTrailingDirectorySeparator(volumeMountPoint, false))) lastError = Marshal.GetLastWin32Error(); if (lastError != Win32Errors.ERROR_SUCCESS && !continueOnException) { if (lastError == Win32Errors.ERROR_FILE_NOT_FOUND) lastError = (int) Win32Errors.ERROR_PATH_NOT_FOUND; NativeError.ThrowException(lastError, volumeMountPoint); } } return lastError; } #endregion // Internal Methods } }