Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
CommandPathSearch.cs597 linesDownload Raw Back to engine
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#nullable enable5 6using System.Collections;7using System.Collections.Generic;8using System.Collections.ObjectModel;9using System.IO;10using System.Linq;11 12namespace System.Management.Automation13{14    /// <summary>15    /// Used to enumerate the commands on the system that match the specified16    /// command name.17    /// </summary>18    internal class CommandPathSearch : IEnumerable<string>, IEnumerator<string>19    {20        [TraceSource("CommandSearch", "CommandSearch")]21        private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("CommandSearch", "CommandSearch");22 23        /// <summary>24        /// Constructs a command searching enumerator that resolves the location25        /// of a command using the PATH environment variable.26        /// </summary>27        /// <param name="commandName">28        /// The command name to search for in the path.29        /// </param>30        /// <param name="lookupPaths">31        /// The paths to directories in which to lookup the command.32        /// Ex.null: paths from PATH environment variable.33        /// </param>34        /// <param name="context">35        /// The execution context for the current engine instance.36        /// </param>37        /// <param name="acceptableCommandNames">38        /// The patterns to search for in the paths.39        /// </param>40        /// <param name="fuzzyMatcher">41        /// The fuzzy matcher to use for fuzzy searching.42        /// </param>43        internal CommandPathSearch(44            string commandName,45            LookupPathCollection lookupPaths,46            ExecutionContext context,47            Collection<string>? acceptableCommandNames,48            FuzzyMatcher? fuzzyMatcher)49        {50            _fuzzyMatcher = fuzzyMatcher;51            string[] commandPatterns;52            if (acceptableCommandNames != null)53            {54                // The name passed in is not a pattern. To minimize enumerating the file system, we55                // turn the command name into a pattern and then match against extensions in PATHEXT.56                // The old code would enumerate the file system many more times, once per possible extension.57                if (Platform.IsWindows)58                {59                    commandPatterns = new[] { commandName + ".*" };60                }61                else62                {63                    // Porting note: on non-Windows platforms, we want to always allow just 'commandName'64                    // as an acceptable command name. However, we also want to allow commands to be65                    // called with the .ps1 extension, so that 'script.ps1' can be called by 'script'.66                    commandPatterns = new[] { commandName, commandName + ".ps1" };67                }68 69                _postProcessEnumeratedFiles = CheckAgainstAcceptableCommandNames;70                _acceptableCommandNames = acceptableCommandNames;71            }72            else73            {74                commandPatterns = new[] { commandName };75                _postProcessEnumeratedFiles = JustCheckExtensions;76            }77 78            // Note, discovery must be set before resolving the current directory79            _context = context;80            _patterns = commandPatterns;81            _lookupPaths = lookupPaths;82            ResolveCurrentDirectoryInLookupPaths();83 84            _orderedPathExt = CommandDiscovery.PathExtensionsWithPs1Prepended;85 86            // The same as in this.Reset()87            _lookupPathsEnumerator = _lookupPaths.GetEnumerator();88            _patternEnumerator = _patterns.GetEnumerator();89            _currentDirectoryResults = Array.Empty<string>();90            _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator();91            _justReset = true;92        }93 94        /// <summary>95        /// Ensures that all the paths in the lookupPaths member are absolute96        /// file system paths.97        /// </summary>98        private void ResolveCurrentDirectoryInLookupPaths()99        {100            var indexesToRemove = new SortedDictionary<int, int>();101            int removalListCount = 0;102 103            string fileSystemProviderName = _context.ProviderNames.FileSystem;104 105            SessionStateInternal sessionState = _context.EngineSessionState;106 107            // Only use the directory if it gets resolved by the FileSystemProvider108            bool isCurrentDriveValid =109                sessionState.CurrentDrive != null &&110                sessionState.CurrentDrive.Provider.NameEquals(fileSystemProviderName) &&111                sessionState.IsProviderLoaded(fileSystemProviderName);112 113            string? environmentCurrentDirectory = null;114 115            try116            {117                environmentCurrentDirectory = Directory.GetCurrentDirectory();118            }119            catch (FileNotFoundException)120            {121                // This can happen if the current working directory is deleted by another process on non-Windows122                // In this case, we'll just ignore it and continue on with the current directory as null123            }124 125            LocationGlobber pathResolver = _context.LocationGlobber;126 127            // Loop through the relative paths and resolve them128 129            foreach (int index in _lookupPaths.IndexOfRelativePath())130            {131                string? resolvedDirectory = null;132                string? resolvedPath = null;133 134                CommandDiscovery.discoveryTracer.WriteLine(135                    "Lookup directory \"{0}\" appears to be a relative path. Attempting resolution...",136                    _lookupPaths[index]);137 138                if (isCurrentDriveValid)139                {140                    try141                    {142                        ProviderInfo provider;143                        resolvedPath =144                            pathResolver.GetProviderPath(145                                _lookupPaths[index],146                                out provider);147                    }148                    catch (ProviderInvocationException providerInvocationException)149                    {150                        CommandDiscovery.discoveryTracer.WriteLine(151                            "The relative path '{0}', could not be resolved because the provider threw an exception: '{1}'",152                            _lookupPaths[index],153                            providerInvocationException.Message);154                    }155                    catch (InvalidOperationException)156                    {157                        CommandDiscovery.discoveryTracer.WriteLine(158                            "The relative path '{0}', could not resolve a home directory for the provider",159                            _lookupPaths[index]);160                    }161 162                    // Note, if the directory resolves to multiple paths, only the first is used.163 164                    if (!string.IsNullOrEmpty(resolvedPath))165                    {166                        CommandDiscovery.discoveryTracer.TraceError(167                            "The relative path resolved to: {0}",168                            resolvedPath);169 170                        resolvedDirectory = resolvedPath;171                    }172                    else173                    {174                        CommandDiscovery.discoveryTracer.WriteLine(175                            "The relative path was not a file system path. {0}",176                            _lookupPaths[index]);177                    }178                }179                else180                {181                    CommandDiscovery.discoveryTracer.TraceWarning(182                        "The current drive is not set, using the process current directory: {0}",183                        environmentCurrentDirectory);184 185                    resolvedDirectory = environmentCurrentDirectory;186                }187 188                // If we successfully resolved the path, make sure it is unique. Remove189                // any duplicates found after the first occurrence of the path.190 191                if (resolvedDirectory != null)192                {193                    int existingIndex = _lookupPaths.IndexOf(resolvedDirectory);194 195                    if (existingIndex != -1)196                    {197                        if (existingIndex > index)198                        {199                            // The relative path index is less than the explicit path,200                            // so remove the explicit path.201 202                            indexesToRemove.Add(removalListCount++, existingIndex);203                            _lookupPaths[index] = resolvedDirectory;204                        }205                        else206                        {207                            // The explicit path index is less than the relative path208                            // index, so remove the relative path.209 210                            indexesToRemove.Add(removalListCount++, index);211                        }212                    }213                    else214                    {215                        // Change the relative path to the resolved path.216 217                        _lookupPaths[index] = resolvedDirectory;218                    }219                }220                else221                {222                    // The directory couldn't be resolved so remove it from the223                    // lookup paths.224 225                    indexesToRemove.Add(removalListCount++, index);226                }227            }228 229            // Now remove all the duplicates starting from the back of the collection.230            // As each element is removed, elements that follow are moved up to occupy231            // the emptied index.232 233            for (int removeIndex = indexesToRemove.Count; removeIndex > 0; --removeIndex)234            {235                int indexToRemove = indexesToRemove[removeIndex - 1];236                _lookupPaths.RemoveAt(indexToRemove);237            }238        }239 240        /// <summary>241        /// Gets an instance of a command enumerator.242        /// </summary>243        /// <returns>244        /// An instance of this class as IEnumerator.245        /// </returns>246        IEnumerator<string> IEnumerable<string>.GetEnumerator()247        {248            return this;249        }250 251        /// <summary>252        /// Gets an instance of a command enumerator.253        /// </summary>254        /// <returns>255        /// An instance of this class as IEnumerator.256        /// </returns>257        IEnumerator IEnumerable.GetEnumerator()258        {259            return this;260        }261 262        /// <summary>263        /// Moves the enumerator to the next command match.264        /// </summary>265        /// <returns>266        /// true if there was another command that matches, false otherwise.267        /// </returns>268        public bool MoveNext()269        {270            bool result = false;271 272            if (_justReset)273            {274                _justReset = false;275 276                if (!_patternEnumerator.MoveNext())277                {278                    s_tracer.TraceError("No patterns were specified");279                    return false;280                }281 282                if (!_lookupPathsEnumerator.MoveNext())283                {284                    s_tracer.TraceError("No lookup paths were specified");285                    return false;286                }287 288                GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current);289            }290 291            while (true) // while lookupPathsEnumerator is valid292            {293                while (true) // while patternEnumerator is valid294                {295                    // Try moving to the next path in the current results296 297                    if (!_currentDirectoryResultsEnumerator.MoveNext())298                    {299                        s_tracer.WriteLine("Current directory results are invalid");300 301                        // Since a path was not found in the current result,302                        // advance the pattern and try again303 304                        if (!_patternEnumerator.MoveNext())305                        {306                            s_tracer.WriteLine("Current patterns exhausted in current directory: {0}", _lookupPathsEnumerator.Current);307                            break;308                        }309 310                        // Get the results of the next pattern311 312                        GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current);313                    }314                    else315                    {316                        s_tracer.WriteLine("Next path found: {0}", _currentDirectoryResultsEnumerator.Current);317                        result = true;318                        break;319                    }320 321                    // Since we have reset the results, loop again to find the next result.322                }323 324                if (result)325                {326                    break;327                }328 329                // Since the path was not found in the current results, and all patterns were exhausted,330                // advance the path and continue331 332                if (!_lookupPathsEnumerator.MoveNext())333                {334                    s_tracer.WriteLine("All lookup paths exhausted, no more matches can be found");335                    break;336                }337 338                // Reset the pattern enumerator and get new results using the new lookup path339 340                _patternEnumerator = _patterns.GetEnumerator();341 342                if (!_patternEnumerator.MoveNext())343                {344                    s_tracer.WriteLine("All patterns exhausted, no more matches can be found");345                    break;346                }347 348                GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current);349            }350 351            return result;352        }353 354        /// <summary>355        /// Resets the enumerator to before the first command match.356        /// </summary>357        public void Reset()358        {359            _lookupPathsEnumerator.Dispose();360            _lookupPathsEnumerator = _lookupPaths.GetEnumerator();361            _patternEnumerator.Dispose();362            _patternEnumerator = _patterns.GetEnumerator();363            _currentDirectoryResults = Array.Empty<string>();364            _currentDirectoryResultsEnumerator.Dispose();365            _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator();366            _justReset = true;367        }368 369        /// <summary>370        /// Gets the path to the current command match.371        /// </summary>372        /// <value></value>373        /// <exception cref="InvalidOperationException">374        /// The enumerator is positioned before the first element of375        /// the collection or after the last element.376        /// </exception>377        string IEnumerator<string>.Current378        {379            get380            {381                if (_currentDirectoryResults == null)382                {383                    throw PSTraceSource.NewInvalidOperationException();384                }385 386                return _currentDirectoryResultsEnumerator.Current;387            }388        }389 390        object IEnumerator.Current391        {392            get393            {394                return ((IEnumerator<string>)this).Current;395            }396        }397 398        /// <summary>399        /// Required by the IEnumerator generic interface.400        /// Resets the searcher.401        /// </summary>402        public void Dispose()403        {404            Reset();405            GC.SuppressFinalize(this);406        }407        #region private members408 409        /// <summary>410        /// Gets the matching files in the specified directories and resets411        /// the currentDirectoryResultsEnumerator to this new set of results.412        /// </summary>413        /// <param name="pattern">414        /// The pattern used to find the matching files in the specified directory.415        /// </param>416        /// <param name="directory">417        /// The path to the directory to find the files in.418        /// </param>419        private void GetNewDirectoryResults(string pattern, string directory)420        {421            IEnumerable<string>? result = null;422            try423            {424                CommandDiscovery.discoveryTracer.WriteLine("Looking for {0} in {1}", pattern, directory);425 426                // Get the matching files in the directory427                if (Directory.Exists(directory))428                {429                    // Win8 bug 92113: Directory.GetFiles() regressed in NET4430                    // Directory.GetFiles(directory, ".") used to return null with CLR 2.431                    // but with CLR4 it started acting like "*". This is a appcompat bug in CLR4432                    // but they cannot fix it as CLR4 is already RTMd by the time this was reported.433                    // If they revert it, it will become a CLR4 appcompat issue. So, using the workaround434                    // to forcefully use null if pattern is "."435                    if (pattern.Length != 1 || pattern[0] != '.')436                    {437                        if (_fuzzyMatcher is not null)438                        {439                            var files = new List<string>();440                            var matchingFiles = Directory.EnumerateFiles(directory);441                            foreach (string file in matchingFiles)442                            {443                                if (_fuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern))444                                {445                                    files.Add(file);446                                }447                            }448 449                            result = _postProcessEnumeratedFiles != null450                                ? _postProcessEnumeratedFiles(files.ToArray())451                                : files;452                        }453                        else454                        {455                            var matchingFiles = Directory.EnumerateFiles(directory, pattern);456                            result = _postProcessEnumeratedFiles != null457                                ? _postProcessEnumeratedFiles(matchingFiles.ToArray())458                                : matchingFiles;459                        }460                    }461                }462            }463            catch (ArgumentException)464            {465                // The pattern contained illegal file system characters466            }467            catch (IOException)468            {469                // A directory specified in the lookup path was not470                // accessible471            }472            catch (UnauthorizedAccessException)473            {474                // A directory specified in the lookup path was not475                // accessible476            }477            catch (NotSupportedException)478            {479                // A directory specified in the lookup path was not480                // accessible481            }482 483            _currentDirectoryResults = result ?? Array.Empty<string>();484            _currentDirectoryResultsEnumerator = _currentDirectoryResults.GetEnumerator();485        }486 487        private IEnumerable<string>? CheckAgainstAcceptableCommandNames(string[] fileNames)488        {489            var baseNames = fileNames.Select(Path.GetFileName).ToArray();490 491            // Result must be ordered by PATHEXT order of precedence.492            // acceptableCommandNames is in this order, so493 494            // Porting note: allow files with executable bit on non-Windows platforms495 496            Collection<string>? result = null;497            if (baseNames.Length > 0 && _acceptableCommandNames != null)498            {499                foreach (var name in _acceptableCommandNames)500                {501                    for (int i = 0; i < baseNames.Length; i++)502                    {503                        if (name.Equals(baseNames[i], StringComparison.OrdinalIgnoreCase)504                            || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(name)))505                        {506                            result ??= new Collection<string>();507                            result.Add(fileNames[i]);508                            break;509                        }510                    }511                }512            }513 514            return result;515        }516 517        private IEnumerable<string>? JustCheckExtensions(string[] fileNames)518        {519            // Warning: pretty duplicated code520            // Result must be ordered by PATHEXT order of precedence.521 522            // Porting note: allow files with executable bit on non-Windows platforms523 524            Collection<string>? result = null;525            foreach (var allowedExt in _orderedPathExt)526            {527                foreach (var fileName in fileNames)528                {529                    if (fileName.EndsWith(allowedExt, StringComparison.OrdinalIgnoreCase)530                        || (!Platform.IsWindows && Platform.NonWindowsIsExecutable(fileName)))531                    {532                        result ??= new Collection<string>();533                        result.Add(fileName);534                    }535                }536            }537 538            return result;539        }540 541        /// <summary>542        /// The directory paths in which to look for commands.543        /// This is derived from the PATH environment variable.544        /// </summary>545        private readonly LookupPathCollection _lookupPaths;546 547        /// <summary>548        /// The enumerator for the lookup paths.549        /// </summary>550        private IEnumerator<string> _lookupPathsEnumerator;551 552        /// <summary>553        /// The list of results matching the pattern in the current554        /// path lookup directory. Resets to null.555        /// </summary>556        private IEnumerable<string> _currentDirectoryResults;557 558        /// <summary>559        /// The enumerator for the list of results.560        /// </summary>561        private IEnumerator<string> _currentDirectoryResultsEnumerator;562 563        /// <summary>564        /// The command name to search for.565        /// </summary>566        private readonly IEnumerable<string> _patterns;567 568        /// <summary>569        /// The enumerator for the patterns.570        /// </summary>571        private IEnumerator<string> _patternEnumerator;572 573        /// <summary>574        /// A reference to the execution context for this runspace.575        /// </summary>576        private readonly ExecutionContext _context;577 578        /// <summary>579        /// When reset is called, this gets set to true. Once MoveNext580        /// is called, this gets set to false.581        /// </summary>582        private bool _justReset;583 584        /// <summary>585        /// If not null, called with the enumerated files for further processing.586        /// </summary>587        private readonly Func<string[], IEnumerable<string>?> _postProcessEnumeratedFiles;588 589        private readonly string[] _orderedPathExt;590        private readonly Collection<string>? _acceptableCommandNames;591 592        private readonly FuzzyMatcher? _fuzzyMatcher;593 594        #endregion private members595    }596}597