Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
SessionStateLocationAPIs.cs1115 linesDownload Raw Back to engine
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