MegaBites-AI/Windows-powershell
0372
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.Diagnostics.CodeAnalysis;10using System.IO;11using System.Management.Automation.Internal;12using System.Management.Automation.Security;13 14using Dbg = System.Management.Automation.Diagnostics;15 16namespace System.Management.Automation17{18 /// <summary>19 /// Used to enumerate the commands on the system that match the specified20 /// command name.21 /// </summary>22 internal class CommandSearcher : IEnumerable<CommandInfo>, IEnumerator<CommandInfo>23 {24 /// <summary>25 /// Constructs a command searching enumerator that resolves the location26 /// to a command using a standard algorithm.27 /// </summary>28 /// <param name="commandName">The name of the command to look for.</param>29 /// <param name="options">Determines which types of commands glob resolution of the name will take place on.</param>30 /// <param name="commandTypes">The types of commands to look for.</param>31 /// <param name="context">The execution context for this engine instance.</param>32 /// <param name="fuzzyMatcher">The fuzzy matcher to use for fuzzy searching.</param>33 ///34 /// <exception cref="ArgumentNullException">If <paramref name="context"/> is null.</exception>35 /// <exception cref="PSArgumentException">If <paramref name="commandName"/> is null or empty.</exception>36 internal CommandSearcher(37 string commandName,38 SearchResolutionOptions options,39 CommandTypes commandTypes,40 ExecutionContext context,41 FuzzyMatcher? fuzzyMatcher = null)42 {43 Diagnostics.Assert(context != null, "caller to verify context is not null");44 Diagnostics.Assert(!string.IsNullOrEmpty(commandName), "caller to verify commandName is valid");45 46 _commandName = commandName;47 _context = context;48 _commandResolutionOptions = options;49 _commandTypes = commandTypes;50 _fuzzyMatcher = fuzzyMatcher;51 52 // Initialize the enumerators53 this.Reset();54 }55 56 /// <summary>57 /// Gets an instance of a command enumerator.58 /// </summary>59 /// <returns>60 /// An instance of this class as IEnumerator.61 /// </returns>62 IEnumerator<CommandInfo> IEnumerable<CommandInfo>.GetEnumerator()63 {64 return this;65 }66 67 IEnumerator IEnumerable.GetEnumerator()68 {69 return this;70 }71 72 /// <summary>73 /// Moves the enumerator to the next command match. Public for IEnumerable.74 /// </summary>75 /// <returns>76 /// true if there was another command that matches, false otherwise.77 /// </returns>78 public bool MoveNext()79 {80 _currentMatch = null;81 82 if (_currentState == SearchState.SearchingAliases)83 {84 _currentMatch = SearchForAliases();85 86 // Why don't we check IsVisible on other scoped items?87 if (_currentMatch != null && SessionState.IsVisible(_commandOrigin, _currentMatch))88 {89 return true;90 }91 92 // Make sure Current doesn't return an alias that isn't visible93 _currentMatch = null;94 95 // Advance the state96 _currentState = SearchState.SearchingFunctions;97 }98 99 if (_currentState == SearchState.SearchingFunctions)100 {101 _currentMatch = SearchForFunctions();102 // Return the alias info only if it is visible. If not, then skip to the next103 // stage of command resolution...104 if (_currentMatch != null)105 {106 return true;107 }108 109 // Advance the state110 _currentState = SearchState.SearchingCmdlets;111 }112 113 if (_currentState == SearchState.SearchingCmdlets)114 {115 _currentMatch = SearchForCmdlets();116 if (_currentMatch != null)117 {118 return true;119 }120 121 // Advance the state122 _currentState = SearchState.StartSearchingForExternalCommands;123 }124 125 if (_currentState == SearchState.StartSearchingForExternalCommands)126 {127 if ((_commandTypes & (CommandTypes.Application | CommandTypes.ExternalScript)) == 0)128 {129 // Since we are not requiring any path lookup in this search, just return false now130 // because all the remaining searches do path lookup.131 return false;132 }133 134 // For security reasons, if the command is coming from outside the runspace and it looks like a path,135 // we want to pre-check that path before doing any probing of the network or drives136 if (_commandOrigin == CommandOrigin.Runspace && _commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) >= 0)137 {138 bool allowed = false;139 140 // Ok - it looks like it might be a path, so we're going to check to see if the command is prefixed141 // by any of the allowed paths. If so, then we allow the search to proceed...142 143 // If either the Applications or Script lists contain just '*' the command is allowed144 // at this point.145 if ((_context.EngineSessionState.Applications.Count == 1 &&146 _context.EngineSessionState.Applications[0].Equals("*", StringComparison.OrdinalIgnoreCase)) ||147 (_context.EngineSessionState.Scripts.Count == 1 &&148 _context.EngineSessionState.Scripts[0].Equals("*", StringComparison.OrdinalIgnoreCase)))149 {150 allowed = true;151 }152 else153 {154 // Ok, see if it's in the applications list155 foreach (string path in _context.EngineSessionState.Applications)156 {157 if (checkPath(path, _commandName))158 {159 allowed = true;160 break;161 }162 }163 164 // If it wasn't in the applications list, see it's in the script list165 if (!allowed)166 {167 foreach (string path in _context.EngineSessionState.Scripts)168 {169 if (checkPath(path, _commandName))170 {171 allowed = true;172 break;173 }174 }175 }176 }177 178 if (!allowed)179 {180 return false;181 }182 }183 184 // Advance the state185 186 _currentState = SearchState.PowerShellPathResolution;187 188 _currentMatch = ProcessBuiltinScriptState();189 190 if (_currentMatch != null)191 {192 // Set the current state to QualifiedFileSystemPath since193 // we want to skip the qualified file system path search194 // in the case where we found a PowerShell qualified path.195 196 _currentState = SearchState.QualifiedFileSystemPath;197 return true;198 }199 }200 201 if (_currentState == SearchState.PowerShellPathResolution)202 {203 _currentState = SearchState.QualifiedFileSystemPath;204 205 _currentMatch = ProcessPathResolutionState();206 207 if (_currentMatch != null)208 {209 return true;210 }211 }212 213 // Search using CommandPathSearch214 215 if (_currentState == SearchState.QualifiedFileSystemPath ||216 _currentState == SearchState.PathSearch)217 {218 _currentMatch = ProcessQualifiedFileSystemState();219 220 if (_currentMatch != null)221 {222 return true;223 }224 }225 226 if (_currentState == SearchState.PathSearch)227 {228 _currentState = SearchState.PowerShellRelativePath;229 230 _currentMatch = ProcessPathSearchState();231 232 if (_currentMatch != null)233 {234 return true;235 }236 }237 238 return false;239 }240 241 private CommandInfo? SearchForAliases()242 {243 CommandInfo? currentMatch = null;244 245 if (_context.EngineSessionState != null &&246 (_commandTypes & CommandTypes.Alias) != 0)247 {248 currentMatch = GetNextAlias();249 }250 251 return currentMatch;252 }253 254 private CommandInfo? SearchForFunctions()255 {256 CommandInfo? currentMatch = null;257 258 if (_context.EngineSessionState != null &&259 (_commandTypes & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Configuration)) != 0)260 {261 currentMatch = GetNextFunction();262 }263 264 return currentMatch;265 }266 267 private CommandInfo? SearchForCmdlets()268 {269 CommandInfo? currentMatch = null;270 271 if ((_commandTypes & CommandTypes.Cmdlet) != 0)272 {273 currentMatch = GetNextCmdlet();274 }275 276 return currentMatch;277 }278 279 private CommandInfo? ProcessBuiltinScriptState()280 {281 CommandInfo? currentMatch = null;282 283 // Check to see if the path is qualified284 285 if (_context.EngineSessionState != null &&286 _context.EngineSessionState.ProviderCount > 0 &&287 IsQualifiedPSPath(_commandName))288 {289 currentMatch = GetNextFromPath();290 }291 292 return currentMatch;293 }294 295 private CommandInfo? ProcessPathResolutionState()296 {297 CommandInfo? currentMatch = null;298 299 try300 {301 // Check to see if the path is a file system path that302 // is rooted. If so that is the next match303 if (Path.IsPathRooted(_commandName) &&304 File.Exists(_commandName))305 {306 try307 {308 currentMatch = GetInfoFromPath(_commandName);309 }310 catch (FileLoadException)311 {312 }313 catch (FormatException)314 {315 }316 catch (MetadataException)317 {318 }319 }320 }321 catch (ArgumentException)322 {323 // If the path contains illegal characters that324 // weren't caught by the other APIs, IsPathRooted325 // will throw an exception.326 // For example, looking for a command called327 // `abcdef328 // The `a will be translated into the beep control character329 // which is not a legal file system character, though330 // Path.InvalidPathChars does not contain it as an invalid331 // character.332 }333 334 return currentMatch;335 }336 337 private CommandInfo? ProcessQualifiedFileSystemState()338 {339 try340 {341 setupPathSearcher();342 }343 catch (ArgumentException)344 {345 _currentState = SearchState.NoMoreMatches;346 throw;347 }348 catch (PathTooLongException)349 {350 _currentState = SearchState.NoMoreMatches;351 throw;352 }353 354 CommandInfo? currentMatch = null;355 _currentState = SearchState.PathSearch;356 if (_canDoPathLookup)357 {358 try359 {360 // the previous call to setupPathSearcher ensures _pathSearcher != null361 while (currentMatch == null && _pathSearcher!.MoveNext())362 {363 currentMatch = GetInfoFromPath(((IEnumerator<string>)_pathSearcher).Current);364 }365 }366 catch (InvalidOperationException)367 {368 // The enumerator may throw if there are no more matches369 }370 }371 372 return currentMatch;373 }374 375 private CommandInfo? ProcessPathSearchState()376 {377 CommandInfo? currentMatch = null;378 string? path = DoPowerShellRelativePathLookup();379 380 if (!string.IsNullOrEmpty(path))381 {382 currentMatch = GetInfoFromPath(path);383 }384 385 return currentMatch;386 }387 388 /// <summary>389 /// Gets the CommandInfo representing the current command match.390 /// </summary>391 /// <value></value>392 /// <exception cref="InvalidOperationException">393 /// The enumerator is positioned before the first element of394 /// the collection or after the last element.395 /// </exception>396 CommandInfo IEnumerator<CommandInfo>.Current397 {398 get399 {400 if ((_currentState == SearchState.SearchingAliases && _currentMatch == null) ||401 _currentState == SearchState.NoMoreMatches ||402 _currentMatch == null)403 {404 throw PSTraceSource.NewInvalidOperationException();405 }406 407 return _currentMatch;408 }409 }410 411 object IEnumerator.Current412 {413 get414 {415 return ((IEnumerator<CommandInfo>)this).Current;416 }417 }418 419 /// <summary>420 /// Required by the IEnumerator generic interface.421 /// Resets the searcher.422 /// </summary>423 public void Dispose()424 {425 if (_pathSearcher != null)426 {427 _pathSearcher.Dispose();428 _pathSearcher = null;429 }430 431 Reset();432 GC.SuppressFinalize(this);433 }434 435 #region private members436 437 /// <summary>438 /// Gets the next command info using the command name as a path.439 /// </summary>440 /// <returns>441 /// A CommandInfo for the next command if it exists as a path, or null otherwise.442 /// </returns>443 private CommandInfo? GetNextFromPath()444 {445 CommandInfo? result = null;446 447 do // false loop448 {449 CommandDiscovery.discoveryTracer.WriteLine(450 "The name appears to be a qualified path: {0}",451 _commandName);452 453 CommandDiscovery.discoveryTracer.WriteLine(454 "Trying to resolve the path as an PSPath");455 456 // Find the match if it is.457 // Try literal path resolution if it is set to run first458 if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))459 {460 var path = GetNextLiteralPathThatExistsAndHandleExceptions(_commandName, out _);461 462 if (path != null)463 {464 return GetInfoFromPath(path);465 }466 }467 468 Collection<string> resolvedPaths = new Collection<string>();469 if (WildcardPattern.ContainsWildcardCharacters(_commandName))470 {471 resolvedPaths = GetNextFromPathUsingWildcards(_commandName, out _);472 }473 474 // Try literal path resolution if wildcards are enable first and wildcard search failed475 if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&476 resolvedPaths.Count == 0)477 {478 string? path = GetNextLiteralPathThatExistsAndHandleExceptions(_commandName, out _);479 480 if (path != null)481 {482 return GetInfoFromPath(path);483 }484 }485 486 if (resolvedPaths.Count > 1)487 {488 CommandDiscovery.discoveryTracer.TraceError(489 "The path resolved to more than one result so this path cannot be used.");490 break;491 }492 493 // If the path was resolved, and it exists494 if (resolvedPaths.Count == 1 &&495 File.Exists(resolvedPaths[0]))496 {497 string path = resolvedPaths[0];498 499 CommandDiscovery.discoveryTracer.WriteLine(500 "Path resolved to: {0}",501 path);502 503 result = GetInfoFromPath(path);504 }505 } while (false);506 507 return result;508 }509 510 /// <summary>511 /// Gets the next path using WildCards.512 /// </summary>513 /// <param name="command">514 /// The command to search for.515 /// </param>516 /// <param name="provider">The provider that the command was found in.</param>517 /// <returns>518 /// A collection of full paths to the commands which were found.519 /// </returns>520 private Collection<string> GetNextFromPathUsingWildcards(string? command, out ProviderInfo? provider)521 {522 try523 {524 return _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(path: command, allowNonexistingPaths: false, provider: out provider, providerInstance: out _);525 }526 catch (ItemNotFoundException)527 {528 CommandDiscovery.discoveryTracer.TraceError(529 "The path could not be found: {0}",530 command);531 }532 catch (DriveNotFoundException)533 {534 CommandDiscovery.discoveryTracer.TraceError(535 "A drive could not be found for the path: {0}",536 command);537 }538 catch (ProviderNotFoundException)539 {540 CommandDiscovery.discoveryTracer.TraceError(541 "A provider could not be found for the path: {0}",542 command);543 }544 catch (InvalidOperationException)545 {546 CommandDiscovery.discoveryTracer.TraceError(547 "The path specified a home directory, but the provider home directory was not set. {0}",548 command);549 }550 catch (ProviderInvocationException providerException)551 {552 CommandDiscovery.discoveryTracer.TraceError(553 "The provider associated with the path '{0}' encountered an error: {1}",554 command,555 providerException.Message);556 }557 catch (PSNotSupportedException)558 {559 CommandDiscovery.discoveryTracer.TraceError(560 "The provider associated with the path '{0}' does not implement ContainerCmdletProvider",561 command);562 }563 564 provider = null;565 return new Collection<string>();566 }567 568 private static bool checkPath(string path, string commandName)569 {570 return path.StartsWith(commandName, StringComparison.OrdinalIgnoreCase);571 }572 573 /// <summary>574 /// Gets the appropriate CommandInfo instance given the specified path.575 /// </summary>576 /// <param name="path">577 /// The path to create the CommandInfo for.578 /// </param>579 /// <returns>580 /// An instance of the appropriate CommandInfo derivative given the specified path.581 /// </returns>582 /// <exception cref="FileLoadException">583 /// The <paramref name="path"/> refers to a cmdlet, or cmdletprovider584 /// and it could not be loaded as an XML document.585 /// </exception>586 /// <exception cref="FormatException">587 /// The <paramref name="path"/> refers to a cmdlet, or cmdletprovider588 /// that does not adhere to the appropriate file format for its extension.589 /// </exception>590 /// <exception cref="MetadataException">591 /// If <paramref name="path"/> refers to a cmdlet file that592 /// contains invalid metadata.593 /// </exception>594 private CommandInfo? GetInfoFromPath(string path)595 {596 CommandInfo? result = null;597 598 do // false loop599 {600 if (!File.Exists(path))601 {602 CommandDiscovery.discoveryTracer.TraceError("The path does not exist: {0}", path);603 break;604 }605 606 // Now create the appropriate CommandInfo using the extension607 string? extension = null;608 609 try610 {611 extension = Path.GetExtension(path);612 }613 catch (ArgumentException)614 {615 // If the path contains illegal characters that616 // weren't caught by the other APIs, GetExtension617 // will throw an exception.618 // For example, looking for a command called619 // `abcdef620 // The `a will be translated into the beep control character621 // which is not a legal file system character.622 }623 624 if (extension == null)625 {626 result = null;627 break;628 }629 630 if (string.Equals(extension, StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase))631 {632 if ((_commandTypes & CommandTypes.ExternalScript) != 0)633 {634 string scriptName = Path.GetFileName(path);635 636 CommandDiscovery.discoveryTracer.WriteLine(637 "Command Found: path ({0}) is a script with name: {1}",638 path,639 scriptName);640 641 // The path is to a PowerShell script642 643 result = new ExternalScriptInfo(scriptName, path, _context);644 break;645 }646 647 break;648 }649 650 if ((_commandTypes & CommandTypes.Application) != 0)651 {652 // Anything else is treated like an application653 654 string appName = Path.GetFileName(path);655 656 CommandDiscovery.discoveryTracer.WriteLine(657 "Command Found: path ({0}) is an application with name: {1}",658 path,659 appName);660 661 result = new ApplicationInfo(appName, path, _context);662 break;663 }664 } while (false);665 666 // Verify that this script is not untrusted, if we aren't constrained.667 if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))668 {669 result = null;670 }671 672 return result;673 }674 675 /// <summary>676 /// Gets the next matching alias.677 /// </summary>678 /// <returns>679 /// A CommandInfo representing the next matching alias if found, otherwise null.680 /// </returns>681 private CommandInfo? GetNextAlias()682 {683 CommandInfo? result = null;684 685 if ((_commandResolutionOptions & SearchResolutionOptions.ResolveAliasPatterns) != 0)686 {687 if (_matchingAlias == null)688 {689 // Generate the enumerator of matching alias names690 691 Collection<AliasInfo> matchingAliases = new Collection<AliasInfo>();692 693 WildcardPattern aliasMatcher =694 WildcardPattern.Get(695 _commandName,696 WildcardOptions.IgnoreCase);697 698 foreach (KeyValuePair<string, AliasInfo> aliasEntry in _context.EngineSessionState.GetAliasTable())699 {700 if (aliasMatcher.IsMatch(aliasEntry.Key) ||701 (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName)))702 {703 matchingAliases.Add(aliasEntry.Value);704 }705 }706 707 // Process alias from modules708 AliasInfo? c = GetAliasFromModules(_commandName);709 if (c != null)710 {711 matchingAliases.Add(c);712 }713 714 _matchingAlias = matchingAliases.GetEnumerator();715 }716 717 if (!_matchingAlias.MoveNext())718 {719 // Advance the state720 _currentState = SearchState.SearchingFunctions;721 722 _matchingAlias = null;723 }724 else725 {726 result = _matchingAlias.Current;727 }728 }729 else730 {731 // Advance the state732 _currentState = SearchState.SearchingFunctions;733 734 result = _context.EngineSessionState.GetAlias(_commandName) ?? GetAliasFromModules(_commandName);735 }736 737 // Verify that this alias was not created by an untrusted constrained language,738 // if we aren't constrained.739 if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))740 {741 result = null;742 }743 744 if (result != null)745 {746 CommandDiscovery.discoveryTracer.WriteLine(747 "Alias found: {0} {1}",748 result.Name,749 result.Definition);750 }751 752 return result;753 }754 755 /// <summary>756 /// Gets the next matching function.757 /// </summary>758 /// <returns>759 /// A CommandInfo representing the next matching function if found, otherwise null.760 /// </returns>761 private CommandInfo? GetNextFunction()762 {763 CommandInfo? result = null;764 765 if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveFunctionPatterns))766 {767 if (_matchingFunctionEnumerator == null)768 {769 Collection<CommandInfo?> matchingFunction = new Collection<CommandInfo?>();770 771 // Generate the enumerator of matching function names772 WildcardPattern functionMatcher =773 WildcardPattern.Get(774 _commandName,775 WildcardOptions.IgnoreCase);776 777 foreach ((string functionName, FunctionInfo functionInfo) in _context.EngineSessionState.GetFunctionTable())778 {779 if (functionMatcher.IsMatch(functionName) ||780 (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(functionName, _commandName)))781 {782 matchingFunction.Add(functionInfo);783 }784 else if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion))785 {786 if (_commandName.Equals(ModuleUtils.AbbreviateName(functionName), StringComparison.OrdinalIgnoreCase))787 {788 matchingFunction.Add(functionInfo);789 }790 }791 }792 793 // Process functions from modules794 CommandInfo? cmdInfo = GetFunctionFromModules(_commandName);795 if (cmdInfo != null)796 {797 matchingFunction.Add(cmdInfo);798 }799 800 _matchingFunctionEnumerator = matchingFunction.GetEnumerator();801 }802 803 if (!_matchingFunctionEnumerator.MoveNext())804 {805 // Advance the state806 _currentState = SearchState.SearchingCmdlets;807 808 _matchingFunctionEnumerator = null;809 }810 else811 {812 result = _matchingFunctionEnumerator.Current;813 }814 }815 else816 {817 // Advance the state818 _currentState = SearchState.SearchingCmdlets;819 820 result = GetFunction(_commandName);821 }822 823 // Verify that this function was not created by an untrusted constrained language,824 // if we aren't constrained.825 if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))826 {827 result = null;828 }829 830 return result;831 }832 833 // Don't return commands to the user if that might result in:834 // - Trusted commands calling untrusted functions that the user has overridden835 // - Debug prompts calling internal functions that are likely to have code injection836 private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? result, ExecutionContext executionContext)837 {838 if (result == null)839 {840 return false;841 }842 843 // Don't return untrusted commands to trusted functions.844 if (result.DefiningLanguageMode == PSLanguageMode.ConstrainedLanguage && executionContext.LanguageMode == PSLanguageMode.FullLanguage)845 {846 if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit)847 {848 return true;849 }850 851 // This audit log message is to inform the user that an expected command will not be available because it is not trusted852 // when the machine is in policy enforcement mode.853 SystemPolicy.LogWDACAuditMessage(854 context: executionContext,855 title: CommandBaseStrings.SearcherWDACLogTitle,856 message: StringUtil.Format(CommandBaseStrings.SearcherWDACLogMessage, result.Name, result.ModuleName ?? string.Empty),857 fqid: "CommandSearchFailureForUntrustedCommand");858 }859 860 // Don't allow invocation of trusted functions from debug breakpoints.861 // They were probably defined within a trusted script, and could be862 // susceptible to injection attacks. However, we do allow execution863 // of functions defined in the global scope (i.e.: "more",) as those864 // are intended to be exposed explicitly to users.865 if ((result is FunctionInfo) &&866 (executionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) &&867 (result.DefiningLanguageMode == PSLanguageMode.FullLanguage) &&868 (executionContext.Debugger != null) &&869 (executionContext.Debugger.InBreakpoint) &&870 (!(executionContext.TopLevelSessionState.GetFunctionTableAtScope("GLOBAL").ContainsKey(result.Name))))871 {872 return true;873 }874 875 return false;876 }877 878 private AliasInfo? GetAliasFromModules(string command)879 {880 AliasInfo? result = null;881 882 if (command.IndexOf('\\') > 0)883 {884 // See if it's a module qualified alias...885 PSSnapinQualifiedName? qualifiedName = PSSnapinQualifiedName.GetInstance(command);886 if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName))887 {888 PSModuleInfo? module = GetImportedModuleByName(qualifiedName.PSSnapInName);889 890 module?.ExportedAliases.TryGetValue(qualifiedName.ShortName, out result);891 }892 }893 894 return result;895 }896 897 private CommandInfo? GetFunctionFromModules(string command)898 {899 FunctionInfo? result = null;900 901 if (command.IndexOf('\\') > 0)902 {903 // See if it's a module qualified function call...904 PSSnapinQualifiedName? qualifiedName = PSSnapinQualifiedName.GetInstance(command);905 if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName))906 {907 PSModuleInfo? module = GetImportedModuleByName(qualifiedName.PSSnapInName);908 909 module?.ExportedFunctions.TryGetValue(qualifiedName.ShortName, out result);910 }911 }912 913 return result;914 }915 916 private PSModuleInfo? GetImportedModuleByName(string moduleName)917 {918 PSModuleInfo? module = null;919 List<PSModuleInfo> modules = _context.Modules.GetModules(new string[] { moduleName }, false);920 921 if (modules != null && modules.Count > 0)922 {923 foreach (PSModuleInfo m in modules)924 {925 if (_context.previousModuleImported.ContainsKey(m.Name) && ((string?)_context.previousModuleImported[m.Name] == m.Path))926 {927 module = m;928 break;929 }930 }931 932 module ??= modules[0];933 }934 935 return module;936 }937 938 /// <summary>939 /// Gets the FunctionInfo or FilterInfo for the specified function name.940 /// </summary>941 /// <param name="function">942 /// The name of the function/filter to retrieve.943 /// </param>944 /// <returns>945 /// A FunctionInfo if the function name exists and is a function, a FilterInfo if946 /// the filter name exists and is a filter, or null otherwise.947 /// </returns>948 private CommandInfo? GetFunction(string function)949 {950 CommandInfo? result = _context.EngineSessionState.GetFunction(function);951 952 if (result != null)953 {954 var formatString = result switch955 {956 FilterInfo => "Filter found: {0}",957 ConfigurationInfo => "Configuration found: {0}",958 _ => "Function found: {0}",959 };960 CommandDiscovery.discoveryTracer.WriteLine(formatString, function);961 }962 else963 {964 result = GetFunctionFromModules(function);965 }966 967 return result;968 }969 970 /// <summary>971 /// Gets the next cmdlet from the collection of matching cmdlets.972 /// If the collection doesn't exist yet it is created and the973 /// enumerator is moved to the first item in the collection.974 /// </summary>975 /// <returns>976 /// A CmdletInfo for the next matching Cmdlet or null if there are977 /// no more matches.978 /// </returns>979 private CmdletInfo? GetNextCmdlet()980 {981 CmdletInfo? result = null;982 bool useAbbreviationExpansion = _commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion);983 984 if (_matchingCmdlet == null)985 {986 if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.CommandNameIsPattern) || useAbbreviationExpansion)987 {988 Collection<CmdletInfo> matchingCmdletInfo = new Collection<CmdletInfo>();989 990 PSSnapinQualifiedName? PSSnapinQualifiedCommandName =991 PSSnapinQualifiedName.GetInstance(_commandName);992 993 if (!useAbbreviationExpansion && PSSnapinQualifiedCommandName == null)994 {995 return null;996 }997 998 string? moduleName = PSSnapinQualifiedCommandName?.PSSnapInName;999 1000 var cmdletShortName = PSSnapinQualifiedCommandName?.ShortName;1001 WildcardPattern? cmdletMatcher = cmdletShortName != null1002 ? WildcardPattern.Get(cmdletShortName, WildcardOptions.IgnoreCase)1003 : null;1004 1005 SessionStateInternal ss = _context.EngineSessionState;1006 1007 foreach (List<CmdletInfo> cmdletList in ss.GetCmdletTable().Values)1008 {1009 foreach (CmdletInfo cmdlet in cmdletList)1010 {1011 if ((cmdletMatcher is not null && cmdletMatcher.IsMatch(cmdlet.Name)) ||1012 (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName)))1013 {1014 if (string.IsNullOrEmpty(moduleName) || moduleName.Equals(cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase))1015 {1016 // If PSSnapin is specified, make sure they match1017 matchingCmdletInfo.Add(cmdlet);1018 }1019 }1020 else if (useAbbreviationExpansion)1021 {1022 if (_commandName.Equals(ModuleUtils.AbbreviateName(cmdlet.Name), StringComparison.OrdinalIgnoreCase))1023 {1024 matchingCmdletInfo.Add(cmdlet);1025 }1026 }1027 }1028 }1029 1030 _matchingCmdlet = matchingCmdletInfo.GetEnumerator();1031 }1032 else1033 {1034 _matchingCmdlet = _context.CommandDiscovery.GetCmdletInfo(_commandName,1035 _commandResolutionOptions.HasFlag(SearchResolutionOptions.SearchAllScopes));1036 }1037 }1038 1039 if (!_matchingCmdlet.MoveNext())1040 {1041 // Advance the state1042 _currentState = SearchState.StartSearchingForExternalCommands;1043 1044 _matchingCmdlet = null;1045 }1046 else1047 {1048 result = _matchingCmdlet.Current;1049 }1050 1051 return traceResult(result);1052 }1053 1054 private IEnumerator<CmdletInfo>? _matchingCmdlet;1055 1056 [return: NotNullIfNotNull("result")]1057 private static CmdletInfo? traceResult(CmdletInfo? result)1058 {1059 if (result != null)1060 {1061 CommandDiscovery.discoveryTracer.WriteLine(1062 "Cmdlet found: {0} {1}",1063 result.Name,1064 result.ImplementingType);1065 }1066 1067 return result;1068 }1069 1070 private string? DoPowerShellRelativePathLookup()1071 {1072 string? result = null;1073 1074 if (_context.EngineSessionState != null &&1075 _context.EngineSessionState.ProviderCount > 0 && _commandName.Length != 0)1076 {1077 // NTRAID#Windows OS Bugs-1009294-2004/02/04-JeffJon1078 // This is really slow. Maybe since we are only allowing FS paths right1079 // now we should use the file system APIs to verify the existence of the file.1080 1081 // Since the path to the command was not found using the PATH variable,1082 // maybe it is relative to the current location. Try resolving the1083 // path.1084 // Relative Path: ".\command.exe"1085 // Home Path: "~\command.exe"1086 // Drive Relative Path: "\Users\User\AppData\Local\Temp\command.exe"1087 1088 char firstChar = _commandName[0];1089 if (firstChar == '.' || firstChar == '~' || firstChar == '\\')1090 {1091 using (CommandDiscovery.discoveryTracer.TraceScope(1092 "{0} appears to be a relative path. Trying to resolve relative path",1093 _commandName))1094 {1095 result = ResolvePSPath(_commandName);1096 }1097 }1098 }1099 1100 return result;1101 }1102 1103 /// <summary>1104 /// Resolves the given path as an PSPath and ensures that it was resolved1105 /// by the FileSystemProvider.1106 /// </summary>1107 /// <param name="path">1108 /// The path to resolve.1109 /// </param>1110 /// <returns>1111 /// The path that was resolved. Null if the path couldn't be resolved or was1112 /// not resolved by the FileSystemProvider.1113 /// </returns>1114 private string? ResolvePSPath(string? path)1115 {1116 string? result = null;1117 1118 try1119 {1120 ProviderInfo? provider = null;1121 string? resolvedPath = null;1122 1123 // Try literal path resolution if it is set to run first1124 if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))1125 {1126 // Cannot return early as this code path only expects1127 // The file system provider and the final check for that1128 // must verify this before we return.1129 resolvedPath = GetNextLiteralPathThatExists(path, out provider);1130 }1131 1132 if (WildcardPattern.ContainsWildcardCharacters(path) &&1133 ((resolvedPath == null) || (provider == null)))1134 {1135 // Let PowerShell resolve relative path with wildcards.1136 Collection<string> resolvedPaths = GetNextFromPathUsingWildcards(path, out provider);1137 1138 if (resolvedPaths.Count == 0)1139 {1140 resolvedPath = null;1141 1142 CommandDiscovery.discoveryTracer.TraceError(1143 "The relative path with wildcard did not resolve to valid path. {0}",1144 path);1145 }1146 else if (resolvedPaths.Count > 1)1147 {1148 resolvedPath = null;1149 1150 CommandDiscovery.discoveryTracer.TraceError(1151 "The relative path with wildcard resolved to multiple paths. {0}",1152 path);1153 }1154 else1155 {1156 resolvedPath = resolvedPaths[0];1157 }1158 }1159 1160 // Try literal path resolution if wildcards are enabled first and wildcard search failed1161 if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&1162 ((resolvedPath == null) || (provider == null)))1163 {1164 resolvedPath = GetNextLiteralPathThatExists(path, out provider);1165 }1166 1167 // Verify the path was resolved to a file system path1168 if (provider != null && provider.NameEquals(_context.ProviderNames.FileSystem))1169 {1170 result = resolvedPath;1171 1172 CommandDiscovery.discoveryTracer.WriteLine(1173 "The relative path was resolved to: {0}",1174 result);1175 }1176 else1177 {1178 // The path was not to the file system1179 CommandDiscovery.discoveryTracer.TraceError(1180 "The relative path was not a file system path. {0}",1181 path);1182 }1183 }1184 catch (InvalidOperationException)1185 {1186 CommandDiscovery.discoveryTracer.TraceError(1187 "The home path was not specified for the provider. {0}",1188 path);1189 }1190 catch (ProviderInvocationException providerInvocationException)1191 {1192 CommandDiscovery.discoveryTracer.TraceError(1193 "While resolving the path, \"{0}\", an error was encountered by the provider: {1}",1194 path,1195 providerInvocationException.Message);1196 }1197 catch (ItemNotFoundException)1198 {1199 CommandDiscovery.discoveryTracer.TraceError(1200 "The path does not exist: {0}",