/* 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
}
}