MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Collections.ObjectModel;6using System.ComponentModel;7using System.Globalization;8using System.IO;9using System.Management.Automation.Provider;10 11using Dbg = System.Management.Automation;12 13#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings14#pragma warning disable 5650015 16namespace System.Management.Automation17{18 /// <summary>19 /// Holds the state of a Monad Shell session.20 /// </summary>21 internal sealed partial class SessionStateInternal22 {23 /// <summary>24 /// The currently active drive. It determines the current working directory.25 /// </summary>26 private PSDriveInfo _currentDrive;27 28 #region NewDrive29 30 /// <summary>31 /// Adds the specified drive to the current scope.32 /// </summary>33 /// <param name="drive">34 /// The drive to be added to the current scope.35 /// </param>36 /// <param name="scopeID">37 /// The ID for the scope to add the drive to. The scope ID can be any of the38 /// "special" scope identifiers like "global", "local", or "private" or it39 /// can be a numeric identifier that is a count of the number of parent40 /// scopes up from the current scope to put the drive in.41 /// If this parameter is null or empty the drive will be placed in the42 /// current scope.43 /// </param>44 /// <returns>45 /// The drive that was added, if any.46 /// </returns>47 /// <exception cref="ArgumentNullException">48 /// If <paramref name="drive"/> is null.49 /// </exception>50 /// <exception cref="ArgumentException">51 /// If the drive already exists,52 /// or53 /// If <paramref name="drive"/>.Name contains one or more invalid characters; ~ / \\ . :54 /// </exception>55 /// <exception cref="NotSupportedException">56 /// If the provider is not a DriveCmdletProvider.57 /// </exception>58 /// <exception cref="ProviderNotFoundException">59 /// The provider for the <paramref name="drive"/> could not be found.60 /// </exception>61 /// <exception cref="ProviderInvocationException">62 /// If the provider threw an exception or returned null.63 /// </exception>64 internal PSDriveInfo NewDrive(PSDriveInfo drive, string scopeID)65 {66 if (drive == null)67 {68 throw PSTraceSource.NewArgumentNullException(nameof(drive));69 }70 71 PSDriveInfo result = null;72 73 // Construct a CmdletProviderContext and call the override74 75 CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);76 77 NewDrive(drive, scopeID, context);78 79 context.ThrowFirstErrorOrDoNothing();80 81 Collection<PSObject> successObjects = context.GetAccumulatedObjects();82 83 if (successObjects != null &&84 successObjects.Count > 0)85 {86 Dbg.Diagnostics.Assert(87 successObjects.Count == 1,88 "NewDrive should only add one PSDriveInfo object to the pipeline");89 90 // set the return value to the first drive (should only be one).91 92 if (!successObjects[0].ImmediateBaseObjectIsEmpty)93 {94 result = (PSDriveInfo)successObjects[0].BaseObject;95 }96 }97 98 return result;99 }100 101 /// <summary>102 /// Adds a drive to the PowerShell namespace.103 /// </summary>104 /// <param name="drive">105 /// The new drive to be added.106 /// </param>107 /// <param name="scopeID">108 /// The ID for the scope to add the drive to. The scope ID can be any of the109 /// "special" scope identifiers like "global", "local", or "private" or it110 /// can be a numeric identifier that is a count of the number of parent111 /// scopes up from the current scope to put the drive in.112 /// If this parameter is null or empty the drive will be placed in the113 /// current scope.114 /// </param>115 /// <param name="context">116 /// The context which the core command is running.117 /// </param>118 /// <exception cref="ArgumentNullException">119 /// If <paramref name="drive"/> or <paramref name="context"/> is null.120 /// </exception>121 /// <exception cref="ArgumentException">122 /// If the drive already exists123 /// or124 /// If <paramref name="drive"/>.Name contains one or more invalid characters; ~ / \\ . :125 /// </exception>126 /// <exception cref="ArgumentOutOfRangeException">127 /// If <paramref name="scopeID"/> is less than zero or greater than the number of currently128 /// active scopes.129 /// </exception>130 /// <exception cref="NotSupportedException">131 /// If the provider is not a DriveCmdletProvider.132 /// </exception>133 /// <exception cref="ProviderNotFoundException">134 /// The provider for the <paramref name="drive"/> could not be found.135 /// </exception>136 /// <exception cref="ProviderInvocationException">137 /// If the provider threw an exception or returned null.138 /// </exception>139 internal void NewDrive(PSDriveInfo drive, string scopeID, CmdletProviderContext context)140 {141 if (drive == null)142 {143 throw PSTraceSource.NewArgumentNullException(nameof(drive));144 }145 146 if (context == null)147 {148 throw PSTraceSource.NewArgumentNullException(nameof(context));149 }150 151 if (!IsValidDriveName(drive.Name))152 {153 ArgumentException e =154 PSTraceSource.NewArgumentException(155 "drive.Name",156 SessionStateStrings.DriveNameIllegalCharacters);157 158 throw e;159 }160 161 // Allow the provider a chance to approve the drive and set162 // provider specific data163 164 PSDriveInfo result = ValidateDriveWithProvider(drive, context, true);165 166 // We assume that the provider wrote the error message as they167 // are suppose to.168 if (result == null)169 {170 return;171 }172 173 if (string.Equals(result.Name, drive.Name, StringComparison.OrdinalIgnoreCase))174 {175 // Set the drive in the current scope.176 177 try178 {179 SessionStateScope scope = _currentScope;180 181 if (!string.IsNullOrEmpty(scopeID))182 {183 scope = GetScopeByID(scopeID);184 }185 186 scope.NewDrive(result);187 }188 catch (ArgumentException argumentException)189 {190 // Wrap up the exception and write it to the error stream191 192 context.WriteError(193 new ErrorRecord(194 argumentException,195 "NewDriveError",196 ErrorCategory.InvalidArgument,197 result));198 return;199 }200 catch (SessionStateException)201 {202 // This should be a pipeline terminating condition203 throw;204 }205 206 if (ProvidersCurrentWorkingDrive[drive.Provider] == null)207 {208 // Set the new drive as the current209 // drive for the provider since there isn't one set.210 211 ProvidersCurrentWorkingDrive[drive.Provider] = drive;212 }213 214 // Upon success, write the drive to the pipeline215 216 context.WriteObject(result);217 }218 else219 {220 ProviderInvocationException e =221 NewProviderInvocationException(222 "NewDriveProviderFailed",223 SessionStateStrings.NewDriveProviderFailed,224 drive.Provider,225 drive.Root,226 PSTraceSource.NewArgumentException("root"));227 228 throw e;229 }230 }231 232 private static bool IsValidDriveName(string name)233 {234 const string CharactersInvalidInDriveName = ":/\\.~";235 236 return !string.IsNullOrEmpty(name)237 && name.AsSpan().IndexOfAny(CharactersInvalidInDriveName) < 0;238 }239 240 /// <summary>241 /// Tries to resolve the drive root as an MSH path. If it successfully resolves242 /// to a single path then the resolved provider internal path is returned. If it243 /// does not resolve to a single MSH path the root is returned as it was passed.244 /// </summary>245 /// <param name="root">246 /// The root path of the drive to be resolved.247 /// </param>248 /// <param name="provider">249 /// The provider that should be used when resolving the path.250 /// </param>251 /// <returns>252 /// The new root path of the drive.253 /// </returns>254 private string GetProviderRootFromSpecifiedRoot(string root, ProviderInfo provider)255 {256 Dbg.Diagnostics.Assert(257 root != null,258 "Caller should have verified the root");259 260 Dbg.Diagnostics.Assert(261 provider != null,262 "Caller should have verified the provider");263 264 string result = root;265 266 SessionState sessionState = new SessionState(ExecutionContext.TopLevelSessionState);267 Collection<string> resolvedPaths = null;268 ProviderInfo resolvedProvider = null;269 270 try271 {272 // First try to resolve the root as an MSH path273 274 resolvedPaths =275 sessionState.Path.GetResolvedProviderPathFromPSPath(root, out resolvedProvider);276 277 // If a single path was resolved...278 279 if (resolvedPaths != null &&280 resolvedPaths.Count == 1)281 {282 // and the provider used to resolve the path,283 // matches the one specified by the drive...284 285 if (provider.NameEquals(resolvedProvider.FullName))286 {287 // and the item exists288 289 ProviderIntrinsics providerIntrinsics =290 new ProviderIntrinsics(this);291 292 if (providerIntrinsics.Item.Exists(root))293 {294 // then use the resolved path as the root of the drive295 296 result = resolvedPaths[0];297 }298 }299 }300 }301 catch (LoopFlowException)302 {303 throw;304 }305 catch (PipelineStoppedException)306 {307 throw;308 }309 catch (ActionPreferenceStopException)310 {311 throw;312 }313 // If any of the following exceptions are thrown we assume that314 // the path is a file system path not an MSH path and try315 // to create the drive with that root.316 catch (DriveNotFoundException)317 {318 }319 catch (ProviderNotFoundException)320 {321 }322 catch (ItemNotFoundException)323 {324 }325 catch (NotSupportedException)326 {327 }328 catch (InvalidOperationException)329 {330 }331 catch (ProviderInvocationException)332 {333 }334 catch (ArgumentException)335 {336 }337 338 return result;339 }340 341 /// <summary>342 /// Gets an object that defines the additional parameters for the NewDrive implementation343 /// for a provider.344 /// </summary>345 /// <param name="providerId">346 /// The provider ID for the drive that is being created.347 /// </param>348 /// <param name="context">349 /// The context under which this method is being called.350 /// </param>351 /// <returns>352 /// An object that has properties and fields decorated with353 /// parsing attributes similar to a cmdlet class.354 /// </returns>355 /// <exception cref="NotSupportedException">356 /// If the <paramref name="providerId"/> is not a DriveCmdletProvider.357 /// </exception>358 /// <exception cref="ProviderNotFoundException">359 /// If <paramref name="providerId"/> does not exist.360 /// </exception>361 internal object NewDriveDynamicParameters(string providerId, CmdletProviderContext context)362 {363 if (providerId == null)364 {365 // If the provider hasn't been specified yet, just return null.366 // The provider can be specified as pipeline input.367 return null;368 }369 370 DriveCmdletProvider provider = GetDriveProviderInstance(providerId);371 372 object result = null;373 try374 {375 result = provider.NewDriveDynamicParameters(context);376 }377 catch (Exception e) // Catch-all OK, 3rd party callout378 {379 throw380 NewProviderInvocationException(381 "NewDriveDynamicParametersProviderException",382 SessionStateStrings.NewDriveDynamicParametersProviderException,383 provider.ProviderInfo,384 null,385 e);386 }387 388 return result;389 }390 391 #endregion NewDrive392 393 #region GetDrive394 395 /// <summary>396 /// Searches through the session state scopes to find a drive.397 /// </summary>398 /// <param name="name">399 /// The name of a drive to find.400 /// </param>401 /// <returns>402 /// The drive information if the drive is found.403 /// </returns>404 /// <exception cref="ArgumentNullException">405 /// If <paramref name="name"/> is null.406 /// </exception>407 /// <exception cref="DriveNotFoundException">408 /// If there is no drive with <paramref name="name"/>.409 /// </exception>410 internal PSDriveInfo GetDrive(string name)411 {412 return GetDrive(name, true);413 }414 415 private PSDriveInfo GetDrive(string name, bool automount)416 {417 if (name == null)418 {419 throw PSTraceSource.NewArgumentNullException(nameof(name));420 }421 422 PSDriveInfo result = null;423 424 // Start searching through the scopes for the drive until the drive425 // is found or the global scope is reached.426 427 SessionStateScopeEnumerator scopeEnumerator = new SessionStateScopeEnumerator(CurrentScope);428 429 int scopeID = 0;430 431 foreach (SessionStateScope processingScope in scopeEnumerator)432 {433 result = processingScope.GetDrive(name);434 435 if (result != null)436 {437 if (result.IsAutoMounted)438 {439 // Validate or remove the auto-mounted drive440 441 if (!ValidateOrRemoveAutoMountedDrive(result, processingScope))442 {443 result = null;444 }445 }446 447 if (result != null)448 {449 s_tracer.WriteLine("Drive found in scope {0}", scopeID);450 break;451 }452 }453 454 // Increment the scope ID455 ++scopeID;456 }457 458 if (result == null && automount)459 {460 // Attempt to automount as a file system drive461 // or as a BuiltIn drive (e.g. "Cert"/"Certificate"/"WSMan")462 result = AutomountFileSystemDrive(name) ?? AutomountBuiltInDrive(name);463 }464 465 if (result == null)466 {467 DriveNotFoundException driveNotFound =468 new DriveNotFoundException(469 name,470 "DriveNotFound",471 SessionStateStrings.DriveNotFound);472 473 throw driveNotFound;474 }475 476 return result;477 }478 479 /// <summary>480 /// Searches through the session state scopes looking481 /// for a drive of the specified name.482 /// </summary>483 /// <param name="name">484 /// The name of the drive to return.485 /// </param>486 /// <param name="scopeID">487 /// The scope ID of the scope to look in for the drive.488 /// If this parameter is null or empty the drive will be489 /// found by searching the scopes using the dynamic scoping490 /// rules.491 /// </param>492 /// <returns>493 /// The drive for the given name in the given scope or null if494 /// the drive was not found.495 /// </returns>496 /// <exception cref="ArgumentNullException">497 /// If <paramref name="name"/> is null.498 /// </exception>499 /// <exception cref="ArgumentException">500 /// If <paramref name="scopeID"/> is less than zero, or not501 /// a number and not "script", "global", "local", or "private"502 /// </exception>503 /// <exception cref="ArgumentOutOfRangeException">504 /// If <paramref name="scopeID"/> is less than zero or greater than the number of currently505 /// active scopes.506 /// </exception>507 internal PSDriveInfo GetDrive(string name, string scopeID)508 {509 if (name == null)510 {511 throw PSTraceSource.NewArgumentNullException(nameof(name));512 }513 514 PSDriveInfo result = null;515 516 // The scope ID wasn't defined or wasn't recognizable517 // so do a search through the scopes looking for the518 // drive.519 520 if (string.IsNullOrEmpty(scopeID))521 {522 SessionStateScopeEnumerator scopeEnumerator =523 new SessionStateScopeEnumerator(CurrentScope);524 525 foreach (SessionStateScope scope in scopeEnumerator)526 {527 result = scope.GetDrive(name);528 529 if (result != null)530 {531 if (result.IsAutoMounted)532 {533 // Validate or remove the auto-mounted drive534 535 if (!ValidateOrRemoveAutoMountedDrive(result, scope))536 {537 result = null;538 }539 }540 541 if (result != null)542 {543 break;544 }545 }546 }547 548 if (result == null)549 {550 result = AutomountFileSystemDrive(name);551 }552 }553 else554 {555 SessionStateScope scope = GetScopeByID(scopeID);556 result = scope.GetDrive(name);557 558 if (result != null)559 {560 if (result.IsAutoMounted)561 {562 // Validate or remove the auto-mounted drive563 564 if (!ValidateOrRemoveAutoMountedDrive(result, scope))565 {566 result = null;567 }568 }569 }570 else571 {572 if (scope == GlobalScope)573 {574 result = AutomountFileSystemDrive(name);575 }576 }577 }578 579 return result;580 }581 582 private PSDriveInfo AutomountFileSystemDrive(string name)583 {584 PSDriveInfo result = null;585 586 // Check to see if it could be a "auto-mounted"587 // file system drive. If so, add the new drive588 // to the global scope and return it589 590 if (name.Length == 1)591 {592 try593 {594 System.IO.DriveInfo driveInfo = new System.IO.DriveInfo(name);595 result = AutomountFileSystemDrive(driveInfo);596 }597 catch (LoopFlowException)598 {599 throw;600 }601 catch (PipelineStoppedException)602 {603 throw;604 }605 catch (ActionPreferenceStopException)606 {607 throw;608 }609 catch (Exception)610 {611 // Catch all exceptions and continue since the drive does not exist612 // This action wasn't requested by the user and as such we don't613 // want to expose the user to any error conditions other than614 // DriveNotFoundException which will be thrown by the caller615 }616 }617 618 return result;619 }620 621 private PSDriveInfo AutomountFileSystemDrive(System.IO.DriveInfo systemDriveInfo)622 {623 PSDriveInfo result = null;624 625 if (!IsProviderLoaded(this.ExecutionContext.ProviderNames.FileSystem))626 {627 s_tracer.WriteLine("The {0} provider is not loaded", this.ExecutionContext.ProviderNames.FileSystem);628 return null;629 }630 631 // Since the drive does exist, add it.632 633 try634 {635 // Get the FS provider636 637 DriveCmdletProvider driveProvider =638 GetDriveProviderInstance(this.ExecutionContext.ProviderNames.FileSystem);639 640 if (driveProvider != null)641 {642 // Create a new drive643 string systemDriveName = systemDriveInfo.Name.Substring(0, 1);644 string volumeLabel = string.Empty;645 string displayRoot = null;646 647 try648 {649 // When run in an AppContainer, we may not have access to the volume label.650 volumeLabel = systemDriveInfo.VolumeLabel;651 }652 catch (UnauthorizedAccessException) { }653 654 // Get the actual root path for Network type drives655 if (systemDriveInfo.DriveType == DriveType.Network)656 {657 try658 {659 displayRoot = Microsoft.PowerShell.Commands.FileSystemProvider660 .GetRootPathForNetworkDriveOrDosDevice(systemDriveInfo);661 }662 // We want to get root path of the network drive as extra information to display to the user.663 // It's okay we failed to get the root path for some reason. We don't want to throw exception664 // here as it would break the current behavior.665 catch (Win32Exception) { }666 catch (InvalidOperationException) { }667 }668 669 PSDriveInfo newPSDriveInfo =670 new PSDriveInfo(671 systemDriveName,672 driveProvider.ProviderInfo,673 systemDriveInfo.RootDirectory.FullName,674 volumeLabel,675 null,676 displayRoot);677 678 newPSDriveInfo.IsAutoMounted = true;679 680 CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);681 682 newPSDriveInfo.DriveBeingCreated = true;683 684 // Validate the drive with the provider685 result = ValidateDriveWithProvider(driveProvider, newPSDriveInfo, context, false);686 687 newPSDriveInfo.DriveBeingCreated = false;688 689 if (result != null && !context.HasErrors())690 {691 // Create the drive in the global scope.692 GlobalScope.NewDrive(result);693 }694 }695 }696 catch (LoopFlowException)697 {698 throw;699 }700 catch (PipelineStoppedException)701 {702 throw;703 }704 catch (ActionPreferenceStopException)705 {706 throw;707 }708 catch (Exception e)709 {710 // Since the user isn't expecting this behavior, we don't711 // want to let errors find their way out. If there are any712 // failures we just don't mount the drive.713 714 MshLog.LogProviderHealthEvent(715 this.ExecutionContext,716 this.ExecutionContext.ProviderNames.FileSystem,717 e,718 Severity.Warning);719 }720 721 return result;722 }723 724 /// <summary>725 /// Auto-mounts a built-in drive.726 /// </summary>727 /// <remarks>728 /// Calls GetDrive(name, false) internally.729 /// </remarks>730 /// <param name="name">The name of the drive to load.</param>731 /// <returns></returns>732 internal PSDriveInfo AutomountBuiltInDrive(string name)733 {734 MountDefaultDrive(name, ExecutionContext);735 PSDriveInfo result = GetDrive(name, false);736 737 return result;738 }739 740 /// <summary>741 /// Automatically mount the specified drive.742 /// </summary>743 /// <remarks>744 /// Neither 'WSMan' nor 'Certificate' provider works in UNIX PS today.745 /// So this method currently does nothing on UNIX.746 /// </remarks>747 internal static void MountDefaultDrive(string name, ExecutionContext context)748 {749#if !UNIX750 PSModuleAutoLoadingPreference moduleAutoLoadingPreference =751 CommandDiscovery.GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference");752 if (moduleAutoLoadingPreference == PSModuleAutoLoadingPreference.None)753 {754 return;755 }756 757 string moduleName = null;758 759 // Note: For the certificate provider, we actually support the provider name as an alternative to760 // mount the default drive, since the provider names can be used for provider-qualified paths.761 // The WSMAN drive is the same as the provider name.762 if (763 string.Equals("Cert", name, StringComparison.OrdinalIgnoreCase) ||764 string.Equals("Certificate", name, StringComparison.OrdinalIgnoreCase)765 )766 {767 moduleName = "Microsoft.PowerShell.Security";768 }769 else if (string.Equals("WSMan", name, StringComparison.OrdinalIgnoreCase))770 {771 moduleName = "Microsoft.WSMan.Management";772 }773 774 if (!string.IsNullOrEmpty(moduleName))775 {776 s_tracer.WriteLine("Auto-mounting built-in drive: {0}", name);777 CommandInfo commandInfo = new CmdletInfo("Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand), null, null, context);778 Exception exception = null;779 s_tracer.WriteLine("Attempting to load module: {0}", moduleName);780 CommandDiscovery.AutoloadSpecifiedModule(moduleName, context, commandInfo.Visibility, out exception);781 if (exception != null)782 {783 // Call-out to user code, catch-all OK784 }785 }786#endif787 }788 789 /// <summary>790 /// Determines if the specified automounted drive still exists. If not,791 /// the drive is removed.792 /// </summary>793 /// <param name="drive">794 /// The drive to validate or remove.795 /// </param>796 /// <param name="scope">797 /// The scope the drive is in. This will be used to remove the drive798 /// if necessary.799 /// </param>800 /// <returns>801 /// True if the drive is still valid, false if the drive was removed.802 /// </returns>803 private bool ValidateOrRemoveAutoMountedDrive(PSDriveInfo drive, SessionStateScope scope)804 {805 bool result = true;806 try807 {808 System.IO.DriveInfo systemDriveInfo = new System.IO.DriveInfo(drive.Name);809 result = systemDriveInfo.DriveType != DriveType.NoRootDirectory;810 }811 catch (LoopFlowException)812 {813 throw;814 }815 catch (PipelineStoppedException)816 {817 throw;818 }819 catch (ActionPreferenceStopException)820 {821 throw;822 }823 catch (Exception)824 {825 // Assume any exception means the drive is no longer valid and needs826 // to be removed.827 828 result = false;829 }830 831 if (!result)832 {833 DriveCmdletProvider driveProvider = null;834 835 try836 {837 driveProvider =838 GetDriveProviderInstance(this.ExecutionContext.ProviderNames.FileSystem);839 }840 catch (NotSupportedException)841 {842 }843 catch (ProviderNotFoundException)844 {845 }846 847 if (driveProvider != null)848 {849 CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);850 851 try852 {853 // Give the provider a chance to cleanup854 driveProvider.RemoveDrive(drive, context);855 }856 // Ignore any exceptions the provider throws because we857 // are doing this without an explicit request from the858 // user. Since the provider can throw any exception859 // we must catch all exceptions here.860 catch (Exception)861 {862 }863 864 scope.RemoveDrive(drive);865 }866 }867 868 return result;869 }870 871 /// <summary>872 /// If a VHD is mounted to a drive prior to the PowerShell session being launched,873 /// then such a drive has to be validated for its existence before performing874 /// any operations on that drive to make sure that the drive is not unmounted.875 /// </summary>876 /// <param name="drive"></param>877 /// <returns>Absence of mounted drive for FileSystem provider or False for other provider types.</returns>878 private bool IsAStaleVhdMountedDrive(PSDriveInfo drive)879 {880 bool result = false;881 882 // check that drive's provider type is FileSystem883 if ((drive.Provider != null) && (!drive.Provider.NameEquals(this.ExecutionContext.ProviderNames.FileSystem)))884 {885 return false;886 }887 888 // A VHD mounted drive gets detected with a DriveType of DriveType.Fixed889 // when the VHD is mounted, however if the drive is unmounted, such a890 // stale drive is no longer valid and gets detected with DriveType.NoRootDirectory.891 // We would hit this situation in the following scenario:892 // 1. Launch Powershell session 'A' and mount the VHD.893 // 2. Launch different powershell session 'B'.894 // 3. Unmount the VHD in session 'A'.895 // The drive pointing to VHD in session 'B' gets detected as DriveType.NoRootDirectory896 // after the VHD is removed in session 'A'.897 if (drive != null && !string.IsNullOrEmpty(drive.Name) && drive.Name.Length == 1)898 {899 try900 {901 char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture);902 903 if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z')904 {905 DriveInfo systemDriveInfo = new DriveInfo(drive.Name);906 907 if (systemDriveInfo.DriveType == DriveType.NoRootDirectory)908 {909 if (!Directory.Exists(drive.Root))910 {911 result = true;912 }913 }914 }915 }916 catch (ArgumentException)917 {918 // At this point, We dont care if the drive is not a valid drive that does not host the VHD.919 }920 }921 922 return result;923 }924 925 /// <summary>926 /// Gets all the drives for a specific provider.927 /// </summary>928 /// <param name="providerId">929 /// The identifier for the provider to retrieve the drives for.930 /// </param>931 /// <returns>932 /// An IEnumerable that contains the drives for the specified provider.933 /// </returns>934 internal Collection<PSDriveInfo> GetDrivesForProvider(string providerId)935 {936 if (string.IsNullOrEmpty(providerId))937 {938 return Drives(null);939 }940 941 // Ensure that the provider name resolves to a single provider942 GetSingleProvider(providerId);943 944 Collection<PSDriveInfo> drives = new Collection<PSDriveInfo>();945 946 foreach (PSDriveInfo drive in Drives(null))947 {948 if (drive != null &&949 drive.Provider.NameEquals(providerId))950 {951 drives.Add(drive);952 }953 }954 955 return drives;956 }957 958 #endregion GetDrive959 960 #region RemoveDrive961 /// <summary>962 /// Removes the drive with the specified name.963 /// </summary>964 /// <param name="driveName">965 /// The name of the drive to remove.966 /// </param>967 /// <param name="force">968 /// Determines whether drive should be forcefully removed even if there was errors.969 /// </param>970 /// <param name="scopeID">971 /// The ID of the scope from which to remove the drive.972 /// If the scope ID is null or empty, the scope hierarchy will be searched973 /// starting at the current scope through all the parent scopes to the974 /// global scope until a drive of the given name is found to remove.975 /// </param>976 internal void RemoveDrive(string driveName, bool force, string scopeID)977 {978 if (driveName == null)979 {980 throw PSTraceSource.NewArgumentNullException(nameof(driveName));981 }982 983 PSDriveInfo drive = GetDrive(driveName, scopeID);984 985 if (drive == null)986 {987 DriveNotFoundException e = new DriveNotFoundException(988 driveName,989 "DriveNotFound",990 SessionStateStrings.DriveNotFound);991 throw e;992 }993 994 RemoveDrive(drive, force, scopeID);995 }996 997 /// <summary>998 /// Removes the drive with the specified name.999 /// </summary>1000 /// <param name="driveName">1001 /// The name of the drive to remove.1002 /// </param>1003 /// <param name="force">1004 /// Determines whether drive should be forcefully removed even if there was errors.1005 /// </param>1006 /// <param name="scopeID">1007 /// The ID of the scope from which to remove the drive.1008 /// If the scope ID is null or empty, the scope hierarchy will be searched1009 /// starting at the current scope through all the parent scopes to the1010 /// global scope until a drive of the given name is found to remove.1011 /// </param>1012 /// <param name="context">1013 /// The context of the command.1014 /// </param>1015 internal void RemoveDrive(1016 string driveName,1017 bool force,1018 string scopeID,1019 CmdletProviderContext context)1020 {1021 if (driveName == null)1022 {1023 throw PSTraceSource.NewArgumentNullException(nameof(driveName));1024 }1025 1026 Dbg.Diagnostics.Assert(1027 context != null,1028 "The caller should verify the context");1029 1030 PSDriveInfo drive = GetDrive(driveName, scopeID);1031 1032 if (drive == null)1033 {1034 DriveNotFoundException e = new DriveNotFoundException(1035 driveName,1036 "DriveNotFound",1037 SessionStateStrings.DriveNotFound);1038 context.WriteError(new ErrorRecord(e.ErrorRecord, e));1039 }1040 else1041 {1042 RemoveDrive(drive, force, scopeID, context);1043 }1044 }1045 1046 /// <summary>1047 /// Removes the specified drive.1048 /// </summary>1049 /// <param name="drive">1050 /// The drive to be removed.1051 /// </param>1052 /// <param name="force">1053 /// Determines whether drive should be forcefully removed even if there was errors.1054 /// </param>1055 /// <param name="scopeID">1056 /// The ID of the scope from which to remove the drive.1057 /// If the scope ID is null or empty, the scope hierarchy will be searched1058 /// starting at the current scope through all the parent scopes to the1059 /// global scope until a drive of the given name is found to remove.1060 /// </param>1061 internal void RemoveDrive(PSDriveInfo drive, bool force, string scopeID)1062 {1063 if (drive == null)1064 {1065 throw PSTraceSource.NewArgumentNullException(nameof(drive));1066 }1067 1068 CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);1069 1070 RemoveDrive(drive, force, scopeID, context);1071 1072 if (context.HasErrors() && !force)1073 {1074 context.ThrowFirstErrorOrDoNothing();1075 }1076 }1077 1078 /// <summary>1079 /// Removes the specified drive.1080 /// </summary>1081 /// <param name="drive">1082 /// The drive to be removed.1083 /// </param>1084 /// <param name="force">1085 /// Determines whether drive should be forcefully removed even if there was errors.1086 /// </param>1087 /// <param name="scopeID">1088 /// The ID of the scope from which to remove the drive.1089 /// If the scope ID is null or empty, the scope hierarchy will be searched1090 /// starting at the current scope through all the parent scopes to the1091 /// global scope until a drive of the given name is found to remove.1092 /// </param>1093 /// <param name="context">1094 /// The context which the core command is running.1095 /// </param>1096 /// <exception cref="ArgumentOutOfRangeException">1097 /// If <paramref name="scopeID"/> is less than zero or greater than the number of currently1098 /// active scopes.1099 /// </exception>1100 internal void RemoveDrive(1101 PSDriveInfo drive,1102 bool force,1103 string scopeID,1104 CmdletProviderContext context)1105 {1106 // Make sure that the CanRemoveDrive is called even if we are forcing1107 // the removal because we want the provider to have a chance to1108 // cleanup.1109 1110 bool canRemove = false;1111 1112 try1113 {1114 canRemove = CanRemoveDrive(drive, context);1115 }1116 catch (LoopFlowException)1117 {1118 throw;1119 }1120 catch (PipelineStoppedException)1121 {1122 throw;1123 }1124 catch (ActionPreferenceStopException)1125 {1126 throw;1127 }1128 catch (ProviderInvocationException)1129 {1130 if (!force)1131 {1132 throw;1133 }1134 }1135 1136 // Now remove the drive if there was no error or we are forcing the removal1137 1138 if (canRemove || force)1139 {1140 // The scope ID wasn't defined or wasn't recognizable1141 // so do a search through the scopes looking for the1142 // drive.1143 1144 if (string.IsNullOrEmpty(scopeID))1145 {1146 SessionStateScopeEnumerator scopeEnumerator =1147 new SessionStateScopeEnumerator(CurrentScope);1148 1149 foreach (SessionStateScope scope in scopeEnumerator)1150 {1151 try1152 {1153 PSDriveInfo result = scope.GetDrive(drive.Name);1154 if (result != null)1155 {1156 scope.RemoveDrive(drive);1157 1158 // If the drive is the current drive for the provider, remove1159 // it from the current drive list.1160 1161 if (ProvidersCurrentWorkingDrive[drive.Provider] == result)1162 {1163 ProvidersCurrentWorkingDrive[drive.Provider] = null;1164 }1165 1166 break;1167 }1168 }1169 catch (ArgumentException)1170 {1171 }1172 }1173 }1174 else1175 {1176 SessionStateScope scope = GetScopeByID(scopeID);1177 scope.RemoveDrive(drive);1178 1179 // If the drive is the current drive for the provider, remove1180 // it from the current drive list.1181 1182 if (ProvidersCurrentWorkingDrive[drive.Provider] == drive)1183 {1184 ProvidersCurrentWorkingDrive[drive.Provider] = null;1185 }1186 }1187 }1188 else1189 {1190 PSInvalidOperationException e =1191 (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException(1192 SessionStateStrings.DriveRemovalPreventedByProvider,1193 drive.Name,1194 drive.Provider);1195 1196 context.WriteError(1197 new ErrorRecord(1198 e.ErrorRecord,1199 e));1200 }