MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.ComponentModel;9using System.Diagnostics;10using System.Diagnostics.CodeAnalysis;11using System.Globalization;12using System.IO;13using System.Linq;14using System.Management.Automation;15using System.Management.Automation.Internal;16using System.Management.Automation.Provider;17using System.Management.Automation.Runspaces;18using System.Runtime.InteropServices;19using System.Security;20using System.Security.AccessControl;21using System.Text;22using System.Threading.Tasks;23using System.Xml;24using System.Xml.XPath;25 26using Microsoft.Win32.SafeHandles;27 28using Dbg = System.Management.Automation;29 30namespace Microsoft.PowerShell.Commands31{32 #region FileSystemProvider33 34 /// <summary>35 /// Defines the implementation of a File System Provider. This provider36 /// allows for stateless namespace navigation of the file system.37 /// </summary>38 [CmdletProvider(FileSystemProvider.ProviderName, ProviderCapabilities.Credentials | ProviderCapabilities.Filter | ProviderCapabilities.ShouldProcess)]39 [OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)]40 [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)]41 [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)]42 [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PopLocation)]43 [OutputType(typeof(byte), typeof(string), ProviderCmdlet = ProviderCmdlet.GetContent)]44 [OutputType(typeof(FileInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]45 [OutputType(typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetChildItem)]46 [OutputType(typeof(FileSecurity), typeof(DirectorySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)]47 [OutputType(typeof(bool), typeof(string), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]48 [OutputType(typeof(bool), typeof(string), typeof(DateTime), typeof(System.IO.FileInfo), typeof(System.IO.DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItemProperty)]49 [OutputType(typeof(string), typeof(System.IO.FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.NewItem)]50 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This coupling is required")]51 public sealed partial class FileSystemProvider : NavigationCmdletProvider,52 IContentCmdletProvider,53 IPropertyCmdletProvider,54 ISecurityDescriptorCmdletProvider,55 ICmdletProviderSupportsHelp56 {57 // 4MB gives the best results without spiking the resources on the remote connection for file transfers between pssessions.58 // NOTE: The script used to copy file data from session (PSCopyFromSessionHelper) has a59 // maximum fragment size value for security. If FILETRANSFERSIZE changes make sure the60 // copy script will accommodate the new value.61 private const int FILETRANSFERSIZE = 4 * 1024 * 1024;62 63 private const int COPY_FILE_ACTIVITY_ID = 0;64 private const int REMOVE_FILE_ACTIVITY_ID = 0;65 66 // The name of the key in an exception's Data dictionary when attempting67 // to copy an item onto itself.68 private const string SelfCopyDataKey = "SelfCopy";69 70 /// <summary>71 /// An instance of the PSTraceSource class used for trace output72 /// using "FileSystemProvider" as the category.73 /// </summary>74 [Dbg.TraceSource("FileSystemProvider", "The namespace navigation provider for the file system")]75 private static readonly Dbg.PSTraceSource s_tracer =76 Dbg.PSTraceSource.GetTracer("FileSystemProvider", "The namespace navigation provider for the file system");77 78 /// <summary>79 /// Gets the name of the provider.80 /// </summary>81 public const string ProviderName = "FileSystem";82 83 /// <summary>84 /// Initializes a new instance of the FileSystemProvider class. Since this85 /// object needs to be stateless, the constructor does nothing.86 /// </summary>87 public FileSystemProvider()88 {89 }90 91 private Collection<WildcardPattern> _excludeMatcher = null;92 93 private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions94 {95 MatchType = MatchType.Win32,96 MatchCasing = MatchCasing.CaseInsensitive,97 AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior98 };99 100 /// <summary>101 /// Converts all / in the path to \102 /// </summary>103 /// <param name="path">104 /// The path to normalize.105 /// </param>106 /// <returns>107 /// The path with all / normalized to \108 /// </returns>109 internal static string NormalizePath(string path)110 {111 return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));112 }113 114 /// <summary>115 /// Get the correct casing for a path. This method assumes it's being called by NormalizePath()116 /// so that the path is already normalized.117 /// </summary>118 /// <param name="path">119 /// The path to retrieve.120 /// </param>121 /// <returns>122 /// The path with accurate casing if item exists, otherwise it returns path that was passed in.123 /// </returns>124 private static string GetCorrectCasedPath(string path)125 {126 // Only apply to directories where there are issues with some tools if the casing127 // doesn't match the source like git128 if (Directory.Exists(path))129 {130 string exactPath = string.Empty;131 int itemsToSkip = 0;132 if (Utils.PathIsUnc(path))133 {134 // With the Split method, a UNC path like \\server\share, we need to skip135 // trying to enumerate the server and share, so skip the first two empty136 // strings, then server, and finally share name.137 itemsToSkip = 4;138 }139 140 var items = path.Split(StringLiterals.DefaultPathSeparator);141 for (int i = 0; i < items.Length; i++)142 {143 if (itemsToSkip-- > 0)144 {145 // This handles the UNC server and share and 8.3 short path syntax146 exactPath += items[i] + StringLiterals.DefaultPathSeparator;147 continue;148 }149 else if (string.IsNullOrEmpty(exactPath))150 {151 // This handles the drive letter or / root path start152 exactPath = items[i] + StringLiterals.DefaultPathSeparator;153 }154 else if (string.IsNullOrEmpty(items[i]) && i == items.Length - 1)155 {156 // This handles the trailing slash case157 if (!exactPath.EndsWith(StringLiterals.DefaultPathSeparator))158 {159 exactPath += StringLiterals.DefaultPathSeparator;160 }161 162 break;163 }164 else if (items[i].Contains('~'))165 {166 // This handles short path names167 exactPath += StringLiterals.DefaultPathSeparator + items[i];168 }169 else170 {171 // Use GetFileSystemEntries to get the correct casing of this element172 try173 {174 var entries = Directory.GetFileSystemEntries(exactPath, items[i]);175 if (entries.Length > 0)176 {177 exactPath = entries[0];178 }179 else180 {181 // If previous call didn't return anything, something failed so we just return the path we were given182 return path;183 }184 }185 catch186 {187 // If we can't enumerate, we stop and just return the original path188 return path;189 }190 }191 }192 193 return exactPath;194 }195 else196 {197 return path;198 }199 }200 201 /// <summary>202 /// Checks if the item exist at the specified path. if it exists then creates203 /// appropriate directoryinfo or fileinfo object.204 /// </summary>205 /// <param name="path">206 /// Refers to the item for which we are checking for existence and creating filesysteminfo object.207 /// </param>208 /// <param name="isContainer">209 /// Return true if path points to a directory else returns false.210 /// </param>211 /// <returns>FileInfo or DirectoryInfo object.</returns>212 /// <exception cref="System.ArgumentNullException">213 /// The path is null.214 /// </exception>215 /// <exception cref="System.IO.IOException">216 /// I/O error occurs.217 /// </exception>218 /// <exception cref="System.UnauthorizedAccessException">219 /// An I/O error or a specific type of security error.220 /// </exception>221 private static FileSystemInfo GetFileSystemInfo(string path, out bool isContainer)222 {223 // We use 'FileInfo.Attributes' (not 'FileInfo.Exist')224 // because we want to get exceptions225 // like UnauthorizedAccessException or IOException.226 FileSystemInfo fsinfo = new FileInfo(path);227 var attr = fsinfo.Attributes;228 var exists = (int)attr != -1;229 isContainer = exists && attr.HasFlag(FileAttributes.Directory);230 231 if (exists)232 {233 if (isContainer)234 {235 return new DirectoryInfo(path);236 }237 else238 {239 return fsinfo;240 }241 }242 243 return null;244 }245 246 /// <summary>247 /// Overrides the method of CmdletProvider, considering the additional248 /// dynamic parameters of FileSystemProvider.249 /// </summary>250 /// <returns>251 /// whether the filter or attribute filter is set.252 /// </returns>253 internal override bool IsFilterSet()254 {255 bool attributeFilterSet = false;256 GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;257 if (fspDynamicParam != null)258 {259 attributeFilterSet = (260 (fspDynamicParam.Attributes != null)261 || (fspDynamicParam.Directory)262 || (fspDynamicParam.File)263 || (fspDynamicParam.Hidden)264 || (fspDynamicParam.ReadOnly)265 || (fspDynamicParam.System));266 }267 268 return (attributeFilterSet || base.IsFilterSet());269 }270 271 /// <summary>272 /// Gets the dynamic parameters for get-childnames on the273 /// FileSystemProvider.274 /// We currently only support one dynamic parameter,275 /// "Attributes" that returns an enum evaluator for the276 /// given expression.277 /// </summary>278 /// <param name="path">279 /// If the path was specified on the command line, this is the path280 /// to the item for which to get the dynamic parameters.281 /// </param>282 /// <returns>283 /// An object that has properties and fields decorated with284 /// parsing attributes similar to a cmdlet class.285 /// </returns>286 protected override object GetChildNamesDynamicParameters(string path)287 {288 return new GetChildDynamicParameters();289 }290 291 /// <summary>292 /// Gets the dynamic parameters for get-childitems on the293 /// FileSystemProvider.294 /// We currently only support one dynamic parameter,295 /// "Attributes" that returns an enum evaluator for the296 /// given expression.297 /// </summary>298 /// <param name="path">299 /// If the path was specified on the command line, this is the path300 /// to the item for which to get the dynamic parameters.301 /// </param>302 /// <param name="recurse">303 /// Ignored.304 /// </param>305 /// <returns>306 /// An object that has properties and fields decorated with307 /// parsing attributes similar to a cmdlet class.308 /// </returns>309 protected override object GetChildItemsDynamicParameters(string path, bool recurse)310 {311 return new GetChildDynamicParameters();312 }313 314 /// <summary>315 /// Gets the dynamic parameters for Copy-Item on the FileSystemProvider.316 /// </summary>317 /// <param name="path">Source for the copy operation.</param>318 /// <param name="destination">Destination for the copy operation.</param>319 /// <param name="recurse">Whether to recurse.</param>320 /// <returns></returns>321 protected override object CopyItemDynamicParameters(string path, string destination, bool recurse)322 {323 return new CopyItemDynamicParameters();324 }325 326 #region ICmdletProviderSupportsHelp members327 328 /// <summary>329 /// Implementation of ICmdletProviderSupportsHelp interface.330 /// Gets provider-specific help content for the corresponding cmdlet.331 /// </summary>332 /// <param name="helpItemName">333 /// Name of command that the help is requested for.334 /// </param>335 /// <param name="path">336 /// Not used here.337 /// </param>338 /// <returns>339 /// The MAML help XML that should be presented to the user.340 /// </returns>341 public string GetHelpMaml(string helpItemName, string path)342 {343 // Get the verb and noun from helpItemName344 //345 string verb = null;346 string noun = null;347 XmlReader reader = null;348 349 try350 {351 if (!string.IsNullOrEmpty(helpItemName))352 {353 CmdletInfo.SplitCmdletName(helpItemName, out verb, out noun);354 }355 else356 {357 return string.Empty;358 }359 360 if (string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(noun))361 {362 return string.Empty;363 }364 365 // Load the help file from the current UI culture subfolder366 XmlDocument document = new XmlDocument();367 CultureInfo currentUICulture = CultureInfo.CurrentUICulture;368 string fullHelpPath = Path.Combine(369 string.IsNullOrEmpty(this.ProviderInfo.ApplicationBase) ? string.Empty : this.ProviderInfo.ApplicationBase,370 currentUICulture.ToString(),371 string.IsNullOrEmpty(this.ProviderInfo.HelpFile) ? string.Empty : this.ProviderInfo.HelpFile);372 373 XmlReaderSettings settings = new XmlReaderSettings();374 settings.XmlResolver = null;375 reader = XmlReader.Create(fullHelpPath, settings);376 document.Load(reader);377 378 // Add "msh" and "command" namespaces from the MAML schema379 XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);380 nsMgr.AddNamespace("msh", HelpCommentsParser.mshURI);381 nsMgr.AddNamespace("command", HelpCommentsParser.commandURI);382 383 // Compose XPath query to select the appropriate node based on the cmdlet384 string xpathQuery = string.Format(385 CultureInfo.InvariantCulture,386 HelpCommentsParser.ProviderHelpCommandXPath,387 "[@id='FileSystem']",388 verb,389 noun);390 391 // Execute the XPath query and return its MAML snippet392 XmlNode result = document.SelectSingleNode(xpathQuery, nsMgr);393 if (result != null)394 {395 return result.OuterXml;396 }397 }398 catch (XmlException)399 {400 return string.Empty;401 }402 catch (PathTooLongException)403 {404 return string.Empty;405 }406 catch (IOException)407 {408 return string.Empty;409 }410 catch (UnauthorizedAccessException)411 {412 return string.Empty;413 }414 catch (NotSupportedException)415 {416 return string.Empty;417 }418 catch (SecurityException)419 {420 return string.Empty;421 }422 catch (XPathException)423 {424 return string.Empty;425 }426 finally427 {428 if (reader != null)429 {430 ((IDisposable)reader).Dispose();431 }432 }433 434 return string.Empty;435 }436 437 #endregion438 439 #region CmdletProvider members440 441 /// <summary>442 /// Starts the File System provider. This method sets the Home for the443 /// provider to providerInfo.Home if specified, and %USERPROFILE%444 /// otherwise.445 /// </summary>446 /// <param name="providerInfo">447 /// The ProviderInfo object that holds the provider's configuration.448 /// </param>449 /// <returns>450 /// The updated ProviderInfo object that holds the provider's configuration.451 /// </returns>452 protected override ProviderInfo Start(ProviderInfo providerInfo)453 {454 // Set the home folder for the user455 if (providerInfo != null && string.IsNullOrEmpty(providerInfo.Home))456 {457 // %USERPROFILE% - indicate where a user's home directory is located in the file system.458 string homeDirectory = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home);459 460 if (!string.IsNullOrEmpty(homeDirectory))461 {462 if (Directory.Exists(homeDirectory))463 {464 s_tracer.WriteLine("Home = {0}", homeDirectory);465 providerInfo.Home = homeDirectory;466 }467 else468 {469 s_tracer.WriteLine("Not setting home directory {0} - does not exist", homeDirectory);470 }471 }472 }473 474 // OneDrive placeholder support (issue #8315)475 // make it so OneDrive placeholders are perceived as such with *all* their attributes accessible476#if !UNIX477 // The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode478 // are only supported starting with Windows 10 version 1803 (build 17134)479 if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134, 0))480 {481 // let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect482 if (Interop.Windows.RtlQueryProcessPlaceholderCompatibilityMode() == Interop.Windows.PHCM_DISGUISE_PLACEHOLDER)483 {484 Interop.Windows.RtlSetProcessPlaceholderCompatibilityMode(Interop.Windows.PHCM_EXPOSE_PLACEHOLDERS);485 }486 }487#endif488 489 return providerInfo;490 }491 492 #endregion CmdletProvider members493 494 #region DriveCmdletProvider members495 496 /// <summary>497 /// Determines if the specified drive can be mounted.498 /// </summary>499 /// <param name="drive">500 /// The drive that is going to be mounted.501 /// </param>502 /// <returns>503 /// The same drive that was passed in, if the drive can be mounted.504 /// null if the drive cannot be mounted.505 /// </returns>506 /// <exception cref="System.ArgumentNullException">507 /// drive is null.508 /// </exception>509 /// <exception cref="System.ArgumentException">510 /// drive root is null or empty.511 /// </exception>512 protected override PSDriveInfo NewDrive(PSDriveInfo drive)513 {514 // verify parameters515 if (drive == null)516 {517 throw PSTraceSource.NewArgumentNullException(nameof(drive));518 }519 520 if (string.IsNullOrEmpty(drive.Root))521 {522 throw PSTraceSource.NewArgumentException("drive.Root");523 }524 525 // -Persist switch parameter is supported only for Network paths.526 if (drive.Persist && !PathIsNetworkPath(drive.Root))527 {528 ErrorRecord er = new ErrorRecord(new NotSupportedException(FileSystemProviderStrings.PersistNotSupported), "DriveRootNotNetworkPath", ErrorCategory.InvalidArgument, drive);529 ThrowTerminatingError(er);530 }531 532 if (IsNetworkMappedDrive(drive))533 {534 // MapNetworkDrive facilitates to map the newly535 // created PS Drive to a network share.536 MapNetworkDrive(drive);537 }538 539 // The drive is valid if the item exists or the540 // drive is not a fixed drive. We want to allow541 // a drive to exist for floppies and other such\542 // removable media, even if the media isn't in place.543 bool driveIsFixed = true;544 PSDriveInfo result = null;545 546 try547 {548 // See if the drive is a fixed drive.549 string pathRoot = Path.GetPathRoot(drive.Root);550 DriveInfo driveInfo = new DriveInfo(pathRoot);551 552 if (driveInfo.DriveType != DriveType.Fixed)553 {554 driveIsFixed = false;555 }556 557 // The current drive is a network drive.558 if (driveInfo.DriveType == DriveType.Network)559 {560 drive.IsNetworkDrive = true;561 }562 }563 catch (ArgumentException) // swallow ArgumentException incl. ArgumentNullException564 {565 }566 567 bool validDrive = true;568 569 if (driveIsFixed)570 {571 // Since the drive is fixed, ensure the root is valid.572 validDrive = Directory.Exists(drive.Root);573 }574 575 if (validDrive)576 {577 result = drive;578 }579 else580 {581 string error = StringUtil.Format(FileSystemProviderStrings.DriveRootError, drive.Root);582 Exception e = new IOException(error);583 WriteError(new ErrorRecord(e, "DriveRootError", ErrorCategory.ReadError, drive));584 }585 586 drive.Trace();587 588 return result;589 }590 591 /// <summary>592 /// MapNetworkDrive facilitates to map the newly created PS Drive to a network share.593 /// </summary>594 /// <param name="drive">The PSDrive info that would be used to create a new PS drive.</param>595 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Can be static on Unix but not on Windows.")]596 597 private void MapNetworkDrive(PSDriveInfo drive)598 {599#if UNIX600 throw new PlatformNotSupportedException();601#else602 // Porting note: mapped network drives are only supported on Windows603 if (drive != null && !string.IsNullOrEmpty(drive.Root))604 {605 // By default the connection is not persisted.606 int connectType = Interop.Windows.CONNECT_NOPERSIST;607 608 string driveName = null;609 byte[] passwd = null;610 string userName = null;611 612 if (drive.Persist)613 {614 if (IsSupportedDriveForPersistence(drive))615 {616 connectType = Interop.Windows.CONNECT_UPDATE_PROFILE;617 driveName = drive.Name + ":";618 drive.DisplayRoot = drive.Root;619 }620 else621 {622 ErrorRecord er = new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.InvalidDriveName), "DriveNameNotSupportedForPersistence", ErrorCategory.InvalidOperation, drive);623 ThrowTerminatingError(er);624 }625 }626 627 // If alternate credentials is supplied then use them to get connected to network share.628 if (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty))629 {630 userName = drive.Credential.UserName;631 632 passwd = SecureStringHelper.GetData(drive.Credential.Password);633 }634 635 try636 {637 int errorCode = Interop.Windows.WNetAddConnection2(driveName, drive.Root, passwd, userName, connectType);638 639 if (errorCode != Interop.Windows.ERROR_SUCCESS)640 {641 ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(errorCode), "CouldNotMapNetworkDrive", ErrorCategory.InvalidOperation, drive);642 ThrowTerminatingError(er);643 }644 645 if (connectType == Interop.Windows.CONNECT_UPDATE_PROFILE)646 {647 // Update the current PSDrive to be a persisted drive.648 drive.IsNetworkDrive = true;649 650 // PsDrive.Root is updated to the name of the Drive for651 // drives targeting network path and being persisted.652 drive.Root = driveName + @"\";653 }654 }655 finally656 {657 // Clear the password in the memory.658 if (passwd != null)659 {660 Array.Clear(passwd);661 }662 }663 }664#endif665 }666 667 /// <summary>668 /// ShouldMapNetworkDrive is a helper function used to detect if the669 /// requested PSDrive to be created has to be mapped to a network drive.670 /// </summary>671 /// <param name="drive"></param>672 /// <returns></returns>673 private static bool IsNetworkMappedDrive(PSDriveInfo drive)674 {675 bool shouldMapNetworkDrive = (drive != null && !string.IsNullOrEmpty(drive.Root) && PathIsNetworkPath(drive.Root)) &&676 (drive.Persist || (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty)));677 678 return shouldMapNetworkDrive;679 }680 681 /// <summary>682 /// RemoveDrive facilitates to remove network mapped persisted PSDrvie.683 /// </summary>684 /// <param name="drive">685 /// PSDrive info.686 /// </param>687 /// <returns>PSDrive info.688 /// </returns>689 protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)690 {691#if UNIX692 return drive;693#else694 if (IsNetworkMappedDrive(drive))695 {696 int flags = Interop.Windows.CONNECT_NOPERSIST;697 string driveName;698 if (drive.IsNetworkDrive)699 {700 // Here we are removing only persisted network drives.701 flags = Interop.Windows.CONNECT_UPDATE_PROFILE;702 driveName = drive.Name + ":";703 }704 else705 {706 // OSGTFS: 608188 PSDrive leaves a connection open after the drive is removed707 // if a drive is not persisted or networkdrive, we need to use the actual root to remove the drive.708 driveName = drive.Root;709 }710 711 // You need to actually remove the drive.712 int errorCode = Interop.Windows.WNetCancelConnection2(driveName, flags, force: true);713 714 if (errorCode != Interop.Windows.ERROR_SUCCESS)715 {716 ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(errorCode), "CouldRemoveNetworkDrive", ErrorCategory.InvalidOperation, drive);717 ThrowTerminatingError(er);718 }719 }720 721 return drive;722#endif723 }724 725 /// <summary>726 /// IsSupportedDriveForPersistence is a helper method used to727 /// check if the psdrive can be persisted or not.728 /// </summary>729 /// <param name="drive">730 /// PS Drive Info.731 /// </param>732 /// <returns>True if the drive can be persisted or else false.</returns>733 private static bool IsSupportedDriveForPersistence(PSDriveInfo drive)734 {735 bool isSupportedDriveForPersistence = false;736 if (drive != null && !string.IsNullOrEmpty(drive.Name) && drive.Name.Length == 1)737 {738 char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture);739 740 if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z')741 {742 isSupportedDriveForPersistence = true;743 }744 }745 746 return isSupportedDriveForPersistence;747 }748 749 /// <summary>750 /// Return the UNC path for a given network drive751 /// using the Windows API.752 /// </summary>753 /// <param name="driveName"></param>754 /// <returns></returns>755 internal static string GetUNCForNetworkDrive(string driveName)756 {757#if UNIX758 return driveName;759#else760 string uncPath = null;761 if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)762 {763 int errorCode = Interop.Windows.GetUNCForNetworkDrive(driveName[0], out uncPath);764 765 if (errorCode != Interop.Windows.ERROR_SUCCESS)766 {767 throw new System.ComponentModel.Win32Exception(errorCode);768 }769 }770 771 return uncPath;772#endif773 }774 775 /// <summary>776 /// Get the substituted path of a NetWork type MS-DOS device that is created by 'subst' command.777 /// When a MS-DOS device is of NetWork type, it could be:778 /// 1. Substitute a path in a drive that maps to a network location. For example:779 /// net use z: \\scratch2\scratch\780 /// subst y: z:\abc\781 /// 2. Substitute a network location directly. For example:782 /// subst y: \\scratch2\scratch\783 /// </summary>784 /// <param name="driveName"></param>785 /// <returns></returns>786 internal static string GetSubstitutedPathForNetworkDosDevice(string driveName)787 {788#if UNIX789 throw new PlatformNotSupportedException();790 }791#else792 return WinGetSubstitutedPathForNetworkDosDevice(driveName);793 }794 795 private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName)796 {797 string associatedPath = null;798 if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)799 {800 associatedPath = Interop.Windows.GetDosDeviceForNetworkPath(driveName[0]);801 }802 803 return associatedPath;804 }805#endif806 807 /// <summary>808 /// Get the root path for a network drive or MS-DOS device.809 /// </summary>810 /// <param name="driveInfo"></param>811 /// <returns></returns>812 internal static string GetRootPathForNetworkDriveOrDosDevice(DriveInfo driveInfo)813 {814 Dbg.Diagnostics.Assert(driveInfo.DriveType == DriveType.Network, "Caller should make sure it is a network drive.");815 816 string driveName = driveInfo.Name.Substring(0, 1);817 string rootPath = null;818 819 try820 {821 rootPath = GetUNCForNetworkDrive(driveName);822 }823 catch (Win32Exception)824 {825 if (driveInfo.IsReady)826 {827 // The drive is ready but we failed to find the UNC path based on the drive name.828 // In this case, it's possibly a MS-DOS device created by 'subst' command that829 // - substitutes a network location directly, or830 // - substitutes a path in a drive that maps to a network location831 rootPath = GetSubstitutedPathForNetworkDosDevice(driveName);832 }833 else834 {835 throw;836 }837 }838 839 return rootPath;840 }841 842 /// <summary>843 /// Returns a collection of all logical drives in the system.844 /// </summary>845 /// <returns>846 /// A collection of PSDriveInfo objects, one for each logical drive returned from847 /// System.Environment.GetLogicalDrives().848 /// </returns>849 protected override Collection<PSDriveInfo> InitializeDefaultDrives()850 {851 Collection<PSDriveInfo> results = new Collection<PSDriveInfo>();852 853 DriveInfo[] logicalDrives = DriveInfo.GetDrives();854 if (logicalDrives != null)855 {856 foreach (DriveInfo newDrive in logicalDrives)857 {858 // Making sure to obey the StopProcessing.859 if (Stopping)860 {861 results.Clear();862 break;863 }864 865 // cover everything by the try-catch block, because some of the866 // DriveInfo properties may throw exceptions867 try868 {869 string newDriveName = newDrive.Name.Substring(0, 1);870 871 string description = string.Empty;872 string root = newDrive.Name;873 string displayRoot = null;874 875 if (newDrive.DriveType == DriveType.Fixed)876 {877 try878 {879 description = newDrive.VolumeLabel;880 }881 // trying to read the volume label may cause an882 // IOException or SecurityException. Just default883 // to an empty description.884 catch (IOException)885 {886 }887 catch (System.Security.SecurityException)888 {889 }890 catch (System.UnauthorizedAccessException)891 {892 }893 }894 895 if (newDrive.DriveType == DriveType.Network)896 {897 // Platform notes: This is important because certain mount898 // points on non-Windows are enumerated as drives by .NET, but899 // the platform itself then has no real network drive support900 // as required by this context. Solution: check for network901 // drive support before using it.902#if UNIX903 continue;904#else905 displayRoot = GetRootPathForNetworkDriveOrDosDevice(newDrive);906#endif907 }908 909 if (newDrive.DriveType == DriveType.Fixed)910 {911 if (!newDrive.RootDirectory.Exists)912 {913 continue;914 }915 916 root = newDrive.RootDirectory.FullName;917 }918 919#if UNIX920 // Porting notes: On platforms with single root filesystems, ensure921 // that we add a filesystem with the root "/" to the initial drive list,922 // otherwise path handling will not work correctly because there923 // is no : available to separate the filesystems from each other924 if (root != StringLiterals.DefaultPathSeparatorString925 && newDriveName == StringLiterals.DefaultPathSeparatorString)926 {927 root = StringLiterals.DefaultPathSeparatorString;928 }929#endif930 931 // Porting notes: On non-windows platforms .net can report two932 // drives with the same root, make sure to only add one of those933 bool skipDuplicate = false;934 foreach (PSDriveInfo driveInfo in results)935 {936 if (driveInfo.Root == root)937 {938 skipDuplicate = true;939 break;940 }941 }942 943 if (skipDuplicate)944 {945 continue;946 }947 948 // Create a new VirtualDrive for each logical drive949 PSDriveInfo newPSDriveInfo =950 new PSDriveInfo(951 newDriveName,952 ProviderInfo,953 root,954 description,955 null,956 displayRoot);957 958 // The network drive is detected when PowerShell is launched.959 // Hence it has been persisted during one of the earlier sessions,960 if (newDrive.DriveType == DriveType.Network)961 {962 newPSDriveInfo.IsNetworkDrive = true;963 }964 965 if (newDrive.DriveType != DriveType.Fixed)966 {967 newPSDriveInfo.IsAutoMounted = true;968 }969 970 // Porting notes: on the non-Windows platforms, the drive never971 // uses : as a separator between drive and path972 if (!Platform.IsWindows)973 {974 newPSDriveInfo.VolumeSeparatedByColon = false;975 }976 977 results.Add(newPSDriveInfo);978 }979 // If there are issues accessing properties of the DriveInfo, do980 // not add the drive981 catch (IOException)982 {983 }984 catch (System.Security.SecurityException)985 {986 }987 catch (System.UnauthorizedAccessException)988 {989 }990 }991 }992 993 results.Add(994 new PSDriveInfo(995 DriveNames.TempDrive,996 ProviderInfo,997 Path.GetTempPath(),998 SessionStateStrings.TempDriveDescription,999 credential: null,1000 displayRoot: null)1001 );1002 1003 return results;1004 }1005 1006 #endregion DriveCmdletProvider methods1007 1008 #region ItemCmdletProvider methods1009 1010 /// <summary>1011 /// Retrieves the dynamic parameters required for the Get-Item cmdlet.1012 /// </summary>1013 /// <param name="path">The path of the file to process.</param>1014 /// <returns>An instance of the FileSystemProviderGetItemDynamicParameters class that represents the dynamic parameters.</returns>1015 protected override object GetItemDynamicParameters(string path)1016 {1017 return new FileSystemProviderGetItemDynamicParameters();1018 }1019 1020 /// <summary>1021 /// Determines if the specified path is syntactically and semantically valid.1022 /// An example path looks like this1023 /// C:\WINNT\Media\chimes.wav.1024 /// </summary>1025 /// <param name="path">1026 /// The fully qualified path to validate.1027 /// </param>1028 /// <returns>1029 /// True if the path is valid, false otherwise.1030 /// </returns>1031 protected override bool IsValidPath(string path)1032 {1033 // Path passed should be fully qualified path.1034 if (string.IsNullOrEmpty(path))1035 {1036 return false;1037 }1038 1039 // Normalize the path1040 path = NormalizePath(path);1041 path = EnsureDriveIsRooted(path);1042 1043#if !UNIX1044 // Remove alternate data stream references1045 // See if they've used the inline stream syntax. They have more than one colon.1046 int firstColon = path.IndexOf(':');1047 int secondColon = path.IndexOf(':', firstColon + 1);1048 if (secondColon > 0)1049 {1050 path = path.Substring(0, secondColon);1051 }1052#endif1053 1054 // Make sure the path is either drive rooted or UNC Path1055 if (!IsAbsolutePath(path) && !Utils.PathIsUnc(path))1056 {1057 return false;1058 }1059 1060 // Exceptions should only deal with exceptional circumstances,1061 // but unfortunately, FileInfo offers no Try() methods that1062 // let us check if we _could_ open the file.1063 try1064 {1065 FileInfo testFile = new FileInfo(path);1066 }1067 catch (Exception e)1068 {1069 if ((e is ArgumentNullException) ||1070 (e is ArgumentException) ||1071 (e is System.Security.SecurityException) ||1072 (e is UnauthorizedAccessException) ||1073 (e is PathTooLongException) ||1074 (e is NotSupportedException))1075 {1076 return false;1077 }1078 else1079 {1080 throw;1081 }1082 }1083 1084 // .NET introduced a change where invalid characters are accepted https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.1#path-apis-dont-throw-an-exception-for-invalid-characters1085 // We need to check for invalid characters ourselves. `Path.GetInvalidFileNameChars()` is a supserset of `Path.GetInvalidPathChars()`1086 1087 // Remove drive root first1088 string pathWithoutDriveRoot = path.Substring(Path.GetPathRoot(path).Length);1089 1090 foreach (string segment in pathWithoutDriveRoot.Split(Path.DirectorySeparatorChar))1091 {1092 if (PathUtils.ContainsInvalidFileNameChars(segment))1093 {1094 return false;1095 }1096 }1097 1098 return true;1099 }1100 1101 /// <summary>1102 /// Gets the item at the specified path.1103 /// </summary>1104 /// <param name="path">1105 /// A fully qualified path representing a file or directory in the1106 /// file system.1107 /// </param>1108 /// <returns>1109 /// Nothing. FileInfo and DirectoryInfo objects are written to the1110 /// context's pipeline.1111 /// </returns>1112 /// <exception cref="System.ArgumentException">1113 /// path is null or empty.1114 /// </exception>1115 protected override void GetItem(string path)1116 {1117 // Validate the argument1118 bool isContainer = false;1119 1120 if (string.IsNullOrEmpty(path))1121 {1122 // The parameter was null, throw an exception1123 throw PSTraceSource.NewArgumentException(nameof(path));1124 }1125 1126 try1127 {1128#if !UNIX1129 bool retrieveStreams = false;1130 FileSystemProviderGetItemDynamicParameters dynamicParameters = null;1131 1132 if (DynamicParameters != null)1133 {1134 dynamicParameters = DynamicParameters as FileSystemProviderGetItemDynamicParameters;1135 if (dynamicParameters != null)1136 {1137 if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))1138 {1139 retrieveStreams = true;1140 }1141 else1142 {1143 // See if they've used the inline stream syntax. They have more than one colon.1144 int firstColon = path.IndexOf(':');1145 int secondColon = path.IndexOf(':', firstColon + 1);1146 if (secondColon > 0)1147 {1148 string streamName = path.Substring(secondColon + 1);1149 path = path.Remove(secondColon);1150 1151 retrieveStreams = true;1152 dynamicParameters = new FileSystemProviderGetItemDynamicParameters();1153 dynamicParameters.Stream = new string[] { streamName };1154 }1155 }1156 }1157 }1158#endif1159 1160 FileSystemInfo result = GetFileSystemItem(path, ref isContainer, false);1161 if (result != null)1162 {1163#if !UNIX1164 // If we want to retrieve the file streams, retrieve them.1165 if (retrieveStreams)1166 {1167 foreach (string desiredStream in dynamicParameters.Stream)1168 {1169 // See that it matches the name specified1170 WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);1171 bool foundStream = false;1172 1173 foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName))1174 {1175 if (!p.IsMatch(stream.Stream))1176 {1177 continue;1178 }1179 1180 string outputPath = result.FullName + ":" + stream.Stream;1181 // Alternate data streams can never be containers.1182 WriteItemObject(stream, outputPath, isContainer: false);1183 foundStream = true;1184 }1185 1186 if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))1187 {1188 string errorMessage = StringUtil.Format(1189 FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName);1190 Exception e = new FileNotFoundException(errorMessage, result.FullName);1191 1192 WriteError(new ErrorRecord(1193 e,1194 "AlternateDataStreamNotFound",1195 ErrorCategory.ObjectNotFound,1196 path));1197 }1198 }1199 }1200 else