You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1147 lines
51 KiB

  1. /* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. using Microsoft.Win32.SafeHandles;
  22. using System;
  23. using System.Collections.Generic;
  24. using System.Diagnostics.CodeAnalysis;
  25. using System.IO;
  26. using System.Linq;
  27. using System.Runtime.InteropServices;
  28. using System.Security;
  29. using System.Text;
  30. namespace Alphaleonis.Win32.Filesystem
  31. {
  32. /// <summary>Static class providing utility methods for working with Microsoft Windows devices and volumes.</summary>
  33. public static class Volume
  34. {
  35. #region DosDevice
  36. #region DefineDosDevice
  37. /// <summary>Defines, redefines, or deletes MS-DOS device names.</summary>
  38. /// <param name="deviceName">An MS-DOS device name string specifying the device the function is defining, redefining, or deleting.</param>
  39. /// <param name="targetPath">An MS-DOS path that will implement this device.</param>
  40. [SecurityCritical]
  41. public static void DefineDosDevice(string deviceName, string targetPath)
  42. {
  43. DefineDosDeviceCore(true, deviceName, targetPath, DosDeviceAttributes.None, false);
  44. }
  45. /// <summary>Defines, redefines, or deletes MS-DOS device names.</summary>
  46. /// <param name="deviceName">
  47. /// An MS-DOS device name string specifying the device the function is defining, redefining, or deleting.
  48. /// </param>
  49. /// <param name="targetPath">
  50. /// &gt;An MS-DOS path that will implement this device. If <paramref name="deviceAttributes"/> parameter has the
  51. /// <see cref="DosDeviceAttributes.RawTargetPath"/> flag specified, <paramref name="targetPath"/> is used as is.
  52. /// </param>
  53. /// <param name="deviceAttributes">
  54. /// The controllable aspects of the DefineDosDevice function, <see cref="DosDeviceAttributes"/> flags which will be combined with the
  55. /// default.
  56. /// </param>
  57. [SecurityCritical]
  58. public static void DefineDosDevice(string deviceName, string targetPath, DosDeviceAttributes deviceAttributes)
  59. {
  60. DefineDosDeviceCore(true, deviceName, targetPath, deviceAttributes, false);
  61. }
  62. #endregion // DefineDosDevice
  63. #region DeleteDosDevice
  64. /// <summary>Deletes an MS-DOS device name.</summary>
  65. /// <param name="deviceName">An MS-DOS device name specifying the device to delete.</param>
  66. [SecurityCritical]
  67. public static void DeleteDosDevice(string deviceName)
  68. {
  69. DefineDosDeviceCore(false, deviceName, null, DosDeviceAttributes.RemoveDefinition, false);
  70. }
  71. /// <summary>Deletes an MS-DOS device name.</summary>
  72. /// <param name="deviceName">An MS-DOS device name string specifying the device to delete.</param>
  73. /// <param name="targetPath">
  74. /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the
  75. /// <see cref="DosDeviceAttributes.RawTargetPath"/> flag is specified, in which case this string is a path string.
  76. /// </param>
  77. [SecurityCritical]
  78. public static void DeleteDosDevice(string deviceName, string targetPath)
  79. {
  80. DefineDosDeviceCore(false, deviceName, targetPath, DosDeviceAttributes.RemoveDefinition, false);
  81. }
  82. /// <summary>Deletes an MS-DOS device name.</summary>
  83. /// <param name="deviceName">An MS-DOS device name string specifying the device to delete.</param>
  84. /// <param name="targetPath">
  85. /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the
  86. /// <see cref="DosDeviceAttributes.RawTargetPath"/> flag is specified, in which case this string is a path string.
  87. /// </param>
  88. /// <param name="exactMatch">
  89. /// Only delete MS-DOS device on an exact name match. If <paramref name="exactMatch"/> is <see langword="true"/>,
  90. /// <paramref name="targetPath"/> must be the same path used to create the mapping.
  91. /// </param>
  92. [SecurityCritical]
  93. public static void DeleteDosDevice(string deviceName, string targetPath, bool exactMatch)
  94. {
  95. DefineDosDeviceCore(false, deviceName, targetPath, DosDeviceAttributes.RemoveDefinition, exactMatch);
  96. }
  97. /// <summary>Deletes an MS-DOS device name.</summary>
  98. /// <param name="deviceName">An MS-DOS device name string specifying the device to delete.</param>
  99. /// <param name="targetPath">
  100. /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the
  101. /// <see cref="DosDeviceAttributes.RawTargetPath"/> flag is specified, in which case this string is a path string.
  102. /// </param>
  103. /// <param name="deviceAttributes">
  104. /// The controllable aspects of the DefineDosDevice function <see cref="DosDeviceAttributes"/> flags which will be combined with the
  105. /// default.
  106. /// </param>
  107. /// <param name="exactMatch">
  108. /// Only delete MS-DOS device on an exact name match. If <paramref name="exactMatch"/> is <see langword="true"/>,
  109. /// <paramref name="targetPath"/> must be the same path used to create the mapping.
  110. /// </param>
  111. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  112. [SecurityCritical]
  113. public static void DeleteDosDevice(string deviceName, string targetPath, DosDeviceAttributes deviceAttributes, bool exactMatch)
  114. {
  115. DefineDosDeviceCore(false, deviceName, targetPath, deviceAttributes, exactMatch);
  116. }
  117. #endregion // DeleteDosDevice
  118. #region QueryAllDosDevices
  119. /// <summary>Retrieves a list of all existing MS-DOS device names.</summary>
  120. /// <returns>An <see cref="IEnumerable{String}"/> with one or more existing MS-DOS device names.</returns>
  121. [SecurityCritical]
  122. public static IEnumerable<string> QueryAllDosDevices()
  123. {
  124. return QueryDosDevice(null, null);
  125. }
  126. /// <summary>Retrieves a list of all existing MS-DOS device names.</summary>
  127. /// <param name="deviceName">
  128. /// (Optional, default: <see langword="null"/>) An MS-DOS device name string specifying the target of the query. This parameter can be
  129. /// "sort". In that case a sorted list of all existing MS-DOS device names is returned. This parameter can be <see langword="null"/>.
  130. /// In that case, the <see cref="QueryDosDevice"/> function will store a list of all existing MS-DOS device names into the buffer.
  131. /// </param>
  132. /// <returns>An <see cref="IEnumerable{String}"/> with or more existing MS-DOS device names.</returns>
  133. [SecurityCritical]
  134. public static IEnumerable<string> QueryAllDosDevices(string deviceName)
  135. {
  136. return QueryDosDevice(null, deviceName);
  137. }
  138. #endregion // QueryAllDosDevices
  139. #region QueryDosDevice
  140. /// <summary>
  141. /// Retrieves information about MS-DOS device names. The function can obtain the current mapping for a particular MS-DOS device name.
  142. /// The function can also obtain a list of all existing MS-DOS device names.
  143. /// </summary>
  144. /// <param name="deviceName">
  145. /// An MS-DOS device name string, or part of, specifying the target of the query. This parameter can be <see langword="null"/>. In that
  146. /// case, the QueryDosDevice function will store a list of all existing MS-DOS device names into the buffer.
  147. /// </param>
  148. /// <param name="options">
  149. /// (Optional, default: <see langword="false"/>) If options[0] = <see langword="true"/> a sorted list will be returned.
  150. /// </param>
  151. /// <returns>An <see cref="IEnumerable{String}"/> with one or more existing MS-DOS device names.</returns>
  152. [SecurityCritical]
  153. public static IEnumerable<string> QueryDosDevice(string deviceName, params string[] options)
  154. {
  155. // deviceName is allowed to be null.
  156. // The deviceName cannot have a trailing backslash.
  157. deviceName = Path.RemoveTrailingDirectorySeparator(deviceName, false);
  158. var searchFilter = deviceName != null;
  159. // Only process options if a device is supplied.
  160. if (searchFilter)
  161. {
  162. // Check that at least one "options[]" has something to say. If so, rebuild them.
  163. options = options != null && options.Any() ? new[] { deviceName, options[0] } : new[] { deviceName, string.Empty };
  164. deviceName = null;
  165. }
  166. // Choose sorted output.
  167. var doSort = options != null &&
  168. options.Any(s => s != null && s.Equals("sort", StringComparison.OrdinalIgnoreCase));
  169. // Start with a larger buffer when using a searchFilter.
  170. var bufferSize = (uint) (searchFilter || doSort || (options == null) ? 8*NativeMethods.DefaultFileBufferSize : 256);
  171. uint bufferResult = 0;
  172. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  173. while (bufferResult == 0)
  174. {
  175. var cBuffer = new char[bufferSize];
  176. // QueryDosDevice()
  177. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  178. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  179. // 2014-01-29: MSDN does not confirm LongPath usage but a Unicode version of this function exists.
  180. bufferResult = NativeMethods.QueryDosDevice(deviceName, cBuffer, bufferSize);
  181. var lastError = Marshal.GetLastWin32Error();
  182. if (bufferResult == 0)
  183. switch ((uint) lastError)
  184. {
  185. case Win32Errors.ERROR_MORE_DATA:
  186. case Win32Errors.ERROR_INSUFFICIENT_BUFFER:
  187. bufferSize *= 2;
  188. continue;
  189. default:
  190. NativeError.ThrowException(lastError, deviceName);
  191. break;
  192. }
  193. var dosDev = new List<string>();
  194. var buffer = new StringBuilder();
  195. for (var i = 0; i < bufferResult; i++)
  196. {
  197. if (cBuffer[i] != Path.StringTerminatorChar)
  198. buffer.Append(cBuffer[i]);
  199. else if (buffer.Length > 0)
  200. {
  201. dosDev.Add(buffer.ToString());
  202. buffer.Length = 0;
  203. }
  204. }
  205. // Choose the yield back query; filtered or list.
  206. var selectQuery = searchFilter
  207. ? dosDev.Where(dev => options != null && dev.StartsWith(options[0], StringComparison.OrdinalIgnoreCase))
  208. : dosDev;
  209. foreach (var dev in (doSort) ? selectQuery.OrderBy(n => n) : selectQuery)
  210. yield return dev;
  211. }
  212. }
  213. #endregion // QueryDosDevice
  214. #endregion // DosDevice
  215. #region Drive
  216. #region GetDriveFormat
  217. /// <summary>Gets the name of the file system, such as NTFS or FAT32.</summary>
  218. /// <remarks>Use DriveFormat to determine what formatting a drive uses.</remarks>
  219. /// <param name="drivePath">
  220. /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  221. /// </param>
  222. /// <returns>The name of the file system on the specified drive or <see langword="null"/> on failure or if not available.</returns>
  223. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  224. [SecurityCritical]
  225. public static string GetDriveFormat(string drivePath)
  226. {
  227. var fsName = new VolumeInfo(drivePath, true, true).FileSystemName;
  228. return Utils.IsNullOrWhiteSpace(fsName) ? null : fsName;
  229. }
  230. #endregion // GetDriveFormat
  231. #region GetDriveNameForNtDeviceName
  232. /// <summary>Gets the drive letter from an MS-DOS device name. For example: "\Device\HarddiskVolume2" returns "C:\".</summary>
  233. /// <param name="deviceName">An MS-DOS device name.</param>
  234. /// <returns>The drive letter from an MS-DOS device name.</returns>
  235. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Nt")]
  236. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nt")]
  237. public static string GetDriveNameForNtDeviceName(string deviceName)
  238. {
  239. return (from drive in Directory.EnumerateLogicalDrivesCore(false, false)
  240. where drive.DosDeviceName.Equals(deviceName, StringComparison.OrdinalIgnoreCase)
  241. select drive.Name).FirstOrDefault();
  242. }
  243. #endregion // GetDriveNameForNtDeviceName
  244. #region GetCurrentDriveType
  245. /// <summary>
  246. /// Determines, based on the root of the current directory, whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network
  247. /// drive.
  248. /// </summary>
  249. /// <returns>A <see cref="DriveType"/> object.</returns>
  250. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
  251. [SecurityCritical]
  252. public static DriveType GetCurrentDriveType()
  253. {
  254. return GetDriveType(null);
  255. }
  256. #endregion // GetCurrentDriveType
  257. #region GetDriveType
  258. /// <summary>Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive.</summary>
  259. /// <param name="drivePath">A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\"</param>
  260. /// <returns>A <see cref="System.IO.DriveType"/> object.</returns>
  261. [SecurityCritical]
  262. public static DriveType GetDriveType(string drivePath)
  263. {
  264. // drivePath is allowed to be == null.
  265. drivePath = Path.AddTrailingDirectorySeparator(drivePath, false);
  266. // ChangeErrorMode is for the Win32 SetThreadErrorMode() method, used to suppress possible pop-ups.
  267. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  268. return NativeMethods.GetDriveType(drivePath);
  269. }
  270. #endregion // GetDriveType
  271. #region GetDiskFreeSpace
  272. /// <summary>
  273. /// Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total
  274. /// amount of free space, and the total amount of free space available to the user that is associated with the calling thread.
  275. /// </summary>
  276. /// <remarks>The calling application must have FILE_LIST_DIRECTORY access rights for this directory.</remarks>
  277. /// <param name="drivePath">
  278. /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  279. /// </param>
  280. /// <returns>A <see ref="Alphaleonis.Win32.Filesystem.DiskSpaceInfo"/> class instance.</returns>
  281. [SecurityCritical]
  282. public static DiskSpaceInfo GetDiskFreeSpace(string drivePath)
  283. {
  284. return new DiskSpaceInfo(drivePath, null, true, true);
  285. }
  286. /// <summary>
  287. /// Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total
  288. /// amount of free space, and the total amount of free space available to the user that is associated with the calling thread.
  289. /// </summary>
  290. /// <remarks>The calling application must have FILE_LIST_DIRECTORY access rights for this directory.</remarks>
  291. /// <param name="drivePath">
  292. /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  293. /// </param>
  294. /// <param name="spaceInfoType">
  295. /// <see langword="null"/> gets both size- and disk cluster information. <see langword="true"/> Get only disk cluster information,
  296. /// <see langword="false"/> Get only size information.
  297. /// </param>
  298. /// <returns>A <see ref="Alphaleonis.Win32.Filesystem.DiskSpaceInfo"/> class instance.</returns>
  299. [SecurityCritical]
  300. public static DiskSpaceInfo GetDiskFreeSpace(string drivePath, bool? spaceInfoType)
  301. {
  302. return new DiskSpaceInfo(drivePath, spaceInfoType, true, true);
  303. }
  304. #endregion // GetDiskFreeSpace
  305. #region IsReady
  306. /// <summary>Gets a value indicating whether a drive is ready.</summary>
  307. /// <param name="drivePath">
  308. /// A path to a drive. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  309. /// </param>
  310. /// <returns><see langword="true"/> if <paramref name="drivePath"/> is ready; otherwise, <see langword="false"/>.</returns>
  311. [SecurityCritical]
  312. public static bool IsReady(string drivePath)
  313. {
  314. return File.ExistsCore(true, null, drivePath, PathFormat.FullPath);
  315. }
  316. #endregion // IsReady
  317. #endregion // Drive
  318. #region Volume
  319. #region DeleteCurrentVolumeLabel
  320. /// <summary>Deletes the label of the file system volume that is the root of the current directory.
  321. /// </summary>
  322. [SecurityCritical]
  323. public static void DeleteCurrentVolumeLabel()
  324. {
  325. SetVolumeLabel(null, null);
  326. }
  327. #endregion // DeleteCurrentVolumeLabel
  328. #region DeleteVolumeLabel
  329. /// <summary>Deletes the label of a file system volume.</summary>
  330. /// <exception cref="ArgumentNullException"/>
  331. /// <param name="rootPathName">The root directory of a file system volume. This is the volume the function will remove the label.</param>
  332. [SecurityCritical]
  333. public static void DeleteVolumeLabel(string rootPathName)
  334. {
  335. if (Utils.IsNullOrWhiteSpace(rootPathName))
  336. throw new ArgumentNullException("rootPathName");
  337. SetVolumeLabel(rootPathName, null);
  338. }
  339. #endregion // DeleteVolumeLabel
  340. #region DeleteVolumeMountPoint
  341. /// <summary>Deletes a Drive letter or mounted folder.</summary>
  342. /// <remarks>Deleting a mounted folder does not cause the underlying directory to be deleted.</remarks>
  343. /// <remarks>
  344. /// If the <paramref name="volumeMountPoint"/> parameter is a directory that is not a mounted folder, the function does nothing. The
  345. /// directory is not deleted.
  346. /// </remarks>
  347. /// <remarks>
  348. /// 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
  349. /// mount point.
  350. /// </remarks>
  351. /// <param name="volumeMountPoint">The Drive letter or mounted folder to be deleted. For example, X:\ or Y:\MountX\.</param>
  352. [SecurityCritical]
  353. public static void DeleteVolumeMountPoint(string volumeMountPoint)
  354. {
  355. DeleteVolumeMountPointCore(volumeMountPoint, false);
  356. }
  357. #endregion // DeleteVolumeMountPoint
  358. #region EnumerateVolumeMountPoints
  359. /// <summary>
  360. /// Returns an enumerable collection of <see cref="String"/> of all mounted folders (volume mount points) on the specified volume.
  361. /// </summary>
  362. /// <exception cref="ArgumentNullException"/>
  363. /// <exception cref="ArgumentException"/>
  364. /// <param name="volumeGuid">A <see cref="string"/> containing the volume <see cref="Guid"/>.</param>
  365. /// <returns>An enumerable collection of <see cref="String"/> of all volume mount points on the specified volume.</returns>
  366. [SecurityCritical]
  367. public static IEnumerable<string> EnumerateVolumeMountPoints(string volumeGuid)
  368. {
  369. if (Utils.IsNullOrWhiteSpace(volumeGuid))
  370. throw new ArgumentNullException("volumeGuid");
  371. if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase))
  372. throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid);
  373. // A trailing backslash is required.
  374. volumeGuid = Path.AddTrailingDirectorySeparator(volumeGuid, false);
  375. var buffer = new StringBuilder(NativeMethods.MaxPathUnicode);
  376. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  377. using (var handle = NativeMethods.FindFirstVolumeMountPoint(volumeGuid, buffer, (uint)buffer.Capacity))
  378. {
  379. var lastError = Marshal.GetLastWin32Error();
  380. if (handle.IsInvalid)
  381. {
  382. handle.Close();
  383. switch ((uint) lastError)
  384. {
  385. case Win32Errors.ERROR_NO_MORE_FILES:
  386. case Win32Errors.ERROR_PATH_NOT_FOUND: // Observed with USB stick, FAT32 formatted.
  387. yield break;
  388. default:
  389. NativeError.ThrowException(lastError, volumeGuid);
  390. break;
  391. }
  392. }
  393. yield return buffer.ToString();
  394. while (NativeMethods.FindNextVolumeMountPoint(handle, buffer, (uint)buffer.Capacity))
  395. {
  396. lastError = Marshal.GetLastWin32Error();
  397. if (handle.IsInvalid)
  398. {
  399. handle.Close();
  400. switch ((uint) lastError)
  401. {
  402. case Win32Errors.ERROR_NO_MORE_FILES:
  403. case Win32Errors.ERROR_PATH_NOT_FOUND: // Observed with USB stick, FAT32 formatted.
  404. case Win32Errors.ERROR_MORE_DATA:
  405. yield break;
  406. default:
  407. NativeError.ThrowException(lastError, volumeGuid);
  408. break;
  409. }
  410. }
  411. yield return buffer.ToString();
  412. }
  413. }
  414. }
  415. #endregion // EnumerateVolumeMountPoints
  416. #region EnumerateVolumePathNames
  417. /// <summary>
  418. /// Returns an enumerable collection of <see cref="String"/> drive letters and mounted folder paths for the specified volume.
  419. /// </summary>
  420. /// <exception cref="ArgumentNullException"/>
  421. /// <exception cref="ArgumentException"/>
  422. /// <param name="volumeGuid">A volume <see cref="Guid"/> path: \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\.</param>
  423. /// <returns>An enumerable collection of <see cref="String"/> containing the path names for the specified volume.</returns>
  424. [SecurityCritical]
  425. public static IEnumerable<string> EnumerateVolumePathNames(string volumeGuid)
  426. {
  427. if (Utils.IsNullOrWhiteSpace(volumeGuid))
  428. throw new ArgumentNullException("volumeGuid");
  429. if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase))
  430. throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid);
  431. var volName = Path.AddTrailingDirectorySeparator(volumeGuid, false);
  432. uint requiredLength = 10;
  433. var cBuffer = new char[requiredLength];
  434. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  435. while (!NativeMethods.GetVolumePathNamesForVolumeName(volName, cBuffer, (uint)cBuffer.Length, out requiredLength))
  436. {
  437. var lastError = Marshal.GetLastWin32Error();
  438. switch ((uint)lastError)
  439. {
  440. case Win32Errors.ERROR_MORE_DATA:
  441. case Win32Errors.ERROR_INSUFFICIENT_BUFFER:
  442. cBuffer = new char[requiredLength];
  443. break;
  444. default:
  445. NativeError.ThrowException(lastError, volumeGuid);
  446. break;
  447. }
  448. }
  449. var buffer = new StringBuilder(cBuffer.Length);
  450. foreach (var c in cBuffer)
  451. {
  452. if (c != Path.StringTerminatorChar)
  453. buffer.Append(c);
  454. else
  455. {
  456. if (buffer.Length > 0)
  457. {
  458. yield return buffer.ToString();
  459. buffer.Length = 0;
  460. }
  461. }
  462. }
  463. }
  464. #endregion // EnumerateVolumePathNames
  465. #region EnumerateVolumes
  466. /// <summary>Returns an enumerable collection of <see cref="String"/> volumes on the computer.</summary>
  467. /// <returns>An enumerable collection of <see cref="String"/> volume names on the computer.</returns>
  468. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
  469. [SecurityCritical]
  470. public static IEnumerable<string> EnumerateVolumes()
  471. {
  472. var buffer = new StringBuilder(NativeMethods.MaxPathUnicode);
  473. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  474. using (var handle = NativeMethods.FindFirstVolume(buffer, (uint)buffer.Capacity))
  475. {
  476. while (handle != null && !handle.IsInvalid)
  477. {
  478. if (NativeMethods.FindNextVolume(handle, buffer, (uint)buffer.Capacity))
  479. yield return buffer.ToString();
  480. else
  481. {
  482. var lastError = Marshal.GetLastWin32Error();
  483. handle.Close();
  484. if (lastError == Win32Errors.ERROR_NO_MORE_FILES)
  485. yield break;
  486. NativeError.ThrowException(lastError);
  487. }
  488. }
  489. }
  490. }
  491. #endregion // EnumerateVolumes
  492. #region GetUniqueVolumeNameForPath
  493. /// <summary>
  494. /// Get the unique volume name for the given path.
  495. /// </summary>
  496. /// <exception cref="ArgumentNullException"/>
  497. /// <param name="volumePathName">
  498. /// A path string. Both absolute and relative file and directory names, for example "..", is acceptable in this path. If you specify a
  499. /// relative file or directory name without a volume qualifier, GetUniqueVolumeNameForPath returns the Drive letter of the current
  500. /// volume.
  501. /// </param>
  502. /// <returns>
  503. /// <para>Returns the unique volume name in the form: "\\?\Volume{GUID}\",</para>
  504. /// <para>or <see langword="null"/> on error or if unavailable.</para>
  505. /// </returns>
  506. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  507. [SecurityCritical]
  508. public static string GetUniqueVolumeNameForPath(string volumePathName)
  509. {
  510. if (Utils.IsNullOrWhiteSpace(volumePathName))
  511. throw new ArgumentNullException("volumePathName");
  512. try
  513. {
  514. return GetVolumeGuid(GetVolumePathName(volumePathName));
  515. }
  516. catch
  517. {
  518. return null;
  519. }
  520. }
  521. #endregion // GetUniqueVolumeNameForPath
  522. #region GetVolumeDeviceName
  523. /// <summary>Retrieves the Win32 Device name from the Volume name.</summary>
  524. /// <exception cref="ArgumentNullException"/>
  525. /// <param name="volumeName">Name of the Volume.</param>
  526. /// <returns>
  527. /// The Win32 Device name from the Volume name (for example: "\Device\HarddiskVolume2"), or <see langword="null"/> on error or if
  528. /// unavailable.
  529. /// </returns>
  530. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  531. [SecurityCritical]
  532. public static string GetVolumeDeviceName(string volumeName)
  533. {
  534. if (Utils.IsNullOrWhiteSpace(volumeName))
  535. throw new ArgumentNullException("volumeName");
  536. volumeName = Path.RemoveTrailingDirectorySeparator(volumeName, false);
  537. #region GlobalRoot
  538. if (volumeName.StartsWith(Path.GlobalRootPrefix, StringComparison.OrdinalIgnoreCase))
  539. return volumeName.Substring(Path.GlobalRootPrefix.Length);
  540. #endregion // GlobalRoot
  541. bool doQueryDos;
  542. #region Volume
  543. if (volumeName.StartsWith(Path.VolumePrefix, StringComparison.OrdinalIgnoreCase))
  544. {
  545. // Isolate the DOS Device from the Volume name, in the format: Volume{GUID}
  546. volumeName = volumeName.Substring(Path.LongPathPrefix.Length);
  547. doQueryDos = true;
  548. }
  549. #endregion // Volume
  550. #region Logical Drive
  551. // Check for Logical Drives: C:, D:, ...
  552. else
  553. {
  554. // Don't use char.IsLetter() here as that can be misleading.
  555. // The only valid drive letters are: a-z and A-Z.
  556. var c = volumeName[0];
  557. doQueryDos = (volumeName[1] == Path.VolumeSeparatorChar && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')));
  558. }
  559. #endregion // Logical Drive
  560. if (doQueryDos)
  561. {
  562. try
  563. {
  564. // Get the real Device underneath.
  565. var dev = QueryDosDevice(volumeName).FirstOrDefault();
  566. return !Utils.IsNullOrWhiteSpace(dev) ? dev : null;
  567. }
  568. catch
  569. {
  570. }
  571. }
  572. return null;
  573. }
  574. #endregion // GetVolumeDeviceName
  575. #region GetVolumeDisplayName
  576. /// <summary>Gets the shortest display name for the specified <paramref name="volumeName"/>.</summary>
  577. /// <remarks>This method basically returns the shortest string returned by <see cref="EnumerateVolumePathNames"/></remarks>
  578. /// <param name="volumeName">A volume <see cref="Guid"/> path: \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\.</param>
  579. /// <returns>
  580. /// The shortest display name for the specified volume found, or <see langword="null"/> if no display names were found.
  581. /// </returns>
  582. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  583. [SecurityCritical]
  584. public static string GetVolumeDisplayName(string volumeName)
  585. {
  586. string[] smallestMountPoint = { new string(Path.WildcardStarMatchAllChar, NativeMethods.MaxPathUnicode) };
  587. try
  588. {
  589. foreach (var m in EnumerateVolumePathNames(volumeName).Where(m => !Utils.IsNullOrWhiteSpace(m) && m.Length < smallestMountPoint[0].Length))
  590. smallestMountPoint[0] = m;
  591. }
  592. catch
  593. {
  594. }
  595. var result = smallestMountPoint[0][0] == Path.WildcardStarMatchAllChar ? null : smallestMountPoint[0];
  596. return Utils.IsNullOrWhiteSpace(result) ? null : result;
  597. }
  598. #endregion // GetVolumeDisplayName
  599. #region GetVolumeGuid
  600. /// <summary>
  601. /// Retrieves a volume <see cref="Guid"/> path for the volume that is associated with the specified volume mount point (drive letter,
  602. /// volume GUID path, or mounted folder).
  603. /// </summary>
  604. /// <exception cref="ArgumentNullException"/>
  605. /// <param name="volumeMountPoint">
  606. /// The path of a mounted folder (for example, "Y:\MountX\") or a drive letter (for example, "X:\").
  607. /// </param>
  608. /// <returns>The unique volume name of the form: "\\?\Volume{GUID}\".</returns>
  609. [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke", Justification = "Marshal.GetLastWin32Error() is manipulated.")]
  610. [SecurityCritical]
  611. public static string GetVolumeGuid(string volumeMountPoint)
  612. {
  613. if (Utils.IsNullOrWhiteSpace(volumeMountPoint))
  614. throw new ArgumentNullException("volumeMountPoint");
  615. // The string must end with a trailing backslash ('\').
  616. volumeMountPoint = Path.GetFullPathCore(null, volumeMountPoint, GetFullPathOptions.AsLongPath | GetFullPathOptions.AddTrailingDirectorySeparator | GetFullPathOptions.FullCheck);
  617. var volumeGuid = new StringBuilder(100);
  618. var uniqueName = new StringBuilder(100);
  619. try
  620. {
  621. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  622. {
  623. // GetVolumeNameForVolumeMountPoint()
  624. // In the ANSI version of this function, the name is limited to 248 characters.
  625. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  626. // 2013-07-18: MSDN does not confirm LongPath usage but a Unicode version of this function exists.
  627. return NativeMethods.GetVolumeNameForVolumeMountPoint(volumeMountPoint, volumeGuid, (uint) volumeGuid.Capacity)
  628. ? NativeMethods.GetVolumeNameForVolumeMountPoint(Path.AddTrailingDirectorySeparator(volumeGuid.ToString(), false), uniqueName, (uint) uniqueName.Capacity)
  629. ? uniqueName.ToString()
  630. : null
  631. : null;
  632. }
  633. }
  634. finally
  635. {
  636. var lastError = (uint) Marshal.GetLastWin32Error();
  637. switch (lastError)
  638. {
  639. case Win32Errors.ERROR_INVALID_NAME:
  640. NativeError.ThrowException(lastError, volumeMountPoint);
  641. break;
  642. case Win32Errors.ERROR_MORE_DATA:
  643. // (1) When GetVolumeNameForVolumeMountPoint() succeeds, lastError is set to Win32Errors.ERROR_MORE_DATA.
  644. break;
  645. default:
  646. // (2) When volumeMountPoint is a network drive mapping or UNC path, lastError is set to Win32Errors.ERROR_INVALID_PARAMETER.
  647. // Throw IOException.
  648. NativeError.ThrowException(lastError, volumeMountPoint);
  649. break;
  650. }
  651. }
  652. }
  653. #endregion // GetVolumeGuid
  654. #region GetVolumeGuidForNtDeviceName
  655. /// <summary>
  656. /// Tranlates DosDevicePath to a Volume GUID. For example: "\Device\HarddiskVolumeX\path\filename.ext" can translate to: "\path\
  657. /// filename.ext" or: "\\?\Volume{GUID}\path\filename.ext".
  658. /// </summary>
  659. /// <param name="dosDevice">A DosDevicePath, for example: \Device\HarddiskVolumeX\path\filename.ext.</param>
  660. /// <returns>A translated dos path.</returns>
  661. [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Nt")]
  662. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nt")]
  663. public static string GetVolumeGuidForNtDeviceName(string dosDevice)
  664. {
  665. return (from drive in Directory.EnumerateLogicalDrivesCore(false, false)
  666. where drive.DosDeviceName.Equals(dosDevice, StringComparison.OrdinalIgnoreCase)
  667. select drive.VolumeInfo.Guid).FirstOrDefault();
  668. }
  669. #endregion // GetVolumeGuidForNtDeviceName
  670. #region GetVolumeInfo
  671. /// <summary>Retrieves information about the file system and volume associated with the specified root file or directorystream.</summary>
  672. /// <param name="volumePath">A path that contains the root directory.</param>
  673. /// <returns>A <see cref="VolumeInfo"/> instance describing the volume associatied with the specified root directory.</returns>
  674. [SecurityCritical]
  675. public static VolumeInfo GetVolumeInfo(string volumePath)
  676. {
  677. return new VolumeInfo(volumePath, true, false);
  678. }
  679. /// <summary>Retrieves information about the file system and volume associated with the specified root file or directorystream.</summary>
  680. /// <param name="volumeHandle">An instance to a <see cref="SafeFileHandle"/> handle.</param>
  681. /// <returns>A <see cref="VolumeInfo"/> instance describing the volume associatied with the specified root directory.</returns>
  682. [SecurityCritical]
  683. public static VolumeInfo GetVolumeInfo(SafeFileHandle volumeHandle)
  684. {
  685. return new VolumeInfo(volumeHandle, true, true);
  686. }
  687. #endregion // GetVolumeInfo
  688. #region GetVolumeLabel
  689. /// <summary>Retrieve the label of a file system volume.</summary>
  690. /// <param name="volumePath">
  691. /// A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  692. /// </param>
  693. /// <returns>
  694. /// The the label of the file system volume. This function can return <c>string.Empty</c> since a volume label is generally not
  695. /// mandatory.
  696. /// </returns>
  697. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  698. [SecurityCritical]
  699. public static string GetVolumeLabel(string volumePath)
  700. {
  701. return new VolumeInfo(volumePath, true, true).Name;
  702. }
  703. #endregion // GetVolumeLabel
  704. #region GetVolumePathName
  705. /// <summary>Retrieves the volume mount point where the specified path is mounted.</summary>
  706. /// <exception cref="ArgumentNullException"/>
  707. /// <param name="path">The path to the volume, for example: "C:\Windows".</param>
  708. /// <returns>
  709. /// <para>Returns the nearest volume root path for a given directory.</para>
  710. /// <para>The volume path name, for example: "C:\Windows" returns: "C:\".</para>
  711. /// </returns>
  712. [SecurityCritical]
  713. public static string GetVolumePathName(string path)
  714. {
  715. if (Utils.IsNullOrWhiteSpace(path))
  716. throw new ArgumentNullException("path");
  717. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  718. {
  719. var volumeRootPath = new StringBuilder(NativeMethods.MaxPathUnicode / 32);
  720. var pathLp = Path.GetFullPathCore(null, path, GetFullPathOptions.AsLongPath | GetFullPathOptions.FullCheck);
  721. // GetVolumePathName()
  722. // In the ANSI version of this function, the name is limited to 248 characters.
  723. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  724. // 2013-07-18: MSDN does not confirm LongPath usage but a Unicode version of this function exists.
  725. var getOk = NativeMethods.GetVolumePathName(pathLp, volumeRootPath, (uint) volumeRootPath.Capacity);
  726. var lastError = Marshal.GetLastWin32Error();
  727. if (getOk)
  728. return Path.GetRegularPathCore(volumeRootPath.ToString(), GetFullPathOptions.None, false);
  729. switch ((uint) lastError)
  730. {
  731. // Don't throw exception on these errors.
  732. case Win32Errors.ERROR_NO_MORE_FILES:
  733. case Win32Errors.ERROR_INVALID_PARAMETER:
  734. case Win32Errors.ERROR_INVALID_NAME:
  735. break;
  736. default:
  737. NativeError.ThrowException(lastError, path);
  738. break;
  739. }
  740. // Return original path.
  741. return path;
  742. }
  743. }
  744. #endregion // GetVolumePathName
  745. #region IsSameVolume
  746. /// <summary>Determines whether the volume of two file system objects is the same.</summary>
  747. /// <param name="path1">The first filesystem ojbect with full path information.</param>
  748. /// <param name="path2">The second file system object with full path information.</param>
  749. /// <returns><see langword="true"/> if both filesytem objects reside on the same volume, <see langword="false"/> otherwise.</returns>
  750. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  751. [SecurityCritical]
  752. public static bool IsSameVolume(string path1, string path2)
  753. {
  754. try
  755. {
  756. var volInfo1 = new VolumeInfo(GetVolumePathName(path1), true, true);
  757. var volInfo2 = new VolumeInfo(GetVolumePathName(path2), true, true);
  758. return volInfo1.SerialNumber == volInfo2.SerialNumber;
  759. }
  760. catch { }
  761. return false;
  762. }
  763. #endregion // IsSameVolume
  764. #region IsVolume
  765. /// <summary>Determines whether the specified volume name is a defined volume on the current computer.</summary>
  766. /// <param name="volumeMountPoint">
  767. /// A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\".
  768. /// </param>
  769. /// <returns><see langword="true"/> on success, <see langword="false"/> otherwise.</returns>
  770. [SecurityCritical]
  771. public static bool IsVolume(string volumeMountPoint)
  772. {
  773. return !Utils.IsNullOrWhiteSpace(GetVolumeGuid(volumeMountPoint));
  774. }
  775. #endregion // IsVolume
  776. #region SetCurrentVolumeLabel
  777. /// <summary>Sets the label of the file system volume that is the root of the current directory.</summary>
  778. /// <exception cref="ArgumentNullException"/>
  779. /// <param name="volumeName">A name for the volume.</param>
  780. [SecurityCritical]
  781. public static void SetCurrentVolumeLabel(string volumeName)
  782. {
  783. if (Utils.IsNullOrWhiteSpace(volumeName))
  784. throw new ArgumentNullException("volumeName");
  785. if (!NativeMethods.SetVolumeLabel(null, volumeName))
  786. Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
  787. }
  788. #endregion // SetCurrentVolumeLabel
  789. #region SetVolumeLabel
  790. /// <summary>Sets the label of a file system volume.</summary>
  791. /// <param name="volumePath">
  792. /// <para>A path to a volume. For example: "C:\", "\\server\share", or "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\"</para>
  793. /// <para>If this parameter is <see langword="null"/>, the function uses the current drive.</para>
  794. /// </param>
  795. /// <param name="volumeName">
  796. /// <para>A name for the volume.</para>
  797. /// <para>If this parameter is <see langword="null"/>, the function deletes any existing label</para>
  798. /// <para>from the specified volume and does not assign a new label.</para>
  799. /// </param>
  800. [SecurityCritical]
  801. public static void SetVolumeLabel(string volumePath, string volumeName)
  802. {
  803. // rootPathName == null is allowed, means current drive.
  804. // Setting volume label only applies to Logical Drives pointing to local resources.
  805. //if (!Path.IsLocalPath(rootPathName))
  806. //return false;
  807. volumePath = Path.AddTrailingDirectorySeparator(volumePath, false);
  808. // NTFS uses a limit of 32 characters for the volume label as of Windows Server 2003.
  809. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  810. if (!NativeMethods.SetVolumeLabel(volumePath, volumeName))
  811. NativeError.ThrowException(volumePath, volumeName);
  812. }
  813. #endregion // SetVolumeLabel
  814. #region SetVolumeMountPoint
  815. /// <summary>Associates a volume with a Drive letter or a directory on another volume.</summary>
  816. /// <exception cref="ArgumentNullException"/>
  817. /// <exception cref="ArgumentException"/>
  818. /// <param name="volumeMountPoint">
  819. /// The user-mode path to be associated with the volume. This may be a Drive letter (for example, "X:\")
  820. /// or a directory on another volume (for example, "Y:\MountX\").
  821. /// </param>
  822. /// <param name="volumeGuid">A <see cref="string"/> containing the volume <see cref="Guid"/>.</param>
  823. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Utils.IsNullOrWhiteSpace validates arguments.")]
  824. [SecurityCritical]
  825. public static void SetVolumeMountPoint(string volumeMountPoint, string volumeGuid)
  826. {
  827. if (Utils.IsNullOrWhiteSpace(volumeMountPoint))
  828. throw new ArgumentNullException("volumeMountPoint");
  829. if (Utils.IsNullOrWhiteSpace(volumeGuid))
  830. throw new ArgumentNullException("volumeGuid");
  831. if (!volumeGuid.StartsWith(Path.VolumePrefix + "{", StringComparison.OrdinalIgnoreCase))
  832. throw new ArgumentException(Resources.Not_A_Valid_Guid, volumeGuid);
  833. volumeMountPoint = Path.GetFullPathCore(null, volumeMountPoint, GetFullPathOptions.AsLongPath | GetFullPathOptions.AddTrailingDirectorySeparator | GetFullPathOptions.FullCheck);
  834. // This string must be of the form "\\?\Volume{GUID}\"
  835. volumeGuid = Path.AddTrailingDirectorySeparator(volumeGuid, false);
  836. // ChangeErrorMode is for the Win32 SetThreadErrorMode() method, used to suppress possible pop-ups.
  837. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  838. // SetVolumeMountPoint()
  839. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  840. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  841. // 2014-01-29: MSDN does not confirm LongPath usage but a Unicode version of this function exists.
  842. if (!NativeMethods.SetVolumeMountPoint(volumeMountPoint, volumeGuid))
  843. {
  844. var lastError = Marshal.GetLastWin32Error();
  845. // If the lpszVolumeMountPoint parameter contains a path to a mounted folder,
  846. // GetLastError returns ERROR_DIR_NOT_EMPTY, even if the directory is empty.
  847. if (lastError != Win32Errors.ERROR_DIR_NOT_EMPTY)
  848. NativeError.ThrowException(lastError, volumeGuid);
  849. }
  850. }
  851. #endregion // SetVolumeMountPoint
  852. #endregion // Volume
  853. #region Internal Methods
  854. /// <summary>Defines, redefines, or deletes MS-DOS device names.</summary>
  855. /// <exception cref="ArgumentNullException"/>
  856. /// <param name="isDefine">
  857. /// <see langword="true"/> defines a new MS-DOS device. <see langword="false"/> deletes a previously defined MS-DOS device.
  858. /// </param>
  859. /// <param name="deviceName">
  860. /// An MS-DOS device name string specifying the device the function is defining, redefining, or deleting.
  861. /// </param>
  862. /// <param name="targetPath">
  863. /// A pointer to a path string that will implement this device. The string is an MS-DOS path string unless the
  864. /// <see cref="DosDeviceAttributes.RawTargetPath"/> flag is specified, in which case this string is a path string.
  865. /// </param>
  866. /// <param name="deviceAttributes">
  867. /// The controllable aspects of the DefineDosDevice function, <see cref="DosDeviceAttributes"/> flags which will be combined with the
  868. /// default.
  869. /// </param>
  870. /// <param name="exactMatch">
  871. /// Only delete MS-DOS device on an exact name match. If <paramref name="exactMatch"/> is <see langword="true"/>,
  872. /// <paramref name="targetPath"/> must be the same path used to create the mapping.
  873. /// </param>
  874. ///
  875. /// <returns><see langword="true"/> on success, <see langword="false"/> otherwise.</returns>
  876. [SecurityCritical]
  877. internal static void DefineDosDeviceCore(bool isDefine, string deviceName, string targetPath, DosDeviceAttributes deviceAttributes, bool exactMatch)
  878. {
  879. if (Utils.IsNullOrWhiteSpace(deviceName))
  880. throw new ArgumentNullException("deviceName");
  881. if (isDefine)
  882. {
  883. // targetPath is allowed to be null.
  884. // In no case is a trailing backslash ("\") allowed.
  885. deviceName = Path.GetRegularPathCore(deviceName, GetFullPathOptions.RemoveTrailingDirectorySeparator | GetFullPathOptions.CheckInvalidPathChars, false);
  886. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  887. if (!NativeMethods.DefineDosDevice(deviceAttributes, deviceName, targetPath))
  888. NativeError.ThrowException(deviceName, targetPath);
  889. }
  890. else
  891. {
  892. // A pointer to a path string that will implement this device.
  893. // 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.
  894. if (exactMatch && !Utils.IsNullOrWhiteSpace(targetPath))
  895. deviceAttributes = deviceAttributes | DosDeviceAttributes.ExactMatchOnRemove | DosDeviceAttributes.RawTargetPath;
  896. // Remove the MS-DOS device name. First, get the name of the Windows NT device
  897. // from the symbolic link and then delete the symbolic link from the namespace.
  898. DefineDosDevice(deviceName, targetPath, deviceAttributes);
  899. }
  900. }
  901. /// <summary>Deletes a Drive letter or mounted folder.</summary>
  902. /// <remarks>
  903. /// <para>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.</para>
  904. /// <para>Deleting a mounted folder does not cause the underlying directory to be deleted.</para>
  905. /// </remarks>
  906. /// <exception cref="ArgumentNullException"/>
  907. /// <param name="volumeMountPoint">The Drive letter or mounted folder to be deleted. For example, X:\ or Y:\MountX\.</param>
  908. /// <param name="continueOnException">
  909. /// <see langword="true"/> suppress any Exception that might be thrown as a result from a failure, such as unavailable resources.
  910. /// </param>
  911. /// <returns>If completed successfully returns <see cref="Win32Errors.ERROR_SUCCESS"/>, otherwise the last error number.</returns>
  912. [SecurityCritical]
  913. internal static int DeleteVolumeMountPointCore(string volumeMountPoint, bool continueOnException)
  914. {
  915. if (Utils.IsNullOrWhiteSpace(volumeMountPoint))
  916. throw new ArgumentNullException("volumeMountPoint");
  917. var lastError = (int) Win32Errors.ERROR_SUCCESS;
  918. using (new NativeMethods.ChangeErrorMode(NativeMethods.ErrorMode.FailCriticalErrors))
  919. {
  920. // DeleteVolumeMountPoint()
  921. // In the ANSI version of this function, the name is limited to MAX_PATH characters.
  922. // To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path.
  923. // 2013-01-13: MSDN does not confirm LongPath usage but a Unicode version of this function exists.
  924. if (!NativeMethods.DeleteVolumeMountPoint(Path.AddTrailingDirectorySeparator(volumeMountPoint, false)))
  925. lastError = Marshal.GetLastWin32Error();
  926. if (lastError != Win32Errors.ERROR_SUCCESS && !continueOnException)
  927. {
  928. if (lastError == Win32Errors.ERROR_FILE_NOT_FOUND)
  929. lastError = (int) Win32Errors.ERROR_PATH_NOT_FOUND;
  930. NativeError.ThrowException(lastError, volumeMountPoint);
  931. }
  932. }
  933. return lastError;
  934. }
  935. #endregion // Internal Methods
  936. }
  937. }