MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Collections.ObjectModel;8using System.Diagnostics;9using System.Globalization;10using System.IO;11using System.Linq;12using System.Management.Automation;13using System.Management.Automation.Internal;14using System.Management.Automation.Language;15using static System.Management.Automation.Verbs;16using Dbg = System.Management.Automation.Diagnostics;17 18namespace Microsoft.PowerShell.Commands19{20 /// <summary>21 /// The get-command cmdlet. It uses the command discovery APIs to find one or more22 /// commands of the given name. It returns an instance of CommandInfo for each23 /// command that is found.24 /// </summary>25 [Cmdlet(VerbsCommon.Get, "Command", DefaultParameterSetName = "CmdletSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096579")]26 [OutputType(typeof(AliasInfo), typeof(ApplicationInfo), typeof(FunctionInfo),27 typeof(CmdletInfo), typeof(ExternalScriptInfo), typeof(FilterInfo),28 typeof(string), typeof(PSObject))]29 public sealed class GetCommandCommand : PSCmdlet30 {31 #region Definitions of cmdlet parameters32 33 /// <summary>34 /// Gets or sets the path(s) or name(s) of the commands to retrieve.35 /// </summary>36 [Parameter(37 Position = 0,38 ValueFromPipeline = true,39 ValueFromPipelineByPropertyName = true,40 ParameterSetName = "AllCommandSet")]41 [ValidateNotNullOrEmpty]42 public string[] Name43 {44 get45 {46 return _names;47 }48 49 set50 {51 _nameContainsWildcard = false;52 _names = value;53 54 if (value != null)55 {56 foreach (string commandName in value)57 {58 if (WildcardPattern.ContainsWildcardCharacters(commandName))59 {60 _nameContainsWildcard = true;61 break;62 }63 }64 }65 }66 }67 68 private string[] _names;69 private bool _nameContainsWildcard;70 71 /// <summary>72 /// Gets or sets the verb parameter to the cmdlet.73 /// </summary>74 [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")]75 [ArgumentCompleter(typeof(VerbArgumentCompleter))]76 public string[] Verb77 {78 get79 {80 return _verbs;81 }82 83 set84 {85 value ??= Array.Empty<string>();86 87 _verbs = value;88 _verbPatterns = null;89 }90 }91 92 private string[] _verbs = Array.Empty<string>();93 94 /// <summary>95 /// Gets or sets the noun parameter to the cmdlet.96 /// </summary>97 [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")]98 [ArgumentCompleter(typeof(NounArgumentCompleter))]99 public string[] Noun100 {101 get102 {103 return _nouns;104 }105 106 set107 {108 value ??= Array.Empty<string>();109 110 _nouns = value;111 _nounPatterns = null;112 }113 }114 115 private string[] _nouns = Array.Empty<string>();116 117 /// <summary>118 /// Gets or sets the PSSnapin/Module parameter to the cmdlet.119 /// </summary>120 [Parameter(ValueFromPipelineByPropertyName = true)]121 [Alias("PSSnapin")]122 public string[] Module123 {124 get125 {126 return _modules;127 }128 129 set130 {131 value ??= Array.Empty<string>();132 133 _modules = value;134 _modulePatterns = null;135 136 _isModuleSpecified = true;137 }138 }139 140 private string[] _modules = Array.Empty<string>();141 private bool _isModuleSpecified = false;142 143 /// <summary>144 /// Gets or sets the ExcludeModule parameter to the cmdlet.145 /// </summary>146 [Parameter()]147 public string[] ExcludeModule148 {149 get150 {151 return _excludedModules;152 }153 154 set155 {156 value ??= Array.Empty<string>();157 158 _excludedModules = value;159 _excludedModulePatterns = null;160 }161 }162 163 private string[] _excludedModules = Array.Empty<string>();164 165 /// <summary>166 /// Gets or sets the FullyQualifiedModule parameter to the cmdlet.167 /// </summary>168 [Parameter(ValueFromPipelineByPropertyName = true)]169 public ModuleSpecification[] FullyQualifiedModule170 {171 get172 {173 return _moduleSpecifications;174 }175 176 set177 {178 if (value != null)179 {180 _moduleSpecifications = value;181 }182 183 _isFullyQualifiedModuleSpecified = true;184 }185 }186 187 private ModuleSpecification[] _moduleSpecifications = Array.Empty<ModuleSpecification>();188 private bool _isFullyQualifiedModuleSpecified = false;189 190 /// <summary>191 /// Gets or sets the type of the command to get.192 /// </summary>193 [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")]194 [Alias("Type")]195 public CommandTypes CommandType196 {197 get198 {199 return _commandType;200 }201 202 set203 {204 _commandType = value;205 _isCommandTypeSpecified = true;206 }207 }208 209 private CommandTypes _commandType = CommandTypes.All;210 private bool _isCommandTypeSpecified = false;211 212 /// <summary>213 /// The parameter representing the total number of commands that will214 /// be returned. If negative, all matching commands that are found will215 /// be returned.216 /// </summary>217 [Parameter(ValueFromPipelineByPropertyName = true)]218 public int TotalCount { get; set; } = -1;219 220 /// <summary>221 /// The parameter that determines if the CommandInfo or the string222 /// definition of the command is output.223 /// </summary>224 [Parameter(ValueFromPipelineByPropertyName = true)]225 public SwitchParameter Syntax226 {227 get228 {229 return _usage;230 }231 232 set233 {234 _usage = value;235 }236 }237 238 private bool _usage;239 240 /// <summary>241 /// This parameter causes the output to be packaged into ShowCommandInfo PSObject types242 /// needed to display GUI command information.243 /// </summary>244 [Parameter()]245 public SwitchParameter ShowCommandInfo { get; set; }246 247 /// <summary>248 /// The parameter that all additional arguments get bound to. These arguments are used249 /// when retrieving dynamic parameters from cmdlets that support them.250 /// </summary>251 [Parameter(Position = 1, ValueFromRemainingArguments = true)]252 [AllowNull]253 [AllowEmptyCollection]254 [Alias("Args")]255 public object[] ArgumentList { get; set; }256 257 /// <summary>258 /// The parameter that determines if additional matching commands should be returned.259 /// (Additional matching functions and aliases are returned from module tables)260 /// </summary>261 [Parameter(ValueFromPipelineByPropertyName = true)]262 public SwitchParameter All263 {264 get { return _all; }265 266 set { _all = value; }267 }268 269 private bool _all;270 271 /// <summary>272 /// The parameter that determines if additional matching commands from available modules should be returned.273 /// If set to true, only those commands currently in the session are returned.274 /// </summary>275 [Parameter(ValueFromPipelineByPropertyName = true)]276 public SwitchParameter ListImported277 {278 get279 {280 return _listImported;281 }282 283 set284 {285 _listImported = value;286 }287 }288 289 private bool _listImported;290 291 /// <summary>292 /// The parameter that filters commands returned to only include commands that have a parameter with a name that matches one of the ParameterName's arguments.293 /// </summary>294 [Parameter]295 [ValidateNotNullOrEmpty]296 public string[] ParameterName297 {298 get299 {300 return _parameterNames;301 }302 303 set304 {305 ArgumentNullException.ThrowIfNull(value);306 307 _parameterNames = value;308 _parameterNameWildcards = SessionStateUtilities.CreateWildcardsFromStrings(309 _parameterNames,310 WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase);311 }312 }313 314 private Collection<WildcardPattern> _parameterNameWildcards;315 private string[] _parameterNames;316 private HashSet<string> _matchedParameterNames;317 318 /// <summary>319 /// The parameter that filters commands returned to only include commands that have a parameter of a type that matches one of the ParameterType's arguments.320 /// </summary>321 [Parameter]322 [ValidateNotNullOrEmpty]323 public PSTypeName[] ParameterType324 {325 get326 {327 return _parameterTypes;328 }329 330 set331 {332 ArgumentNullException.ThrowIfNull(value);333 334 // if '...CimInstance#Win32_Process' is specified, then exclude '...CimInstance'335 List<PSTypeName> filteredParameterTypes = new List<PSTypeName>(value.Length);336 for (int i = 0; i < value.Length; i++)337 {338 PSTypeName ptn = value[i];339 340 if (value.Any(otherPtn => otherPtn.Name.StartsWith(ptn.Name + "#", StringComparison.OrdinalIgnoreCase)))341 {342 continue;343 }344 345 if ((i != 0) && (ptn.Type != null) && (ptn.Type.Equals(typeof(object))))346 {347 continue;348 }349 350 filteredParameterTypes.Add(ptn);351 }352 353 _parameterTypes = filteredParameterTypes.ToArray();354 }355 }356 357 private PSTypeName[] _parameterTypes;358 359 /// <summary>360 /// Gets or sets the parameter that enables using fuzzy matching.361 /// </summary>362 [Parameter(ParameterSetName = "AllCommandSet")]363 public SwitchParameter UseFuzzyMatching { get; set; }364 365 /// <summary>366 /// Gets or sets the minimum fuzzy matching distance.367 /// </summary>368 [Parameter(ParameterSetName = "AllCommandSet")]369 public uint FuzzyMinimumDistance { get; set; } = 5;370 371 private FuzzyMatcher _fuzzyMatcher;372 private List<CommandScore> _commandScores;373 374 /// <summary>375 /// Gets or sets the parameter that determines if return cmdlets based on abbreviation expansion.376 /// This means it matches cmdlets where the uppercase characters for the noun match377 /// the given characters. i.e., g-sgc would match Get-SomeGreatCmdlet.378 /// </summary>379 [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")]380 public SwitchParameter UseAbbreviationExpansion { get; set; }381 382 #endregion Definitions of cmdlet parameters383 384 #region Overrides385 386 /// <summary>387 /// Begin Processing.388 /// </summary>389 protected override void BeginProcessing()390 {391#if LEGACYTELEMETRY392 _timer.Start();393#endif394 if (UseFuzzyMatching)395 {396 _fuzzyMatcher = new FuzzyMatcher(FuzzyMinimumDistance);397 _commandScores = new List<CommandScore>();398 }399 400 if (ShowCommandInfo.IsPresent && Syntax.IsPresent)401 {402 ThrowTerminatingError(403 new ErrorRecord(404 new PSArgumentException(DiscoveryExceptions.GetCommandShowCommandInfoParamError),405 "GetCommandCannotSpecifySyntaxAndShowCommandInfoTogether",406 ErrorCategory.InvalidArgument,407 null));408 }409 }410 411 /// <summary>412 /// Method that implements get-command.413 /// </summary>414 protected override void ProcessRecord()415 {416 _commandsWritten.Clear();417 418 // Module and FullyQualifiedModule should not be specified at the same time.419 // Throw out terminating error if this is the case.420 if (_isModuleSpecified && _isFullyQualifiedModuleSpecified)421 {422 string errMsg = string.Format(CultureInfo.InvariantCulture, SessionStateStrings.GetContent_TailAndHeadCannotCoexist, "Module", "FullyQualifiedModule");423 ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null);424 ThrowTerminatingError(error);425 }426 427 // Initialize the module patterns428 _modulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Module, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);429 _excludedModulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(ExcludeModule, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);430 431 switch (ParameterSetName)432 {433 case "CmdletSet":434 AccumulateMatchingCmdlets();435 break;436 437 case "AllCommandSet":438 AccumulateMatchingCommands();439 break;440 441 default:442 Dbg.Assert(443 false,444 "Only the valid parameter set names should be used");445 break;446 }447 }448 449 /// <summary>450 /// Writes out the accumulated matching commands.451 /// </summary>452 protected override void EndProcessing()453 {454 // We do not show the pithy aliases (not of the format Verb-Noun) and applications by default.455 // We will show them only if the Name, All and totalCount are not specified.456 if ((this.Name == null) && (!_all) && TotalCount == -1 && !UseFuzzyMatching)457 {458 CommandTypes commandTypesToIgnore = 0;459 460 if (((this.CommandType & CommandTypes.Alias) != CommandTypes.Alias) || (!_isCommandTypeSpecified))461 {462 commandTypesToIgnore |= CommandTypes.Alias;463 }464 465 if (((_commandType & CommandTypes.Application) != CommandTypes.Application) ||466 (!_isCommandTypeSpecified))467 {468 commandTypesToIgnore |= CommandTypes.Application;469 }470 471 _accumulatedResults =472 _accumulatedResults.Where(473 commandInfo =>474 (((commandInfo.CommandType & commandTypesToIgnore) == 0) ||475 (commandInfo.Name.IndexOf('-') > 0))).ToList();476 }477 478 // report not-found errors for ParameterName and ParameterType if needed479 if ((_matchedParameterNames != null) && (ParameterName != null))480 {481 foreach (string requestedParameterName in ParameterName)482 {483 if (WildcardPattern.ContainsWildcardCharacters(requestedParameterName))484 {485 continue;486 }487 488 if (_matchedParameterNames.Contains(requestedParameterName))489 {490 continue;491 }492 493 string errorMessage = string.Format(494 CultureInfo.InvariantCulture,495 DiscoveryExceptions.CommandParameterNotFound,496 requestedParameterName);497 var exception = new ArgumentException(errorMessage, requestedParameterName);498 var errorRecord = new ErrorRecord(exception, "CommandParameterNotFound",499 ErrorCategory.ObjectNotFound, requestedParameterName);500 WriteError(errorRecord);501 }502 }503 504 // Only sort if they didn't fully specify a name)505 if ((_names == null) || (_nameContainsWildcard))506 {507 // Use the stable sorting to sort the result list508 _accumulatedResults = _accumulatedResults.Order(new CommandInfoComparer()).ToList();509 }510 511 OutputResultsHelper(_accumulatedResults);512 513 object pssenderInfo = Context.GetVariableValue(SpecialVariables.PSSenderInfoVarPath);514 if ((pssenderInfo != null) && (pssenderInfo is System.Management.Automation.Remoting.PSSenderInfo))515 {516 // Win8: 593295. Exchange has around 1000 cmdlets. During Import-PSSession,517 // Get-Command | select-object ..,HelpURI,... is run. HelpURI is a script property518 // which in turn runs Get-Help. Get-Help loads the help content and caches it in the process.519 // This caching is using around 190 MB. During V3, we have implemented HelpURI attribute520 // and this should solve it. In V2, we dont have this attribute and hence 3rd parties521 // run into the same issue. The fix here is to reset help cache whenever get-command is run on522 // a remote endpoint. In the worst case, this will affect get-help to run a little longer523 // after get-command is run..but that should be OK because get-help is used mainly for524 // document reading purposes and not in production.525 Context.HelpSystem.ResetHelpProviders();526 }527 }528 529 #endregion530 531 #region Private Methods532 533 private void OutputResultsHelper(IEnumerable<CommandInfo> results)534 {535 CommandOrigin origin = MyInvocation.CommandOrigin;536 537 if (UseFuzzyMatching)538 {539 _commandScores = _commandScores.OrderBy(static x => x.Score).ToList();540 results = _commandScores.Select(static x => x.Command);541 }542 543 int count = 0;544 foreach (CommandInfo result in results)545 {546 // Only write the command if it is visible to the requestor547 if (SessionState.IsVisible(origin, result))548 {549 // If the -syntax flag was specified, write the definition as a string550 // otherwise just return the object...551 if (Syntax)552 {553 if (!string.IsNullOrEmpty(result.Syntax))554 {555 PSObject syntax = GetSyntaxObject(result);556 557 WriteObject(syntax);558 }559 }560 else561 {562 if (ShowCommandInfo.IsPresent)563 {564 // Write output as ShowCommandCommandInfo object.565 WriteObject(566 ConvertToShowCommandInfo(result));567 }568 else569 {570 if (UseFuzzyMatching)571 {572 PSObject obj = new PSObject(result);573 obj.Properties.Add(new PSNoteProperty("Score", _commandScores[count].Score));574 WriteObject(obj);575 }576 else577 {578 WriteObject(result);579 }580 }581 }582 }583 584 count += 1;585 }586 587#if LEGACYTELEMETRY588 _timer.Stop();589 590 // No telemetry here - capturing the name of a command which we are not familiar with591 // may be confidential customer information592 // We want telemetry on commands people look for but don't exist - this should give us an idea593 // what sort of commands people expect but either don't exist, or maybe should be installed by default.594 // The StartsWith is to avoid logging telemetry when suggestion mode checks the595 // current directory for scripts/exes in the current directory and '.' is not in the path.596 if (count == 0 && Name != null && Name.Length > 0 && !Name[0].StartsWith(".\\", StringComparison.OrdinalIgnoreCase))597 {598 Telemetry.Internal.TelemetryAPI.ReportGetCommandFailed(Name, _timer.ElapsedMilliseconds);599 }600#endif601 }602 603 /// <summary>604 /// Creates the syntax output based on if the command is an alias, script, application or command.605 /// </summary>606 /// <param name="command">607 /// CommandInfo object containing the syntax to be output.608 /// </param>609 /// <returns>610 /// Syntax string cast as a PSObject for outputting.611 /// </returns>612 private PSObject GetSyntaxObject(CommandInfo command)613 {614 PSObject syntax = PSObject.AsPSObject(command.Syntax);615 616 // This is checking if the command name that's been passed in is one that was specified by a user,617 // if not then we have to assume they specified an alias or a wildcard and do some extra formatting for those,618 // if it is then just go with the default formatting.619 // So if a user runs Get-Command -Name del -Syntax the code will find del and the command it resolves to as Remove-Item620 // and attempt to return that, but as the user specified del we want to fiddle with the output a bit to make it clear621 // that's an alias but still give the Remove-Item syntax.622 if (this.Name != null && !Array.Exists(this.Name, name => name.Equals(command.Name, StringComparison.InvariantCultureIgnoreCase)))623 {624 string aliasName = _nameContainsWildcard ? command.Name : this.Name[0];625 626 IDictionary<string, AliasInfo> aliasTable = SessionState.Internal.GetAliasTable();627 foreach (KeyValuePair<string, AliasInfo> tableEntry in aliasTable)628 {629 if ((Array.Exists(this.Name, name => name.Equals(tableEntry.Key, StringComparison.InvariantCultureIgnoreCase)) &&630 tableEntry.Value.Definition == command.Name) ||631 (_nameContainsWildcard && tableEntry.Value.Definition == command.Name))632 {633 aliasName = tableEntry.Key;634 break;635 }636 }637 638 string replacedSyntax = string.Empty;639 switch (command)640 {641 case ExternalScriptInfo externalScript:642 replacedSyntax = string.Format(643 "{0} (alias) -> {1}{2}{3}",644 aliasName,645 string.Format("{0}{1}", externalScript.Path, Environment.NewLine),646 Environment.NewLine,647 command.Syntax.Replace(command.Name, aliasName));648 break;649 case ApplicationInfo app:650 replacedSyntax = app.Path;651 break;652 default:653 if (aliasName.Equals(command.Name))654 {655 replacedSyntax = command.Syntax;656 }657 else658 {659 replacedSyntax = string.Format(660 "{0} (alias) -> {1}{2}{3}",661 aliasName,662 command.Name,663 Environment.NewLine,664 command.Syntax.Replace(command.Name, aliasName));665 }666 667 break;668 }669 670 syntax = PSObject.AsPSObject(replacedSyntax);671 }672 673 syntax.IsHelpObject = true;674 675 return syntax;676 }677 678 /// <summary>679 /// The comparer to sort CommandInfo objects in the result list.680 /// </summary>681 private sealed class CommandInfoComparer : IComparer<CommandInfo>682 {683 /// <summary>684 /// Compare two CommandInfo objects first by their command types, and if they685 /// are with the same command type, then we compare their names.686 /// </summary>687 /// <param name="x"></param>688 /// <param name="y"></param>689 /// <returns></returns>690 public int Compare(CommandInfo x, CommandInfo y)691 {692 if ((int)x.CommandType < (int)y.CommandType)693 {694 return -1;695 }696 else if ((int)x.CommandType > (int)y.CommandType)697 {698 return 1;699 }700 else701 {702 return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);703 }704 }705 }706 707 private void AccumulateMatchingCmdlets()708 {709 _commandType = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Alias | CommandTypes.Configuration;710 711 Collection<string> commandNames = new Collection<string>();712 commandNames.Add("*");713 AccumulateMatchingCommands(commandNames);714 }715 716 private bool IsNounVerbMatch(CommandInfo command)717 {718 bool result = false;719 720 do // false loop721 {722 _verbPatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Verb, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);723 724 _nounPatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Noun, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);725 726 if (!string.IsNullOrEmpty(command.ModuleName))727 {728 if (_excludedModulePatterns is not null729 && _excludedModulePatterns.Count > 0730 && SessionStateUtilities.MatchesAnyWildcardPattern(command.ModuleName, _excludedModulePatterns, true))731 {732 break;733 }734 735 if (_isFullyQualifiedModuleSpecified)736 {737 if (!_moduleSpecifications.Any(738 moduleSpecification =>739 ModuleIntrinsics.IsModuleMatchingModuleSpec(command.Module, moduleSpecification)))740 {741 break;742 }743 }744 else if (!SessionStateUtilities.MatchesAnyWildcardPattern(command.ModuleName, _modulePatterns, true))745 {746 break;747 }748 }749 else750 {751 if (_modulePatterns.Count > 0 || _moduleSpecifications.Length > 0)752 {753 // Its not a match if we are filtering on a PSSnapin/Module name but the cmdlet doesn't have one.754 break;755 }756 }757 758 // Get the noun and verb to check...759 string verb;760 string noun;761 CmdletInfo cmdlet = command as CmdletInfo;762 if (cmdlet != null)763 {764 verb = cmdlet.Verb;765 noun = cmdlet.Noun;766 }767 else768 {769 if (!CmdletInfo.SplitCmdletName(command.Name, out verb, out noun))770 break;771 }772 773 if (!SessionStateUtilities.MatchesAnyWildcardPattern(verb, _verbPatterns, true))774 {775 break;776 }777 778 if (!SessionStateUtilities.MatchesAnyWildcardPattern(noun, _nounPatterns, true))779 {780 break;781 }782 783 result = true;784 } while (false);785 786 return result;787 }788 789 /// <summary>790 /// Writes out the commands for the AllCommandSet using the specified CommandType.791 /// </summary>792 private void AccumulateMatchingCommands()793 {794 Collection<string> commandNames =795 SessionStateUtilities.ConvertArrayToCollection<string>(this.Name);796 797 if (commandNames.Count == 0)798 {799 commandNames.Add("*");800 }801 802 AccumulateMatchingCommands(commandNames);803 }804 805 private void AccumulateMatchingCommands(IEnumerable<string> commandNames)806 {807 // First set the search options808 809 SearchResolutionOptions options = SearchResolutionOptions.None;810 if (All)811 {812 options = SearchResolutionOptions.SearchAllScopes;813 }814 815 if (UseAbbreviationExpansion)816 {817 options |= SearchResolutionOptions.UseAbbreviationExpansion;818 }819 820 if ((this.CommandType & CommandTypes.Alias) != 0)821 {822 options |= SearchResolutionOptions.ResolveAliasPatterns;823 }824 825 if ((this.CommandType & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Configuration)) != 0)826 {827 options |= SearchResolutionOptions.ResolveFunctionPatterns;828 }829 830 foreach (string commandName in commandNames)831 {832 try833 {834 // Determine if the command name is module-qualified, and search835 // available modules for the command.836 string moduleName;837 string plainCommandName = Utils.ParseCommandName(commandName, out moduleName);838 bool isModuleQualified = (moduleName != null);839 840 // If they've specified a module name, we can do some smarter filtering.841 // Otherwise, we have to filter everything.842 if ((this.Module.Length == 1) && (!WildcardPattern.ContainsWildcardCharacters(this.Module[0])))843 {844 moduleName = this.Module[0];845 }846 847 bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName) || UseAbbreviationExpansion || UseFuzzyMatching;848 if (isPattern)849 {850 options |= SearchResolutionOptions.CommandNameIsPattern;851 }852 853 // Try to initially find the command in the available commands854 int count = 0;855 bool isDuplicate;856 bool resultFound = FindCommandForName(options, commandName, isPattern, true, ref count, out isDuplicate);857 858 // If we didn't find the command, or if it had a wildcard, also see if it859 // is in an available module860 if (!resultFound || isPattern)861 {862 // If the command name had no wildcards or was module-qualified,863 // import the module so that we can return the fully structured data.864 // This uses the same code path as module auto-loading.865 if ((!isPattern) || (!string.IsNullOrEmpty(moduleName)))866 {867 string tempCommandName = commandName;868 if ((!isModuleQualified) && (!string.IsNullOrEmpty(moduleName)))869 {870 tempCommandName = moduleName + "\\" + commandName;871 }872 873 try874 {875 CommandDiscovery.LookupCommandInfo(tempCommandName, this.MyInvocation.CommandOrigin, this.Context);876 }877 catch (CommandNotFoundException)878 {879 // Ignore, LookupCommandInfo doesn't handle wildcards.880 }881 882 resultFound = FindCommandForName(options, commandName, isPattern, false, ref count, out isDuplicate);883 }884 // Show additional commands from available modules only if ListImported is not specified885 else if (!ListImported)886 {887 if (TotalCount < 0 || count < TotalCount)888 {889 IEnumerable<CommandInfo> commands;890 if (UseFuzzyMatching)891 {892 foreach (var commandScore in ModuleUtils.GetFuzzyMatchingCommands(893 plainCommandName,894 Context,895 MyInvocation.CommandOrigin,896 _fuzzyMatcher,897 rediscoverImportedModules: true,898 moduleVersionRequired: _isFullyQualifiedModuleSpecified))899 {900 _commandScores.Add(commandScore);901 }902 903 commands = _commandScores.Select(static x => x.Command);904 }905 else906 {907 commands = ModuleUtils.GetMatchingCommands(908 plainCommandName,909 Context,910 MyInvocation.CommandOrigin,911 rediscoverImportedModules: true,912 moduleVersionRequired: _isFullyQualifiedModuleSpecified,913 useAbbreviationExpansion: UseAbbreviationExpansion);914 }915 916 foreach (CommandInfo command in commands)917 {918 // Cannot pass in "command" by ref (foreach iteration variable)919 CommandInfo current = command;920 921 if (IsCommandMatch(ref current, out isDuplicate) && (!IsCommandInResult(current)) && IsParameterMatch(current))922 {923 _accumulatedResults.Add(current);924 925 // Make sure we don't exceed the TotalCount parameter926 ++count;927 928 if (TotalCount >= 0 && count >= TotalCount)929 {930 break;931 }932 }933 }934 }935 }936 }937 938 // If we are trying to match a single specific command name (no glob characters)939 // then we need to write an error if we didn't find it.940 if (!isDuplicate)941 {942 if (!resultFound && !isPattern)943 {944 CommandNotFoundException e =945 new CommandNotFoundException(946 commandName,947 null,948 "CommandNotFoundException",949 DiscoveryExceptions.CommandNotFoundException);950 951 WriteError(952 new ErrorRecord(953 e.ErrorRecord,954 e));955 continue;956 }957 }958 }959 catch (CommandNotFoundException exception)960 {961 WriteError(962 new ErrorRecord(963 exception.ErrorRecord,964 exception));965 }966 }967 }968 969 private bool FindCommandForName(SearchResolutionOptions options, string commandName, bool isPattern, bool emitErrors, ref int currentCount, out bool isDuplicate)970 {971 var searcher = new CommandSearcher(972 commandName,973 options,974 CommandType,975 Context,976 _fuzzyMatcher);977 978 bool resultFound = false;979 isDuplicate = false;980 981 while (true)982 {983 try984 {985 if (!searcher.MoveNext())986 {987 break;988 }989 }990 catch (ArgumentException argumentException)991 {992 if (emitErrors)993 {994 WriteError(new ErrorRecord(argumentException, "GetCommandInvalidArgument", ErrorCategory.SyntaxError, null));995 }996 997 continue;998 }999 catch (PathTooLongException pathTooLong)1000 {1001 if (emitErrors)1002 {1003 WriteError(new ErrorRecord(pathTooLong, "GetCommandInvalidArgument", ErrorCategory.SyntaxError, null));1004 }1005 1006 continue;1007 }1008 catch (FileLoadException fileLoadException)1009 {1010 if (emitErrors)1011 {1012 WriteError(new ErrorRecord(fileLoadException, "GetCommandFileLoadError", ErrorCategory.ReadError, null));1013 }1014 1015 continue;1016 }1017 catch (MetadataException metadataException)1018 {1019 if (emitErrors)1020 {1021 WriteError(new ErrorRecord(metadataException, "GetCommandMetadataError", ErrorCategory.MetadataError, null));1022 }1023 1024 continue;1025 }1026 catch (FormatException formatException)1027 {1028 if (emitErrors)1029 {1030 WriteError(new ErrorRecord(formatException, "GetCommandBadFileFormat", ErrorCategory.InvalidData, null));1031 }1032 1033 continue;1034 }1035 1036 CommandInfo current = ((IEnumerator<CommandInfo>)searcher).Current;1037 1038 // skip private commands as early as possible1039 // (i.e. before setting "result found" flag and before trying to use ArgumentList parameter)1040 // see bugs Windows 7: #520498 and #5204701041 CommandOrigin origin = this.MyInvocation.CommandOrigin;1042 if (!SessionState.IsVisible(origin, current))1043 {1044 continue;1045 }1046 1047 bool tempResultFound = IsCommandMatch(ref current, out isDuplicate);1048 1049 if (tempResultFound && (!IsCommandInResult(current)))1050 {1051 resultFound = true;1052 if (IsParameterMatch(current))1053 {1054 // Make sure we don't exceed the TotalCount parameter1055 ++currentCount;1056 1057 if (TotalCount >= 0 && currentCount > TotalCount)1058 {1059 break;1060 }1061 1062 if (UseFuzzyMatching)1063 {1064 if (_fuzzyMatcher.IsFuzzyMatch(current.Name, commandName, out int score))1065 {1066 _commandScores.Add(new CommandScore(current, score));1067 }1068 }1069 1070 _accumulatedResults.Add(current);1071 1072 if (ArgumentList != null)1073 {1074 // Don't iterate the enumerator any more. If -arguments was specified, then we stop at the first match1075 break;1076 }1077 }1078 1079 // Only for this case, the loop should exit1080 // Get-Command Foo1081 if (isPattern || All || TotalCount != -1 || _isCommandTypeSpecified || _isModuleSpecified || _isFullyQualifiedModuleSpecified)1082 {1083 continue;1084 }1085 else1086 {1087 break;1088 }1089 }1090 }1091 1092 if (All)1093 {1094 // Get additional matching commands from module tables.1095 foreach (CommandInfo command in GetMatchingCommandsFromModules(commandName))1096 {1097 CommandInfo c = command;1098 bool tempResultFound = IsCommandMatch(ref c, out isDuplicate);1099 if (tempResultFound)1100 {1101 resultFound = true;1102 if (!IsCommandInResult(command) && IsParameterMatch(c))1103 {1104 ++currentCount;1105 1106 if (TotalCount >= 0 && currentCount > TotalCount)1107 {1108 break;1109 }1110 1111 _accumulatedResults.Add(c);1112 }1113 // Make sure we don't exceed the TotalCount parameter1114 }1115 }1116 }1117 1118 return resultFound;1119 }1120 1121 /// <summary>1122 /// Determines if the specific command information has already been1123 /// written out based on the path or definition.1124 /// </summary>1125 /// <param name="info">1126 /// The command information to check for duplication.1127 /// </param>1128 /// <returns>1129 /// true if the command has already been written out.1130 /// </returns>1131 private bool IsDuplicate(CommandInfo info)1132 {1133 bool result = false;1134 string key = null;1135 1136 do // false loop1137 {1138 ApplicationInfo appInfo = info as ApplicationInfo;1139 if (appInfo != null)1140 {1141 key = appInfo.Path;1142 break;1143 }1144 1145 CmdletInfo cmdletInfo = info as CmdletInfo;1146 if (cmdletInfo != null)1147 {1148 key = cmdletInfo.FullName;1149 break;1150 }1151 1152 ScriptInfo scriptInfo = info as ScriptInfo;1153 if (scriptInfo != null)1154 {1155 key = scriptInfo.Definition;1156 break;1157 }1158 1159 ExternalScriptInfo externalScriptInfo = info as ExternalScriptInfo;1160 if (externalScriptInfo != null)1161 {1162 key = externalScriptInfo.Path;1163 break;1164 }1165 } while (false);1166 1167 if (key != null)1168 {1169 if (_commandsWritten.ContainsKey(key))1170 {1171 result = true;1172 }1173 else1174 {1175 _commandsWritten.Add(key, info);1176 }1177 }1178 1179 return result;1180 }1181 1182 private bool IsParameterMatch(CommandInfo commandInfo)1183 {1184 if ((this.ParameterName == null) && (this.ParameterType == null))1185 {1186 return true;1187 }1188 1189 _matchedParameterNames ??= new HashSet<string>(StringComparer.OrdinalIgnoreCase);1190 1191 IEnumerable<ParameterMetadata> commandParameters = null;1192 try1193 {1194 IDictionary<string, ParameterMetadata> tmp = commandInfo.Parameters;1195 if (tmp != null)1196 {1197 commandParameters = tmp.Values;1198 }1199 }1200 catch (Exception)