MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#pragma warning disable 1634, 16915 6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.IO;9using System.Management.Automation.Internal;10using System.Management.Automation.Provider;11using Dbg = System.Management.Automation;12 13namespace System.Management.Automation14{15 /// <summary>16 /// Holds the state of a Monad Shell session.17 /// </summary>18 internal sealed partial class SessionStateInternal19 {20 #region Current working directory/drive21 22 /// <summary>23 /// Gets the current monad namespace specific working location. If24 /// you want to change the current working directory use the SetLocation25 /// method.26 /// </summary>27 /// <exception cref="InvalidOperationException">28 /// If a location has not been set yet.29 /// </exception>30 internal PathInfo CurrentLocation31 {32 get33 {34 if (CurrentDrive == null)35 {36 // We need the error handling, and moving to a method would be37 // a breaking change38#pragma warning suppress 5650339 throw PSTraceSource.NewInvalidOperationException();40 }41 42 PathInfo result =43 new PathInfo(44 CurrentDrive,45 CurrentDrive.Provider,46 CurrentDrive.CurrentLocation,47 new SessionState(this));48 49 return result;50 }51 }52 53 /// <summary>54 /// Gets the namespace specific path of the current working directory55 /// for the specified namespace.56 /// </summary>57 /// <param name="namespaceID">58 /// An identifier that uniquely identifies the namespace to get the59 /// current working directory for.60 /// </param>61 /// <returns>62 /// The namespace specific path of the current working directory for63 /// the specified namespace.64 /// </returns>65 /// <exception cref="ArgumentNullException">66 /// If <paramref name="namespaceID"/> is null.67 /// </exception>68 /// <exception cref="ProviderNotFoundException">69 /// If <paramref name="namespacesID"/> refers to a provider that does not exist.70 /// </exception>71 /// <exception cref="DriveNotFoundException">72 /// If a current drive cannot be found for the provider <paramref name="namespaceID"/>73 /// </exception>74 internal PathInfo GetNamespaceCurrentLocation(string namespaceID)75 {76 if (namespaceID == null)77 {78 throw PSTraceSource.NewArgumentNullException(nameof(namespaceID));79 }80 81 // If namespace ID is empty, we will use the current working drive82 PSDriveInfo drive = null;83 84 if (namespaceID.Length == 0)85 {86 ProvidersCurrentWorkingDrive.TryGetValue(CurrentDrive.Provider, out drive);87 }88 else89 {90 // First check to see if the provider exists91 ProvidersCurrentWorkingDrive.TryGetValue(GetSingleProvider(namespaceID), out drive);92 }93 94 if (drive == null)95 {96 DriveNotFoundException e =97 new DriveNotFoundException(98 namespaceID,99 "DriveNotFound",100 SessionStateStrings.DriveNotFound);101 throw e;102 }103 104 CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext);105 context.Drive = drive;106 107 // Now make the namespace specific path108 string path = null;109 110 if (drive.Hidden)111 {112 if (LocationGlobber.IsProviderDirectPath(drive.CurrentLocation))113 {114 path = drive.CurrentLocation;115 }116 else117 {118 path = LocationGlobber.GetProviderQualifiedPath(drive.CurrentLocation, drive.Provider);119 }120 }121 else122 {123 path = LocationGlobber.GetDriveQualifiedPath(drive.CurrentLocation, drive);124 }125 126 return new PathInfo(drive, drive.Provider, path, new SessionState(this));127 }128 129 /// <summary>130 /// Changes the current working directory to the path specified.131 /// </summary>132 /// <param name="path">133 /// The path of the new current working directory.134 /// </param>135 /// <returns>136 /// The PathInfo object representing the path of the location137 /// that was set.138 /// </returns>139 /// <exception cref="ArgumentNullException">140 /// If <paramref name="path"/> is null.141 /// </exception>142 /// <exception cref="ArgumentException">143 /// If <paramref name="path"/> does not exist, is not a container, or144 /// resolved to multiple containers.145 /// </exception>146 /// <exception cref="ProviderNotFoundException">147 /// If <paramref name="path"/> refers to a provider that does not exist.148 /// </exception>149 /// <exception cref="DriveNotFoundException">150 /// If <paramref name="path"/> refers to a drive that does not exist.151 /// </exception>152 /// <exception cref="ProviderInvocationException">153 /// If the provider associated with <paramref name="path"/> threw an154 /// exception.155 /// </exception>156 internal PathInfo SetLocation(string path)157 {158 return SetLocation(path, null);159 }160 161 /// <summary>162 /// Changes the current working directory to the path specified.163 /// </summary>164 /// <param name="path">165 /// The path of the new current working directory166 /// </param>167 /// <param name="context">168 /// The context the provider uses when performing the operation.169 /// </param>170 /// <returns>171 /// The PathInfo object representing the path of the location172 /// that was set.173 /// </returns>174 /// <exception cref="ArgumentNullException">175 /// If <paramref name="path"/> is null.176 /// </exception>177 /// <exception cref="ArgumentException">178 /// If <paramref name="path"/> does not exist, is not a container, or179 /// resolved to multiple containers.180 /// </exception>181 /// <exception cref="ProviderNotFoundException">182 /// If <paramref name="path"/> refers to a provider that does not exist.183 /// </exception>184 /// <exception cref="DriveNotFoundException">185 /// If <paramref name="path"/> refers to a drive that does not exist.186 /// </exception>187 /// <exception cref="ProviderInvocationException">188 /// If the provider associated with <paramref name="path"/> threw an189 /// exception.190 /// </exception>191 /// <exception cref="ItemNotFoundException">192 /// If the <paramref name="path"/> could not be resolved.193 /// </exception>194 internal PathInfo SetLocation(string path, CmdletProviderContext context)195 {196 return SetLocation(path, context, literalPath: false);197 }198 199 /// <summary>200 /// Changes the current working directory to the path specified.201 /// </summary>202 /// <param name="path">203 /// The path of the new current working directory.204 /// </param>205 /// <param name="context">206 /// The context the provider uses when performing the operation.207 /// </param>208 /// <param name="literalPath">209 /// Indicate if the path is a literal path.210 /// </param>211 /// <returns>212 /// The PathInfo object representing the path of the location213 /// that was set.214 /// </returns>215 /// <exception cref="ArgumentNullException">216 /// If <paramref name="path"/> is null.217 /// </exception>218 /// <exception cref="ArgumentException">219 /// If <paramref name="path"/> does not exist, is not a container, or220 /// resolved to multiple containers.221 /// </exception>222 /// <exception cref="ProviderNotFoundException">223 /// If <paramref name="path"/> refers to a provider that does not exist.224 /// </exception>225 /// <exception cref="DriveNotFoundException">226 /// If <paramref name="path"/> refers to a drive that does not exist.227 /// </exception>228 /// <exception cref="ProviderInvocationException">229 /// If the provider associated with <paramref name="path"/> threw an230 /// exception.231 /// </exception>232 /// <exception cref="ItemNotFoundException">233 /// If the <paramref name="path"/> could not be resolved.234 /// </exception>235 internal PathInfo SetLocation(string path, CmdletProviderContext context, bool literalPath)236 {237 if (path == null)238 {239 throw PSTraceSource.NewArgumentNullException(nameof(path));240 }241 242 PathInfo current = CurrentLocation;243 string originalPath = path;244 string driveName = null;245 ProviderInfo provider = null;246 string providerId = null;247 248 switch (originalPath)249 {250 case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("-", StringComparison.Ordinal):251 if (_setLocationHistory.UndoCount <= 0)252 {253 throw new InvalidOperationException(SessionStateStrings.LocationUndoStackIsEmpty);254 }255 256 path = _setLocationHistory.Undo(this.CurrentLocation).Path;257 break;258 case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("+", StringComparison.Ordinal):259 if (_setLocationHistory.RedoCount <= 0)260 {261 throw new InvalidOperationException(SessionStateStrings.LocationRedoStackIsEmpty);262 }263 264 path = _setLocationHistory.Redo(this.CurrentLocation).Path;265 break;266 default:267 var pushPathInfo = GetNewPushPathInfo();268 _setLocationHistory.Push(pushPathInfo);269 break;270 }271 272 PSDriveInfo previousWorkingDrive = CurrentDrive;273 274 // First check to see if the path is a home path275 if (LocationGlobber.IsHomePath(path))276 {277 path = Globber.GetHomeRelativePath(path);278 }279 280 if (LocationGlobber.IsProviderDirectPath(path))281 {282 // The path is a provider-direct path so use the current283 // provider and its hidden drive but don't modify the path284 // at all.285 provider = CurrentLocation.Provider;286 CurrentDrive = provider.HiddenDrive;287 }288 else if (LocationGlobber.IsProviderQualifiedPath(path, out providerId))289 {290 provider = GetSingleProvider(providerId);291 CurrentDrive = provider.HiddenDrive;292 }293 else294 {295 // See if the path is a relative or absolute296 // path.297 if (Globber.IsAbsolutePath(path, out driveName))298 {299 // Since the path is an absolute path300 // we need to change the current working301 // drive302 PSDriveInfo newWorkingDrive = GetDrive(driveName);303 CurrentDrive = newWorkingDrive;304 305 // If the path is simply a colon-terminated drive,306 // not a slash-terminated path to the root of a drive,307 // set the path to the current working directory of that drive.308 string colonTerminatedVolume = CurrentDrive.Name + ':';309 if (CurrentDrive.VolumeSeparatedByColon && (path.Length == colonTerminatedVolume.Length))310 {311 path = Path.Combine(colonTerminatedVolume + Path.DirectorySeparatorChar, CurrentDrive.CurrentLocation);312 }313 314 // Now that the current working drive is set,315 // process the rest of the path as a relative path.316 }317 }318 319 context ??= new CmdletProviderContext(this.ExecutionContext);320 321 if (CurrentDrive != null)322 {323 context.Drive = CurrentDrive;324 }325 326 CmdletProvider providerInstance = null;327 328 Collection<PathInfo> workingPath = null;329 330 try331 {332 workingPath =333 Globber.GetGlobbedMonadPathsFromMonadPath(334 path,335 false,336 context,337 out providerInstance);338 }339 catch (LoopFlowException)340 {341 throw;342 }343 catch (PipelineStoppedException)344 {345 throw;346 }347 catch (ActionPreferenceStopException)348 {349 throw;350 }351 catch (Exception)352 {353 // Reset the drive to the previous drive and354 // then rethrow the error355 CurrentDrive = previousWorkingDrive;356 throw;357 }358 359 if (workingPath.Count == 0)360 {361 // Set the current working drive back to the previous362 // one in case it was changed.363 CurrentDrive = previousWorkingDrive;364 365 throw366 new ItemNotFoundException(367 path,368 "PathNotFound",369 SessionStateStrings.PathNotFound);370 }371 372 // We allow globbing the location as long as it only resolves a single container.373 bool foundContainer = false;374 bool pathIsContainer = false;375 bool pathIsProviderQualifiedPath = false;376 bool currentPathisProviderQualifiedPath = false;377 378 for (int index = 0; index < workingPath.Count; ++index)379 {380 CmdletProviderContext normalizePathContext =381 new CmdletProviderContext(context);382 383 PathInfo resolvedPath = workingPath[index];384 string currentPath = path;385 try386 {387 string providerName = null;388 currentPathisProviderQualifiedPath = LocationGlobber.IsProviderQualifiedPath(resolvedPath.Path, out providerName);389 if (currentPathisProviderQualifiedPath)390 {391 // The path should be the provider-qualified path without the provider ID392 // or ::393 string providerInternalPath = LocationGlobber.RemoveProviderQualifier(resolvedPath.Path);394 395 try396 {397 currentPath = NormalizeRelativePath(GetSingleProvider(providerName), providerInternalPath, string.Empty, normalizePathContext);398 }399 catch (NotSupportedException)400 {401 // Since the provider does not support normalizing the path, just402 // use the path we currently have.403 }404 catch (LoopFlowException)405 {406 throw;407 }408 catch (PipelineStoppedException)409 {410 throw;411 }412 catch (ActionPreferenceStopException)413 {414 throw;415 }416 catch (Exception)417 {418 // Reset the drive to the previous drive and419 // then rethrow the error420 CurrentDrive = previousWorkingDrive;421 throw;422 }423 }424 else425 {426 try427 {428 currentPath = NormalizeRelativePath(resolvedPath.Path, CurrentDrive.Root, normalizePathContext);429 }430 catch (NotSupportedException)431 {432 // Since the provider does not support normalizing the path, just433 // use the path we currently have.434 }435 catch (LoopFlowException)436 {437 throw;438 }439 catch (PipelineStoppedException)440 {441 throw;442 }443 catch (ActionPreferenceStopException)444 {445 throw;446 }447 catch (Exception)448 {449 // Reset the drive to the previous drive and450 // then rethrow the error451 CurrentDrive = previousWorkingDrive;452 throw;453 }454 }455 456 // Now see if there was errors while normalizing the path457 if (normalizePathContext.HasErrors())458 {459 // Set the current working drive back to the previous460 // one in case it was changed.461 CurrentDrive = previousWorkingDrive;462 463 normalizePathContext.ThrowFirstErrorOrDoNothing();464 }465 }466 finally467 {468 normalizePathContext.RemoveStopReferral();469 }470 471 bool isContainer = false;472 473 CmdletProviderContext itemContainerContext =474 new CmdletProviderContext(context);475 itemContainerContext.SuppressWildcardExpansion = true;476 477 try478 {479 isContainer =480 IsItemContainer(481 resolvedPath.Path,482 itemContainerContext);483 484 if (itemContainerContext.HasErrors())485 {486 // Set the current working drive back to the previous487 // one in case it was changed.488 CurrentDrive = previousWorkingDrive;489 490 itemContainerContext.ThrowFirstErrorOrDoNothing();491 }492 }493 catch (NotSupportedException)494 {495 if (currentPath.Length == 0)496 {497 // Treat this as a container because providers that only498 // support the ContainerCmdletProvider interface are really499 // containers at their root.500 isContainer = true;501 }502 }503 finally504 {505 itemContainerContext.RemoveStopReferral();506 }507 508 if (isContainer)509 {510 if (foundContainer)511 {512 // The path resolved to more than one container513 // Set the current working drive back to the previous514 // one in case it was changed.515 CurrentDrive = previousWorkingDrive;516 517 throw518 PSTraceSource.NewArgumentException(519 nameof(path),520 SessionStateStrings.PathResolvedToMultiple,521 originalPath);522 }523 else524 {525 // Set the path to use526 path = currentPath;527 528 // Mark it as a container529 pathIsContainer = true;530 531 // Mark whether or not it was provider-qualified532 pathIsProviderQualifiedPath = currentPathisProviderQualifiedPath;533 534 // Mark that we have already found one container. Finding additional535 // should be an error536 foundContainer = true;537 }538 }539 }540 541 if (pathIsContainer)542 {543 // Remove the root slash since it is implied that the544 // current working directory is relative to the root.545 if (!LocationGlobber.IsProviderDirectPath(path) &&546 path.StartsWith(StringLiterals.DefaultPathSeparator) &&547 !pathIsProviderQualifiedPath)548 {549 path = path.Substring(1);550 }551 552 s_tracer.WriteLine(553 "New working path = {0}",554 path);555 556 CurrentDrive.CurrentLocation = path;557 }558 else559 {560 // Set the current working drive back to the previous561 // one in case it was changed.562 CurrentDrive = previousWorkingDrive;563 564 throw565 new ItemNotFoundException(566 originalPath,567 "PathNotFound",568 SessionStateStrings.PathNotFound);569 }570 571 // Now make sure the current drive is set in the provider's572 // current working drive hashtable573 ProvidersCurrentWorkingDrive[CurrentDrive.Provider] =574 CurrentDrive;575 576 // Set the $PWD variable to the new location577 this.SetVariable(SpecialVariables.PWDVarPath, this.CurrentLocation, false, true, CommandOrigin.Internal);578 579 // If an action has been defined for location changes, invoke it now.580 if (PublicSessionState.InvokeCommand.LocationChangedAction != null)581 {582 var eventArgs = new LocationChangedEventArgs(PublicSessionState, current, CurrentLocation);583 PublicSessionState.InvokeCommand.LocationChangedAction.Invoke(ExecutionContext.CurrentRunspace, eventArgs);584 s_tracer.WriteLine("Invoked LocationChangedAction");585 }586 587 return this.CurrentLocation;588 }589 590 /// <summary>591 /// Determines if the specified path is the current working directory592 /// or a parent of the current working directory.593 /// </summary>594 /// <param name="path">595 /// A monad namespace absolute or relative path.596 /// </param>597 /// <param name="context">598 /// The context the provider uses when performing the operation.599 /// </param>600 /// <returns>601 /// true, if the path is the current working directory or a parent of the current602 /// working directory. false, otherwise.603 /// </returns>604 /// <exception cref="ArgumentNullException">605 /// If <paramref name="path"/> is null.606 /// </exception>607 /// <exception cref="ProviderNotFoundException">608 /// If the path is a provider-qualified path for a provider that is609 /// not loaded into the system.610 /// </exception>611 /// <exception cref="DriveNotFoundException">612 /// If the <paramref name="path"/> refers to a drive that could not be found.613 /// </exception>614 /// <exception cref="ProviderInvocationException">615 /// If the provider used to build the path threw an exception.616 /// </exception>617 /// <exception cref="NotSupportedException">618 /// If the provider that the <paramref name="path"/> represents is not a NavigationCmdletProvider619 /// or ContainerCmdletProvider.620 /// </exception>621 /// <exception cref="InvalidOperationException">622 /// If the <paramref name="path"/> starts with "~" and the home location is not set for623 /// the provider.624 /// </exception>625 /// <exception cref="ProviderInvocationException">626 /// If the provider specified by <paramref name="providerId"/> threw an627 /// exception when its GetParentPath or MakePath was called while628 /// processing the <paramref name="path"/>.629 /// </exception>630 internal bool IsCurrentLocationOrAncestor(string path, CmdletProviderContext context)631 {632 bool result = false;633 634 if (path == null)635 {636 throw PSTraceSource.NewArgumentNullException(nameof(path));637 }638 639 PSDriveInfo drive = null;640 ProviderInfo provider = null;641 642 string providerSpecificPath =643 Globber.GetProviderPath(644 path,645 context,646 out provider,647 out drive);648 649 if (drive != null)650 {651 s_tracer.WriteLine("Tracing drive");652 drive.Trace();653 }654 655 Dbg.Diagnostics.Assert(656 providerSpecificPath != null,657 "There should always be a way to generate a provider path for a " +658 "given path");659 660 if (drive != null)661 {662 context.Drive = drive;663 }664 665 // Check to see if the path that was specified is within the current666 // working drive667 if (drive == CurrentDrive)668 {669 // The path needs to be normalized to get rid of relative path tokens670 // so they don't interfere with our path comparisons below671 CmdletProviderContext normalizePathContext672 = new CmdletProviderContext(context);673 674 try675 {676 providerSpecificPath = NormalizeRelativePath(path, null, normalizePathContext);677 }678 catch (NotSupportedException)679 {680 // Since the provider does not support normalizing the path, just681 // use the path we currently have.682 }683 catch (LoopFlowException)684 {685 throw;686 }687 catch (PipelineStoppedException)688 {689 throw;690 }691 catch (ActionPreferenceStopException)692 {693 throw;694 }695 finally696 {697 normalizePathContext.RemoveStopReferral();698 }699 700 if (normalizePathContext.HasErrors())701 {702 normalizePathContext.ThrowFirstErrorOrDoNothing();703 }704 705 s_tracer.WriteLine("Provider path = {0}", providerSpecificPath);706 707 // Get the current working directory provider specific path708 PSDriveInfo currentWorkingDrive = null;709 ProviderInfo currentDriveProvider = null;710 711 string currentWorkingPath =712 Globber.GetProviderPath(713 ".",714 context,715 out currentDriveProvider,716 out currentWorkingDrive);717 718 Dbg.Diagnostics.Assert(719 currentWorkingDrive == CurrentDrive,720 "The current working drive should be the CurrentDrive.");721 722 s_tracer.WriteLine(723 "Current working path = {0}",724 currentWorkingPath);725 726 // See if the path is the current working directory or a parent727 // of the current working directory728 s_tracer.WriteLine(729 "Comparing {0} to {1}",730 providerSpecificPath,731 currentWorkingPath);732 733 if (string.Equals(providerSpecificPath, currentWorkingPath, StringComparison.OrdinalIgnoreCase))734 {735 // The path is the current working directory so736 // return true737 s_tracer.WriteLine("The path is the current working directory");738 739 result = true;740 }741 else742 {743 // Check to see if the specified path is a parent744 // of the current working directory745 string lockedDirectory = currentWorkingPath;746 747 while (lockedDirectory.Length > 0)748 {749 // We need to allow the provider to go as far up the tree750 // as it can even if that means it has to traverse higher751 // than the mount point for this drive. That is752 // why we are passing the empty string as the root here.753 lockedDirectory =754 GetParentPath(755 drive.Provider,756 lockedDirectory,757 string.Empty,758 context);759 760 s_tracer.WriteLine(761 "Comparing {0} to {1}",762 lockedDirectory,763 providerSpecificPath);764 765 if (string.Equals(lockedDirectory, providerSpecificPath, StringComparison.OrdinalIgnoreCase))766 {767 // The path is a parent of the current working768 // directory769 s_tracer.WriteLine(770 "The path is a parent of the current working directory: {0}",771 lockedDirectory);772 773 result = true;774 break;775 }776 }777 }778 }779 else780 {781 s_tracer.WriteLine("Drives are not the same");782 }783 784 return result;785 }786 787 #endregion Current working directory/drive788 789 #region push-Pop current working directory790 791 /// <summary>792 /// Location history for Set-Location that supports Undo/Redo using bounded stacks.793 /// </summary>794 private readonly HistoryStack<PathInfo> _setLocationHistory;795 796 /// <summary>797 /// A stack of the most recently pushed locations.798 /// </summary>799 private readonly Dictionary<string, Stack<PathInfo>> _workingLocationStack;800 801 private const string startingDefaultStackName = "default";802 /// <summary>803 /// The name of the default location stack.804 /// </summary>805 private string _defaultStackName = startingDefaultStackName;806 807 /// <summary>808 /// Pushes the current location onto the working809 /// location stack so that it can be retrieved later.810 /// </summary>811 /// <param name="stackName">812 /// The ID of the stack to push the location on. If813 /// it is null or empty the default stack is used.814 /// </param>815 internal void PushCurrentLocation(string stackName)816 {817 if (string.IsNullOrEmpty(stackName))818 {819 stackName = _defaultStackName;820 }821 822 // Get the location stack from the hashtable823 Stack<PathInfo> locationStack = null;824 825 if (!_workingLocationStack.TryGetValue(stackName, out locationStack))826 {827 locationStack = new Stack<PathInfo>();828 _workingLocationStack[stackName] = locationStack;829 }830 831 // Push the directory/drive pair onto the stack832 var pushPathInfo = GetNewPushPathInfo();833 locationStack.Push(pushPathInfo);834 }835 836 private PathInfo GetNewPushPathInfo()837 {838 // Create a new instance of the directory/drive pair839 ProviderInfo provider = CurrentDrive.Provider;840 string mshQualifiedPath =841 LocationGlobber.GetMshQualifiedPath(CurrentDrive.CurrentLocation, CurrentDrive);842 843 PathInfo newPushLocation =844 new PathInfo(845 CurrentDrive,846 provider,847 mshQualifiedPath,848 new SessionState(this));849 850 s_tracer.WriteLine(851 "Pushing drive: {0} directory: {1}",852 CurrentDrive.Name,853 mshQualifiedPath);854 855 return newPushLocation;856 }857 858 /// <summary>859 /// Resets the current working drive and directory to the first860 /// entry on the working directory stack and removes that entry861 /// from the stack.862 /// </summary>863 /// <param name="stackName">864 /// The ID of the stack to pop the location from. If it is null or865 /// empty the default stack is used.866 /// </param>867 /// <returns>868 /// A PathInfo object representing the location that was popped869 /// from the location stack and set as the new location.870 /// </returns>871 /// <exception cref="ArgumentException">872 /// If the path on the stack does not exist, is not a container, or873 /// resolved to multiple containers.874 /// or875 /// If <paramref name="stackName"/> contains wildcard characters and resolves876 /// to multiple location stacks.877 /// or878 /// A stack was not found with the specified name.879 /// </exception>880 /// <exception cref="ProviderNotFoundException">881 /// If the path on the stack refers to a provider that does not exist.882 /// </exception>883 /// <exception cref="DriveNotFoundException">884 /// If the path on the stack refers to a drive that does not exist.885 /// </exception>886 /// <exception cref="ProviderInvocationException">887 /// If the provider associated with the path on the stack threw an888 /// exception.889 /// </exception>890 internal PathInfo PopLocation(string stackName)891 {892 if (string.IsNullOrEmpty(stackName))893 {894 stackName = _defaultStackName;895 }896 897 if (WildcardPattern.ContainsWildcardCharacters(stackName))898 {899 // Need to glob the stack name, but it can only glob to a single.900 bool haveMatch = false;901 902 WildcardPattern stackNamePattern =903 WildcardPattern.Get(stackName, WildcardOptions.IgnoreCase);904 905 foreach (string key in _workingLocationStack.Keys)906 {907 if (stackNamePattern.IsMatch(key))908 {909 if (haveMatch)910 {911 throw912 PSTraceSource.NewArgumentException(913 nameof(stackName),914 SessionStateStrings.StackNameResolvedToMultiple,915 stackName);916 }917 918 haveMatch = true;919 stackName = key;920 }921 }922 }923 924 PathInfo result = CurrentLocation;925 926 try927 {928 Stack<PathInfo> locationStack = null;929 if (!_workingLocationStack.TryGetValue(stackName, out locationStack))930 {931 if (!string.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase))932 {933 throw934 PSTraceSource.NewArgumentException(935 nameof(stackName),936 SessionStateStrings.StackNotFound,937 stackName);938 }939 940 return null;941 }942 943 PathInfo poppedWorkingDirectory = locationStack.Pop();944 945 Dbg.Diagnostics.Assert(946 poppedWorkingDirectory != null,947 "All items in the workingLocationStack should be " +948 "of type PathInfo");949 950 string newPath =951 LocationGlobber.GetMshQualifiedPath(952 WildcardPattern.Escape(poppedWorkingDirectory.Path),953 poppedWorkingDirectory.GetDrive());954 955 result = SetLocation(newPath);956 957 if (locationStack.Count == 0 &&958 !string.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase))959 {960 // Remove the stack from the stack list if it961 // no longer contains any paths.962 _workingLocationStack.Remove(stackName);963 }964 }965 catch (InvalidOperationException)966 {967 // This is a no-op. We stay with the current working968 // directory.969 }970 971 return result;972 }973 974 /// <summary>975 /// Gets the monad namespace paths for all the directories that are976 /// pushed on the working directory stack.977 /// </summary>978 /// <param name="stackName">979 /// The stack of the ID of the location stack to retrieve. If it is980 /// null or empty the default stack is used.981 /// </param>982 /// <returns>983 /// The PathInfoStack representing the location stack for the specified984 /// stack ID.985 /// </returns>986 /// <exception cref="ArgumentException">987 /// If no location stack <paramref name="stackName"/> exists except if988 /// the default stack is requested.989 /// </exception>990 internal PathInfoStack LocationStack(string stackName)991 {992 if (string.IsNullOrEmpty(stackName))993 {994 stackName = _defaultStackName;995 }996 997 Stack<PathInfo> locationStack = null;998 999 if (!_workingLocationStack.TryGetValue(stackName, out locationStack))1000 {1001 // If the request was for the default stack, but it doesn't1002 // yet exist, create a dummy stack and return it.1003 if (string.Equals(1004 stackName,1005 startingDefaultStackName,1006 StringComparison.OrdinalIgnoreCase))1007 {1008 locationStack = new Stack<PathInfo>();1009 }1010 else1011 {1012 throw PSTraceSource.NewArgumentException(nameof(stackName));1013 }1014 }1015 1016 PathInfoStack result = new PathInfoStack(stackName, locationStack);1017 1018 return result;1019 }1020 1021 /// <summary>1022 /// Sets the default stack ID to the specified stack ID.1023 /// </summary>1024 /// <param name="stackName">1025 /// The stack ID to be used as the default.1026 /// </param>1027 /// <returns>1028 /// The PathInfoStack for the new default stack or null if the1029 /// stack does not exist yet.1030 /// </returns>1031 /// <exception cref="ItemNotFoundException">1032 /// If <paramref name="stackName"/> does not exist as a location stack.1033 /// </exception>1034 internal PathInfoStack SetDefaultLocationStack(string stackName)1035 {1036 if (string.IsNullOrEmpty(stackName))1037 {1038 stackName = startingDefaultStackName;1039 }1040 1041 if (!_workingLocationStack.ContainsKey(stackName))1042 {1043 if (string.Equals(stackName, startingDefaultStackName, StringComparison.OrdinalIgnoreCase))1044 {1045 // Since the "default" stack must always exist, create it here1046 return new PathInfoStack(startingDefaultStackName, new Stack<PathInfo>());1047 }1048 1049 ItemNotFoundException itemNotFound =1050 new ItemNotFoundException(1051 stackName,1052 "StackNotFound",1053 SessionStateStrings.PathNotFound);1054 1055 throw itemNotFound;1056 }1057 1058 _defaultStackName = stackName;1059 1060 Stack<PathInfo> locationStack = _workingLocationStack[_defaultStackName];1061 1062 if (locationStack != null)1063 {1064 return new PathInfoStack(_defaultStackName, locationStack);1065 }1066 1067 return null;1068 }1069 1070 #endregion push-Pop current working directory1071 }1072 1073 /// <summary>1074 /// Event argument for the LocationChangedAction containing1075 /// information about the old location we were in and the new1076 /// location we changed to.1077 /// </summary>1078 public class LocationChangedEventArgs : EventArgs1079 {1080 /// <summary>1081 /// Initializes a new instance of the LocationChangedEventArgs class.1082 /// </summary>1083 /// <param name="sessionState">1084 /// The public session state instance associated with this runspace.1085 /// </param>1086 /// <param name="oldPath">1087 /// The path we changed locations from.1088 /// </param>1089 /// <param name="newPath">1090 /// The path we change locations to.1091 /// </param>1092 internal LocationChangedEventArgs(SessionState sessionState, PathInfo oldPath, PathInfo newPath)1093 {1094 SessionState = sessionState;1095 OldPath = oldPath;1096 NewPath = newPath;1097 }1098 1099 /// <summary>1100 /// Gets the path we changed location from.1101 /// </summary>1102 public PathInfo OldPath { get; internal set; }1103 1104 /// <summary>1105 /// Gets the path we changed location to.1106 /// </summary>1107 public PathInfo NewPath { get; internal set; }1108 1109 /// <summary>1110 /// Gets the session state instance for the current runspace.1111 /// </summary>1112 public SessionState SessionState { get; internal set; }1113 }1114}1115 