2020-03-24 11:08:37 -07:00
// Copyright (c) Microsoft Corporation.
2018-02-13 09:23:53 -08:00
// Licensed under the MIT License.
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
2016-04-28 13:41:25 -07:00
using System.Diagnostics ;
2019-04-30 10:25:11 +05:00
using System.Globalization ;
2016-03-30 14:20:52 -07:00
using System.IO ;
2019-04-30 10:25:11 +05:00
using System.Linq ;
2016-03-30 14:20:52 -07:00
using System.Management.Automation ;
2019-01-10 13:35:30 -08:00
using System.Management.Automation.Internal ;
2016-03-30 14:20:52 -07:00
using System.Management.Automation.Language ;
using Dbg = System . Management . Automation . Diagnostics ;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// The get-command cmdlet. It uses the command discovery APIs to find one or more
/// commands of the given name. It returns an instance of CommandInfo for each
/// command that is found.
/// </summary>
2019-12-02 10:25:56 -08:00
[Cmdlet(VerbsCommon.Get, "Command", DefaultParameterSetName = "CmdletSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096579")]
2016-03-30 14:20:52 -07:00
[OutputType(typeof(AliasInfo), typeof(ApplicationInfo), typeof(FunctionInfo),
typeof(CmdletInfo), typeof(ExternalScriptInfo), typeof(FilterInfo),
2018-04-02 16:18:56 -07:00
typeof(string), typeof(PSObject))]
2016-03-30 14:20:52 -07:00
public sealed class GetCommandCommand : PSCmdlet
{
#region Definitions of cmdlet parameters
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the path(s) or name(s) of the commands to retrieve.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(
Position = 0,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "AllCommandSet")]
[ValidateNotNullOrEmpty]
public string [] Name
{
get
{
2016-07-29 13:02:49 -07:00
return _names ;
2016-03-30 14:20:52 -07:00
}
set
{
2016-07-29 13:02:49 -07:00
_nameContainsWildcard = false ;
_names = value ;
2016-03-30 14:20:52 -07:00
if ( value != null )
{
foreach ( string commandName in value )
{
if ( WildcardPattern . ContainsWildcardCharacters ( commandName ))
{
2016-07-29 13:02:49 -07:00
_nameContainsWildcard = true ;
2016-03-30 14:20:52 -07:00
break ;
}
}
}
}
2018-12-08 18:53:30 +02:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private string [] _names ;
private bool _nameContainsWildcard ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the verb parameter to the cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")]
public string [] Verb
{
get
{
2016-07-29 13:02:49 -07:00
return _verbs ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
set
{
2022-07-25 21:11:29 +04:00
value ??= Array . Empty < string >();
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_verbs = value ;
_verbPatterns = null ;
2018-12-08 18:53:30 +02:00
}
}
2018-12-23 22:20:06 -08:00
2019-03-10 06:53:31 +05:00
private string [] _verbs = Array . Empty < string >();
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the noun parameter to the cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "CmdletSet")]
[ArgumentCompleter(typeof(NounArgumentCompleter))]
public string [] Noun
{
get
{
2016-07-29 13:02:49 -07:00
return _nouns ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
set
{
2022-07-25 21:11:29 +04:00
value ??= Array . Empty < string >();
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_nouns = value ;
_nounPatterns = null ;
2018-12-08 18:53:30 +02:00
}
}
2018-12-23 22:20:06 -08:00
2019-03-10 06:53:31 +05:00
private string [] _nouns = Array . Empty < string >();
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the PSSnapin/Module parameter to the cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
[Alias("PSSnapin")]
public string [] Module
{
get
{
return _modules ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
set
{
2022-07-25 21:11:29 +04:00
value ??= Array . Empty < string >();
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
_modules = value ;
_modulePatterns = null ;
2016-07-29 13:02:49 -07:00
_isModuleSpecified = true ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2019-03-10 06:53:31 +05:00
private string [] _modules = Array . Empty < string >();
2016-07-29 13:02:49 -07:00
private bool _isModuleSpecified = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the FullyQualifiedModule parameter to the cmdlet.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
public ModuleSpecification [] FullyQualifiedModule
{
get
{
return _moduleSpecifications ;
}
set
{
if ( value != null )
{
_moduleSpecifications = value ;
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_isFullyQualifiedModuleSpecified = true ;
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2019-03-10 06:53:31 +05:00
private ModuleSpecification [] _moduleSpecifications = Array . Empty < ModuleSpecification >();
2016-07-29 13:02:49 -07:00
private bool _isFullyQualifiedModuleSpecified = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets or sets the type of the command to get.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")]
[Alias("Type")]
public CommandTypes CommandType
{
get
{
2016-07-29 13:02:49 -07:00
return _commandType ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
set
{
2016-07-29 13:02:49 -07:00
_commandType = value ;
_isCommandTypeSpecified = true ;
2018-12-08 18:53:30 +02:00
}
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private CommandTypes _commandType = CommandTypes . All ;
private bool _isCommandTypeSpecified = false ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// The parameter representing the total number of commands that will
/// be returned. If negative, all matching commands that are found will
/// be returned.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
2016-08-02 13:29:09 -07:00
public int TotalCount { get ; set ; } = - 1 ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// The parameter that determines if the CommandInfo or the string
/// definition of the command is output.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
public SwitchParameter Syntax
{
get
{
2016-07-29 13:02:49 -07:00
return _usage ;
2016-03-30 14:20:52 -07:00
}
set
{
2016-07-29 13:02:49 -07:00
_usage = value ;
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _usage ;
2016-03-30 14:20:52 -07:00
/// <summary>
/// This parameter causes the output to be packaged into ShowCommandInfo PSObject types
/// needed to display GUI command information.
/// </summary>
[Parameter()]
2016-08-02 13:29:09 -07:00
public SwitchParameter ShowCommandInfo { get ; set ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// The parameter that all additional arguments get bound to. These arguments are used
/// when retrieving dynamic parameters from cmdlets that support them.
/// </summary>
[Parameter(Position = 1, ValueFromRemainingArguments = true)]
[AllowNull]
[AllowEmptyCollection]
[Alias("Args")]
2016-08-02 13:29:09 -07:00
public object [] ArgumentList { get ; set ; }
2016-03-30 14:20:52 -07:00
/// <summary>
2017-01-16 13:31:14 -08:00
/// The parameter that determines if additional matching commands should be returned.
2016-03-30 14:20:52 -07:00
/// (Additional matching functions and aliases are returned from module tables)
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
public SwitchParameter All
{
2016-08-02 13:29:09 -07:00
get { return _all ; }
2018-12-23 22:20:06 -08:00
2016-08-02 13:29:09 -07:00
set { _all = value ; }
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _all ;
2016-03-30 14:20:52 -07:00
/// <summary>
2017-01-16 13:31:14 -08:00
/// The parameter that determines if additional matching commands from available modules should be returned.
/// If set to true, only those commands currently in the session are returned.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
public SwitchParameter ListImported
{
get
{
2016-07-29 13:02:49 -07:00
return _listImported ;
2016-03-30 14:20:52 -07:00
}
set
{
2016-07-29 13:02:49 -07:00
_listImported = value ;
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
private bool _listImported ;
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// 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.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public string [] ParameterName
{
2021-01-09 07:44:38 +00:00
get
{
return _parameterNames ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
set
{
2021-07-06 07:04:13 +02:00
_parameterNames = value ?? throw new ArgumentNullException ( nameof ( value ));
2016-07-29 13:02:49 -07:00
_parameterNameWildcards = SessionStateUtilities . CreateWildcardsFromStrings (
_parameterNames ,
2016-03-30 14:20:52 -07:00
WildcardOptions . CultureInvariant | WildcardOptions . IgnoreCase );
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
private Collection < WildcardPattern > _parameterNameWildcards ;
private string [] _parameterNames ;
private HashSet < string > _matchedParameterNames ;
/// <summary>
2019-01-06 04:40:24 +05:00
/// 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.
2016-03-30 14:20:52 -07:00
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public PSTypeName [] ParameterType
{
get
{
2016-07-29 13:02:49 -07:00
return _parameterTypes ;
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
set
{
2020-07-30 16:06:38 -07:00
if ( value == null )
2016-03-30 14:20:52 -07:00
{
2021-07-06 07:04:13 +02:00
throw new ArgumentNullException ( nameof ( value ));
2016-03-30 14:20:52 -07:00
}
// if '...CimInstance#Win32_Process' is specified, then exclude '...CimInstance'
List < PSTypeName > filteredParameterTypes = new List < PSTypeName >( value . Length );
for ( int i = 0 ; i < value . Length ; i ++)
{
PSTypeName ptn = value [ i ];
if ( value . Any ( otherPtn => otherPtn . Name . StartsWith ( ptn . Name + "#" , StringComparison . OrdinalIgnoreCase )))
{
continue ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if (( i != 0 ) && ( ptn . Type != null ) && ( ptn . Type . Equals ( typeof ( object ))))
{
continue ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
filteredParameterTypes . Add ( ptn );
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_parameterTypes = filteredParameterTypes . ToArray ();
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
private PSTypeName [] _parameterTypes ;
2019-01-10 13:35:30 -08:00
/// <summary>
/// Gets or sets the parameter that enables using fuzzy matching.
/// </summary>
[Parameter(ParameterSetName = "AllCommandSet")]
public SwitchParameter UseFuzzyMatching { get ; set ; }
2022-10-12 16:08:20 -07:00
/// <summary>
/// Gets or sets the minimum fuzzy matching distance.
/// </summary>
[Parameter(ParameterSetName = "AllCommandSet")]
public uint FuzzyMinimumDistance { get ; set ; } = 5 ;
private List < CommandScore > _commandScores = new List < CommandScore >();
2019-01-10 13:35:30 -08:00
2019-01-10 16:11:43 -08:00
/// <summary>
/// Gets or sets the parameter that determines if return cmdlets based on abbreviation expansion.
/// This means it matches cmdlets where the uppercase characters for the noun match
/// the given characters. i.e., g-sgc would match Get-SomeGreatCmdlet.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")]
public SwitchParameter UseAbbreviationExpansion { get ; set ; }
2016-03-30 14:20:52 -07:00
#endregion Definitions of cmdlet parameters
#region Overrides
/// <summary>
2019-01-06 04:40:24 +05:00
/// Begin Processing.
2016-03-30 14:20:52 -07:00
/// </summary>
protected override void BeginProcessing ()
{
2019-08-15 16:13:53 -07:00
#if LEGACYTELEMETRY
2016-04-28 13:41:25 -07:00
_timer . Start ();
2019-08-15 16:13:53 -07:00
#endif
2016-03-30 14:20:52 -07:00
base . BeginProcessing ();
if ( ShowCommandInfo . IsPresent && Syntax . IsPresent )
{
ThrowTerminatingError (
new ErrorRecord (
new PSArgumentException ( DiscoveryExceptions . GetCommandShowCommandInfoParamError ),
"GetCommandCannotSpecifySyntaxAndShowCommandInfoTogether" ,
ErrorCategory . InvalidArgument ,
null ));
}
}
/// <summary>
2019-01-10 02:07:41 +05:00
/// Method that implements get-command.
2016-03-30 14:20:52 -07:00
/// </summary>
protected override void ProcessRecord ()
{
2019-11-07 11:59:24 -08:00
_commandsWritten . Clear ();
2016-03-30 14:20:52 -07:00
// Module and FullyQualifiedModule should not be specified at the same time.
// Throw out terminating error if this is the case.
2016-07-29 13:02:49 -07:00
if ( _isModuleSpecified && _isFullyQualifiedModuleSpecified )
2016-03-30 14:20:52 -07:00
{
2018-12-31 20:44:16 +05:00
string errMsg = string . Format ( CultureInfo . InvariantCulture , SessionStateStrings . GetContent_TailAndHeadCannotCoexist , "Module" , "FullyQualifiedModule" );
2016-03-30 14:20:52 -07:00
ErrorRecord error = new ErrorRecord ( new InvalidOperationException ( errMsg ), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether" , ErrorCategory . InvalidOperation , null );
ThrowTerminatingError ( error );
}
// Initialize the module patterns
2022-07-25 21:11:29 +04:00
_modulePatterns ??= SessionStateUtilities . CreateWildcardsFromStrings ( Module , WildcardOptions . IgnoreCase | WildcardOptions . CultureInvariant );
2016-03-30 14:20:52 -07:00
switch ( ParameterSetName )
{
case "CmdletSet" :
AccumulateMatchingCmdlets ();
break ;
case "AllCommandSet" :
AccumulateMatchingCommands ();
break ;
default :
Dbg . Assert (
false ,
"Only the valid parameter set names should be used" );
break ;
}
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Writes out the accumulated matching commands.
2016-03-30 14:20:52 -07:00
/// </summary>
protected override void EndProcessing ()
{
2017-01-16 13:31:14 -08:00
// We do not show the pithy aliases (not of the format Verb-Noun) and applications by default.
2016-03-30 14:20:52 -07:00
// We will show them only if the Name, All and totalCount are not specified.
2020-07-30 16:06:38 -07:00
if (( this . Name == null ) && (! _all ) && TotalCount == - 1 && ! UseFuzzyMatching )
2016-03-30 14:20:52 -07:00
{
CommandTypes commandTypesToIgnore = 0 ;
2016-07-29 13:02:49 -07:00
if ((( this . CommandType & CommandTypes . Alias ) != CommandTypes . Alias ) || (! _isCommandTypeSpecified ))
2016-03-30 14:20:52 -07:00
{
commandTypesToIgnore |= CommandTypes . Alias ;
}
2016-07-29 13:02:49 -07:00
if ((( _commandType & CommandTypes . Application ) != CommandTypes . Application ) ||
(! _isCommandTypeSpecified ))
2016-03-30 14:20:52 -07:00
{
commandTypesToIgnore |= CommandTypes . Application ;
}
2016-07-29 13:02:49 -07:00
_accumulatedResults =
_accumulatedResults . Where (
2016-03-30 14:20:52 -07:00
commandInfo =>
((( commandInfo . CommandType & commandTypesToIgnore ) == 0 ) ||
( commandInfo . Name . IndexOf ( '-' ) > 0 ))). ToList ();
}
// report not-found errors for ParameterName and ParameterType if needed
if (( _matchedParameterNames != null ) && ( ParameterName != null ))
{
foreach ( string requestedParameterName in ParameterName )
{
if ( WildcardPattern . ContainsWildcardCharacters ( requestedParameterName ))
{
continue ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ( _matchedParameterNames . Contains ( requestedParameterName ))
{
continue ;
}
string errorMessage = string . Format (
CultureInfo . InvariantCulture ,
DiscoveryExceptions . CommandParameterNotFound ,
requestedParameterName );
var exception = new ArgumentException ( errorMessage , requestedParameterName );
var errorRecord = new ErrorRecord ( exception , "CommandParameterNotFound" ,
ErrorCategory . ObjectNotFound , requestedParameterName );
WriteError ( errorRecord );
}
}
// Only sort if they didn't fully specify a name)
2020-07-30 16:06:38 -07:00
if (( _names == null ) || ( _nameContainsWildcard ))
2016-03-30 14:20:52 -07:00
{
// Use the stable sorting to sort the result list
2021-04-13 00:44:05 +05:00
_accumulatedResults = _accumulatedResults . OrderBy ( static a => a , new CommandInfoComparer ()). ToList ();
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
OutputResultsHelper ( _accumulatedResults );
2016-03-30 14:20:52 -07:00
object pssenderInfo = Context . GetVariableValue ( SpecialVariables . PSSenderInfoVarPath );
2018-06-26 02:37:09 +05:00
if (( pssenderInfo != null ) && ( pssenderInfo is System . Management . Automation . Remoting . PSSenderInfo ))
2016-03-30 14:20:52 -07:00
{
2017-01-16 13:31:14 -08:00
// Win8: 593295. Exchange has around 1000 cmdlets. During Import-PSSession,
2016-03-30 14:20:52 -07:00
// Get-Command | select-object ..,HelpURI,... is run. HelpURI is a script property
// which in turn runs Get-Help. Get-Help loads the help content and caches it in the process.
// This caching is using around 190 MB. During V3, we have implemented HelpURI attribute
// and this should solve it. In V2, we dont have this attribute and hence 3rd parties
// run into the same issue. The fix here is to reset help cache whenever get-command is run on
// a remote endpoint. In the worst case, this will affect get-help to run a little longer
// after get-command is run..but that should be OK because get-help is used mainly for
// document reading purposes and not in production.
Context . HelpSystem . ResetHelpProviders ();
}
}
#endregion
#region Private Methods
private void OutputResultsHelper ( IEnumerable < CommandInfo > results )
{
CommandOrigin origin = this . MyInvocation . CommandOrigin ;
2019-01-10 13:35:30 -08:00
if ( UseFuzzyMatching )
{
2022-10-12 16:08:20 -07:00
_commandScores = _commandScores
. Where ( x => x . Score <= FuzzyMinimumDistance )
. OrderBy ( static x => x . Score )
. ToList ();
results = _commandScores . Select ( static x => x . Command );
2019-01-10 13:35:30 -08:00
}
2016-04-28 13:41:25 -07:00
int count = 0 ;
2016-03-30 14:20:52 -07:00
foreach ( CommandInfo result in results )
{
// Only write the command if it is visible to the requestor
if ( SessionState . IsVisible ( origin , result ))
{
// If the -syntax flag was specified, write the definition as a string
// otherwise just return the object...
if ( Syntax )
{
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( result . Syntax ))
2016-03-30 14:20:52 -07:00
{
2020-04-13 19:01:08 +01:00
PSObject syntax = GetSyntaxObject ( result );
2016-03-30 14:20:52 -07:00
WriteObject ( syntax );
}
}
else
{
if ( ShowCommandInfo . IsPresent )
{
// Write output as ShowCommandCommandInfo object.
WriteObject (
ConvertToShowCommandInfo ( result ));
}
else
{
2022-10-12 16:08:20 -07:00
if ( UseFuzzyMatching )
{
PSObject obj = new PSObject ( result );
obj . Properties . Add ( new PSNoteProperty ( "Score" , _commandScores [ count ]. Score ));
WriteObject ( obj );
}
else
{
WriteObject ( result );
}
2016-03-30 14:20:52 -07:00
}
}
}
2022-10-12 16:08:20 -07:00
count += 1 ;
2016-03-30 14:20:52 -07:00
}
2016-04-28 13:41:25 -07:00
2019-08-15 16:13:53 -07:00
#if LEGACYTELEMETRY
2016-04-28 13:41:25 -07:00
_timer . Stop ();
2019-08-15 16:13:53 -07:00
// No telemetry here - capturing the name of a command which we are not familiar with
// may be confidential customer information
2016-08-28 05:44:55 +00:00
// We want telementry on commands people look for but don't exist - this should give us an idea
2016-04-28 13:41:25 -07:00
// what sort of commands people expect but either don't exist, or maybe should be installed by default.
// The StartsWith is to avoid logging telemetry when suggestion mode checks the
// current directory for scripts/exes in the current directory and '.' is not in the path.
2016-04-28 13:41:25 -07:00
if ( count == 0 && Name != null && Name . Length > 0 && ! Name [ 0 ]. StartsWith ( ".\\" , StringComparison . OrdinalIgnoreCase ))
2016-04-28 13:41:25 -07:00
{
Telemetry . Internal . TelemetryAPI . ReportGetCommandFailed ( Name , _timer . ElapsedMilliseconds );
}
2017-07-17 09:44:54 -07:00
#endif
2016-03-30 14:20:52 -07:00
}
2020-04-13 19:01:08 +01:00
/// <summary>
/// Creates the syntax output based on if the command is an alias, script, application or command.
/// </summary>
/// <param name="command">
/// CommandInfo object containing the syntax to be output.
/// </param>
/// <returns>
/// Syntax string cast as a PSObject for outputting.
/// </returns>
private PSObject GetSyntaxObject ( CommandInfo command )
{
PSObject syntax = PSObject . AsPSObject ( command . Syntax );
// This is checking if the command name that's been passed in is one that was specified by a user,
// if not then we have to assume they specified an alias or a wildcard and do some extra formatting for those,
// if it is then just go with the default formatting.
// So if a user runs Get-Command -Name del -Syntax the code will find del and the command it resolves to as Remove-Item
// and attempt to return that, but as the user specified del we want to fiddle with the output a bit to make it clear
// that's an alias but still give the Remove-Item syntax.
if ( this . Name != null && ! Array . Exists ( this . Name , name => name . Equals ( command . Name , StringComparison . InvariantCultureIgnoreCase )))
{
string aliasName = _nameContainsWildcard ? command . Name : this . Name [ 0 ];
2020-05-20 07:36:06 +02:00
2020-04-13 19:01:08 +01:00
IDictionary < string , AliasInfo > aliasTable = SessionState . Internal . GetAliasTable ();
foreach ( KeyValuePair < string , AliasInfo > tableEntry in aliasTable )
{
2020-05-20 07:36:06 +02:00
if (( Array . Exists ( this . Name , name => name . Equals ( tableEntry . Key , StringComparison . InvariantCultureIgnoreCase )) &&
tableEntry . Value . Definition == command . Name ) ||
2020-04-13 19:01:08 +01:00
( _nameContainsWildcard && tableEntry . Value . Definition == command . Name ))
{
aliasName = tableEntry . Key ;
break ;
}
}
string replacedSyntax = string . Empty ;
switch ( command )
{
case ExternalScriptInfo externalScript :
replacedSyntax = string . Format (
"{0} (alias) -> {1}{2}{3}" ,
aliasName ,
string . Format ( "{0}{1}" , externalScript . Path , Environment . NewLine ),
Environment . NewLine ,
command . Syntax . Replace ( command . Name , aliasName ));
break ;
case ApplicationInfo app :
replacedSyntax = app . Path ;
break ;
default :
if ( aliasName . Equals ( command . Name ))
{
replacedSyntax = command . Syntax ;
}
else
{
replacedSyntax = string . Format (
"{0} (alias) -> {1}{2}{3}" ,
aliasName ,
command . Name ,
Environment . NewLine ,
command . Syntax . Replace ( command . Name , aliasName ));
}
break ;
}
2020-05-20 07:36:06 +02:00
2020-04-13 19:01:08 +01:00
syntax = PSObject . AsPSObject ( replacedSyntax );
}
syntax . IsHelpObject = true ;
return syntax ;
}
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// The comparer to sort CommandInfo objects in the result list.
2016-03-30 14:20:52 -07:00
/// </summary>
2021-07-19 10:09:12 +01:00
private sealed class CommandInfoComparer : IComparer < CommandInfo >
2016-03-30 14:20:52 -07:00
{
/// <summary>
/// Compare two CommandInfo objects first by their command types, and if they
/// are with the same command type, then we compare their names.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare ( CommandInfo x , CommandInfo y )
{
if (( int ) x . CommandType < ( int ) y . CommandType )
{
return - 1 ;
}
else if (( int ) x . CommandType > ( int ) y . CommandType )
{
return 1 ;
}
else
{
2018-12-31 20:44:16 +05:00
return string . Compare ( x . Name , y . Name , StringComparison . OrdinalIgnoreCase );
2016-03-30 14:20:52 -07:00
}
}
}
private void AccumulateMatchingCmdlets ()
{
2018-04-27 23:02:15 -07:00
_commandType = CommandTypes . Cmdlet | CommandTypes . Function | CommandTypes . Filter | CommandTypes . Alias | CommandTypes . Configuration ;
2016-03-30 14:20:52 -07:00
Collection < string > commandNames = new Collection < string >();
commandNames . Add ( "*" );
AccumulateMatchingCommands ( commandNames );
}
private bool IsNounVerbMatch ( CommandInfo command )
{
bool result = false ;
do // false loop
{
2022-07-25 21:11:29 +04:00
_verbPatterns ??= SessionStateUtilities . CreateWildcardsFromStrings ( Verb , WildcardOptions . IgnoreCase | WildcardOptions . CultureInvariant );
2016-03-30 14:20:52 -07:00
2022-07-25 21:11:29 +04:00
_nounPatterns ??= SessionStateUtilities . CreateWildcardsFromStrings ( Noun , WildcardOptions . IgnoreCase | WildcardOptions . CultureInvariant );
2016-03-30 14:20:52 -07:00
if (! string . IsNullOrEmpty ( command . ModuleName ))
{
2016-07-29 13:02:49 -07:00
if ( _isFullyQualifiedModuleSpecified )
2016-03-30 14:20:52 -07:00
{
if (! _moduleSpecifications . Any (
moduleSpecification =>
ModuleIntrinsics . IsModuleMatchingModuleSpec ( command . Module , moduleSpecification )))
{
break ;
}
}
else if (! SessionStateUtilities . MatchesAnyWildcardPattern ( command . ModuleName , _modulePatterns , true ))
{
break ;
}
}
else
{
2020-06-25 07:30:40 +01:00
if ( _modulePatterns . Count > 0 || _moduleSpecifications . Length > 0 )
2016-03-30 14:20:52 -07:00
{
// Its not a match if we are filtering on a PSSnapin/Module name but the cmdlet doesn't have one.
break ;
}
}
// Get the noun and verb to check...
string verb ;
string noun ;
CmdletInfo cmdlet = command as CmdletInfo ;
if ( cmdlet != null )
{
verb = cmdlet . Verb ;
noun = cmdlet . Noun ;
}
else
{
if (! CmdletInfo . SplitCmdletName ( command . Name , out verb , out noun ))
break ;
}
2016-07-29 13:02:49 -07:00
if (! SessionStateUtilities . MatchesAnyWildcardPattern ( verb , _verbPatterns , true ))
2016-03-30 14:20:52 -07:00
{
break ;
}
2016-07-29 13:02:49 -07:00
if (! SessionStateUtilities . MatchesAnyWildcardPattern ( noun , _nounPatterns , true ))
2016-03-30 14:20:52 -07:00
{
break ;
}
result = true ;
} while ( false );
return result ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Writes out the commands for the AllCommandSet using the specified CommandType.
2016-03-30 14:20:52 -07:00
/// </summary>
private void AccumulateMatchingCommands ()
{
Collection < string > commandNames =
SessionStateUtilities . ConvertArrayToCollection < string >( this . Name );
if ( commandNames . Count == 0 )
{
commandNames . Add ( "*" );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
AccumulateMatchingCommands ( commandNames );
}
private void AccumulateMatchingCommands ( IEnumerable < string > commandNames )
{
// First set the search options
SearchResolutionOptions options = SearchResolutionOptions . None ;
if ( All )
{
options = SearchResolutionOptions . SearchAllScopes ;
}
2019-01-10 16:11:43 -08:00
if ( UseAbbreviationExpansion )
{
options |= SearchResolutionOptions . UseAbbreviationExpansion ;
}
if ( UseFuzzyMatching )
{
options |= SearchResolutionOptions . FuzzyMatch ;
}
2016-03-30 14:20:52 -07:00
if (( this . CommandType & CommandTypes . Alias ) != 0 )
{
options |= SearchResolutionOptions . ResolveAliasPatterns ;
}
2018-04-27 23:02:15 -07:00
if (( this . CommandType & ( CommandTypes . Function | CommandTypes . Filter | CommandTypes . Configuration )) != 0 )
2016-03-30 14:20:52 -07:00
{
options |= SearchResolutionOptions . ResolveFunctionPatterns ;
}
foreach ( string commandName in commandNames )
{
try
{
// Determine if the command name is module-qualified, and search
// available modules for the command.
string moduleName ;
string plainCommandName = Utils . ParseCommandName ( commandName , out moduleName );
bool isModuleQualified = ( moduleName != null );
// If they've specified a module name, we can do some smarter filtering.
// Otherwise, we have to filter everything.
if (( this . Module . Length == 1 ) && (! WildcardPattern . ContainsWildcardCharacters ( this . Module [ 0 ])))
{
moduleName = this . Module [ 0 ];
}
2019-01-10 16:11:43 -08:00
bool isPattern = WildcardPattern . ContainsWildcardCharacters ( plainCommandName ) || UseAbbreviationExpansion || UseFuzzyMatching ;
2016-03-30 14:20:52 -07:00
if ( isPattern )
{
options |= SearchResolutionOptions . CommandNameIsPattern ;
}
// Try to initially find the command in the available commands
int count = 0 ;
bool isDuplicate ;
bool resultFound = FindCommandForName ( options , commandName , isPattern , true , ref count , out isDuplicate );
// If we didn't find the command, or if it had a wildcard, also see if it
// is in an available module
if (! resultFound || isPattern )
{
// If the command name had no wildcards or was module-qualified,
// import the module so that we can return the fully structured data.
// This uses the same code path as module auto-loading.
2018-12-31 15:10:15 +05:00
if ((! isPattern ) || (! string . IsNullOrEmpty ( moduleName )))
2016-03-30 14:20:52 -07:00
{
string tempCommandName = commandName ;
2018-12-31 15:10:15 +05:00
if ((! isModuleQualified ) && (! string . IsNullOrEmpty ( moduleName )))
2016-03-30 14:20:52 -07:00
{
tempCommandName = moduleName + "\\" + commandName ;
}
try
{
CommandDiscovery . LookupCommandInfo ( tempCommandName , this . MyInvocation . CommandOrigin , this . Context );
}
catch ( CommandNotFoundException )
{
// Ignore, LookupCommandInfo doesn't handle wildcards.
}
resultFound = FindCommandForName ( options , commandName , isPattern , false , ref count , out isDuplicate );
}
// Show additional commands from available modules only if ListImported is not specified
else if (! ListImported )
{
if ( TotalCount < 0 || count < TotalCount )
{
2019-01-10 13:35:30 -08:00
IEnumerable < CommandInfo > commands ;
if ( UseFuzzyMatching )
{
foreach ( var commandScore in System . Management . Automation . Internal . ModuleUtils . GetFuzzyMatchingCommands (
plainCommandName ,
this . Context ,
this . MyInvocation . CommandOrigin ,
rediscoverImportedModules : true ,
moduleVersionRequired : _isFullyQualifiedModuleSpecified ))
{
_commandScores . Add ( commandScore );
}
2021-04-13 00:44:05 +05:00
commands = _commandScores . Select ( static x => x . Command ). ToList ();
2019-01-10 13:35:30 -08:00
}
else
{
commands = System . Management . Automation . Internal . ModuleUtils . GetMatchingCommands (
plainCommandName ,
this . Context ,
this . MyInvocation . CommandOrigin ,
rediscoverImportedModules : true ,
2019-01-10 16:11:43 -08:00
moduleVersionRequired : _isFullyQualifiedModuleSpecified ,
useAbbreviationExpansion : UseAbbreviationExpansion );
2019-01-10 13:35:30 -08:00
}
foreach ( CommandInfo command in commands )
2016-03-30 14:20:52 -07:00
{
// Cannot pass in "command" by ref (foreach iteration variable)
CommandInfo current = command ;
if ( IsCommandMatch ( ref current , out isDuplicate ) && (! IsCommandInResult ( current )) && IsParameterMatch ( current ))
{
2016-07-29 13:02:49 -07:00
_accumulatedResults . Add ( current );
2016-03-30 14:20:52 -07:00
// Make sure we don't exceed the TotalCount parameter
++ count ;
if ( TotalCount >= 0 && count >= TotalCount )
{
break ;
}
}
}
}
}
}
// If we are trying to match a single specific command name (no glob characters)
// then we need to write an error if we didn't find it.
if (! isDuplicate )
{
if (! resultFound && ! isPattern )
{
CommandNotFoundException e =
new CommandNotFoundException (
commandName ,
null ,
"CommandNotFoundException" ,
DiscoveryExceptions . CommandNotFoundException );
WriteError (
new ErrorRecord (
e . ErrorRecord ,
e ));
continue ;
}
}
}
catch ( CommandNotFoundException exception )
{
WriteError (
new ErrorRecord (
exception . ErrorRecord ,
exception ));
}
}
}
private bool FindCommandForName ( SearchResolutionOptions options , string commandName , bool isPattern , bool emitErrors , ref int currentCount , out bool isDuplicate )
{
CommandSearcher searcher =
new CommandSearcher (
commandName ,
options ,
this . CommandType ,
this . Context );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
bool resultFound = false ;
isDuplicate = false ;
2020-07-20 05:23:14 +01:00
while ( true )
2016-03-30 14:20:52 -07:00
{
try
{
if (! searcher . MoveNext ())
{
break ;
}
}
catch ( ArgumentException argumentException )
{
if ( emitErrors )
{
WriteError ( new ErrorRecord ( argumentException , "GetCommandInvalidArgument" , ErrorCategory . SyntaxError , null ));
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
continue ;
}
catch ( PathTooLongException pathTooLong )
{
if ( emitErrors )
{
WriteError ( new ErrorRecord ( pathTooLong , "GetCommandInvalidArgument" , ErrorCategory . SyntaxError , null ));
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
continue ;
}
catch ( FileLoadException fileLoadException )
{
if ( emitErrors )
{
WriteError ( new ErrorRecord ( fileLoadException , "GetCommandFileLoadError" , ErrorCategory . ReadError , null ));
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
continue ;
}
catch ( MetadataException metadataException )
{
if ( emitErrors )
{
WriteError ( new ErrorRecord ( metadataException , "GetCommandMetadataError" , ErrorCategory . MetadataError , null ));
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
continue ;
}
catch ( FormatException formatException )
{
if ( emitErrors )
{
WriteError ( new ErrorRecord ( formatException , "GetCommandBadFileFormat" , ErrorCategory . InvalidData , null ));
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
continue ;
}
CommandInfo current = (( IEnumerator < CommandInfo >) searcher ). Current ;
2017-01-16 13:31:14 -08:00
// skip private commands as early as possible
2016-03-30 14:20:52 -07:00
// (i.e. before setting "result found" flag and before trying to use ArgumentList parameter)
// see bugs Windows 7: #520498 and #520470
CommandOrigin origin = this . MyInvocation . CommandOrigin ;
if (! SessionState . IsVisible ( origin , current ))
{
continue ;
}
bool tempResultFound = IsCommandMatch ( ref current , out isDuplicate );
if ( tempResultFound && (! IsCommandInResult ( current )))
{
resultFound = true ;
if ( IsParameterMatch ( current ))
{
// Make sure we don't exceed the TotalCount parameter
++ currentCount ;
if ( TotalCount >= 0 && currentCount > TotalCount )
{
break ;
}
2019-01-10 13:35:30 -08:00
if ( UseFuzzyMatching )
{
int score = FuzzyMatcher . GetDamerauLevenshteinDistance ( current . Name , commandName );
_commandScores . Add ( new CommandScore ( current , score ));
}
2016-07-29 13:02:49 -07:00
_accumulatedResults . Add ( current );
2016-03-30 14:20:52 -07:00
if ( ArgumentList != null )
{
// Don't iterate the enumerator any more. If -arguments was specified, then we stop at the first match
break ;
}
}
// Only for this case, the loop should exit
// Get-Command Foo
2016-08-02 13:29:09 -07:00
if ( isPattern || All || TotalCount != - 1 || _isCommandTypeSpecified || _isModuleSpecified || _isFullyQualifiedModuleSpecified )
2016-03-30 14:20:52 -07:00
{
continue ;
}
else
{
break ;
}
}
2020-07-20 05:23:14 +01:00
}
2016-03-30 14:20:52 -07:00
if ( All )
{
// Get additional matching commands from module tables.
foreach ( CommandInfo command in GetMatchingCommandsFromModules ( commandName ))
{
CommandInfo c = command ;
bool tempResultFound = IsCommandMatch ( ref c , out isDuplicate );
if ( tempResultFound )
{
resultFound = true ;
if (! IsCommandInResult ( command ) && IsParameterMatch ( c ))
{
++ currentCount ;
if ( TotalCount >= 0 && currentCount > TotalCount )
{
break ;
}
2018-12-23 22:20:06 -08:00
2016-07-29 13:02:49 -07:00
_accumulatedResults . Add ( c );
2016-03-30 14:20:52 -07:00
}
// Make sure we don't exceed the TotalCount parameter
}
}
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
return resultFound ;
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Determines if the specific command information has already been
2016-03-30 14:20:52 -07:00
/// written out based on the path or definition.
/// </summary>
/// <param name="info">
/// The command information to check for duplication.
/// </param>
/// <returns>
/// true if the command has already been written out.
/// </returns>
private bool IsDuplicate ( CommandInfo info )
{
bool result = false ;
string key = null ;
do // false loop
{
ApplicationInfo appInfo = info as ApplicationInfo ;
if ( appInfo != null )
{
key = appInfo . Path ;
break ;
}
CmdletInfo cmdletInfo = info as CmdletInfo ;
if ( cmdletInfo != null )
{
key = cmdletInfo . FullName ;
break ;
}
ScriptInfo scriptInfo = info as ScriptInfo ;
if ( scriptInfo != null )
{
key = scriptInfo . Definition ;
break ;
}
ExternalScriptInfo externalScriptInfo = info as ExternalScriptInfo ;
if ( externalScriptInfo != null )
{
key = externalScriptInfo . Path ;
break ;
}
} while ( false );
if ( key != null )
{
2016-07-29 13:02:49 -07:00
if ( _commandsWritten . ContainsKey ( key ))
2016-03-30 14:20:52 -07:00
{
result = true ;
}
else
{
2016-07-29 13:02:49 -07:00
_commandsWritten . Add ( key , info );
2016-03-30 14:20:52 -07:00
}
}
return result ;
}
private bool IsParameterMatch ( CommandInfo commandInfo )
{
2020-07-30 16:06:38 -07:00
if (( this . ParameterName == null ) && ( this . ParameterType == null ))
2016-03-30 14:20:52 -07:00
{
return true ;
}
2022-07-25 21:11:29 +04:00
_matchedParameterNames ??= new HashSet < string >( StringComparer . OrdinalIgnoreCase );
2016-03-30 14:20:52 -07:00
IEnumerable < ParameterMetadata > commandParameters = null ;
try
{
IDictionary < string , ParameterMetadata > tmp = commandInfo . Parameters ;
if ( tmp != null )
{
commandParameters = tmp . Values ;
}
}
2016-11-01 15:24:07 -07:00
catch ( Exception )
2016-03-30 14:20:52 -07:00
{
// ignore all exceptions when getting parameter metadata (i.e. parse exceptions, dangling alias exceptions)
// and proceed as if there was no parameter metadata
}
2018-12-23 22:20:06 -08:00
2020-07-30 16:06:38 -07:00
if ( commandParameters == null )
2016-03-30 14:20:52 -07:00
{
// do not match commands which have not been imported yet / for which we don't have parameter metadata yet
return false ;
}
else
{
bool foundMatchingParameter = false ;
foreach ( ParameterMetadata parameterMetadata in commandParameters )
{
if ( IsParameterMatch ( parameterMetadata ))
{
foundMatchingParameter = true ;
// not breaking out of the loop early, to ensure that _matchedParameterNames gets populated for all command parameters
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return foundMatchingParameter ;
}
}
private bool IsParameterMatch ( ParameterMetadata parameterMetadata )
{
//
// ParameterName matching
//
bool nameIsDirectlyMatching = SessionStateUtilities . MatchesAnyWildcardPattern ( parameterMetadata . Name , _parameterNameWildcards , true );
bool oneOfAliasesIsMatching = false ;
foreach ( string alias in parameterMetadata . Aliases ?? Enumerable . Empty < string >())
{
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( alias , _parameterNameWildcards , true ))
{
_matchedParameterNames . Add ( alias );
oneOfAliasesIsMatching = true ;
// don't want to break out of the loop early (need to fully populate _matchedParameterNames hashset)
}
}
bool nameIsMatching = nameIsDirectlyMatching || oneOfAliasesIsMatching ;
if ( nameIsMatching )
{
_matchedParameterNames . Add ( parameterMetadata . Name );
}
//
// ParameterType matching
//
bool typeIsMatching ;
2020-07-30 16:06:38 -07:00
if (( _parameterTypes == null ) || ( _parameterTypes . Length == 0 ))
2016-03-30 14:20:52 -07:00
{
typeIsMatching = true ;
}
else
{
typeIsMatching = false ;
if ( _parameterTypes != null &&
_parameterTypes . Length > 0 )
{
typeIsMatching |= _parameterTypes . Any ( parameterMetadata . IsMatchingType );
}
}
return nameIsMatching && typeIsMatching ;
}
private bool IsCommandMatch ( ref CommandInfo current , out bool isDuplicate )
{
bool isCommandMatch = false ;
isDuplicate = false ;
// Be sure we haven't already found this command before
if (! IsDuplicate ( current ))
{
if (( current . CommandType & this . CommandType ) != 0 )
{
isCommandMatch = true ;
}
2018-04-27 23:02:15 -07:00
// If the command in question is a cmdlet or (a function/filter/configuration/alias and we are filtering on nouns or verbs),
// then do the verb/noun check
2016-03-30 14:20:52 -07:00
if ( current . CommandType == CommandTypes . Cmdlet ||
2016-07-29 13:02:49 -07:00
(( _verbs . Length > 0 || _nouns . Length > 0 ) &&
2016-03-30 14:20:52 -07:00
( current . CommandType == CommandTypes . Function ||
current . CommandType == CommandTypes . Filter ||
current . CommandType == CommandTypes . Configuration ||
current . CommandType == CommandTypes . Alias )))
{
if (! IsNounVerbMatch ( current ))
{
isCommandMatch = false ;
}
}
else
{
2016-07-29 13:02:49 -07:00
if ( _isFullyQualifiedModuleSpecified )
2016-03-30 14:20:52 -07:00
{
bool foundModuleMatch = false ;
foreach ( var moduleSpecification in _moduleSpecifications )
{
if ( ModuleIntrinsics . IsModuleMatchingModuleSpec ( current . Module , moduleSpecification ))
{
foundModuleMatch = true ;
break ;
}
}
if (! foundModuleMatch )
{
isCommandMatch = false ;
}
}
else if ( _modulePatterns != null && _modulePatterns . Count > 0 )
{
if (! SessionStateUtilities . MatchesAnyWildcardPattern ( current . ModuleName , _modulePatterns , true ))
{
isCommandMatch = false ;
}
}
}
if ( isCommandMatch )
{
2020-04-13 19:01:08 +01:00
if ( Syntax . IsPresent && current is AliasInfo ai )
2016-03-30 14:20:52 -07:00
{
2020-04-13 19:01:08 +01:00
// If the matching command was an alias, then use the resolved command
// instead of the alias...
current = ai . ResolvedCommand ?? CommandDiscovery . LookupCommandInfo (
ai . UnresolvedCommandName ,
this . MyInvocation . CommandOrigin ,
this . Context );
// there are situations where both ResolvedCommand and UnresolvedCommandName
// are both null (often due to multiple versions of modules with aliases)
// therefore we need to exit early.
2020-07-30 16:06:38 -07:00
if ( current == null )
2016-03-30 14:20:52 -07:00
{
2020-04-13 19:01:08 +01:00
return false ;
2016-03-30 14:20:52 -07:00
}
}
2020-11-05 05:23:50 +00:00
if ( ArgumentList != null
&& current is not CmdletInfo
&& current is not IScriptCommandInfo )
2020-04-13 19:01:08 +01:00
{
// If current is not a cmdlet or script, we need to throw a terminating error.
ThrowTerminatingError (
new ErrorRecord (
PSTraceSource . NewArgumentException (
"ArgumentList" ,
DiscoveryExceptions . CommandArgsOnlyForSingleCmdlet ),
"CommandArgsOnlyForSingleCmdlet" ,
ErrorCategory . InvalidArgument ,
current ));
}
2016-03-30 14:20:52 -07:00
// If the command implements dynamic parameters
// then we must make a copy of the CommandInfo which merges the
// dynamic parameter metadata with the statically defined parameter
// metadata
bool needCopy = false ;
try
{
// We can ignore some errors that occur when checking if
// the command implements dynamic parameters.
needCopy = current . ImplementsDynamicParameters ;
}
catch ( PSSecurityException )
{
// Ignore execution policies in get-command, those will get
// raised when trying to run the real command
}
catch ( RuntimeException )
{
// Ignore parse/runtime exceptions. Again, they will get
// raised again if the script is actually run.
}
if ( needCopy )
{
try
{
CommandInfo newCurrent = current . CreateGetCommandCopy ( ArgumentList );
if ( ArgumentList != null )
{
// We need to prepopulate the parameter metadata in the CmdletInfo to
// ensure there are no errors. Getting the ParameterSets property
// triggers the parameter metadata to be generated
ReadOnlyCollection < CommandParameterSetInfo > parameterSets =
newCurrent . ParameterSets ;
}
current = newCurrent ;
}
catch ( MetadataException metadataException )
{
// A metadata exception can be thrown if the dynamic parameters duplicates a parameter
// of the cmdlet.
WriteError ( new ErrorRecord ( metadataException , "GetCommandMetadataError" ,
ErrorCategory . MetadataError , current ));
}
catch ( ParameterBindingException parameterBindingException )
{
// if the exception is thrown when retrieving dynamic parameters, ignore it and
// the static parameter info will be used.
if (! parameterBindingException . ErrorRecord . FullyQualifiedErrorId . StartsWith (
"GetDynamicParametersException" , StringComparison . Ordinal ))
{
throw ;
}
}
}
}
}
else
{
isDuplicate = true ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return isCommandMatch ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Gets matching commands from the module tables.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="commandName">
/// The commandname to look for
/// </param>
/// <returns>
/// IEnumerable of CommandInfo objects
/// </returns>
private IEnumerable < CommandInfo > GetMatchingCommandsFromModules ( string commandName )
{
WildcardPattern matcher = WildcardPattern . Get (
commandName ,
WildcardOptions . IgnoreCase );
2017-01-16 13:31:14 -08:00
// Use ModuleTableKeys list in reverse order
2016-03-30 14:20:52 -07:00
for ( int i = Context . EngineSessionState . ModuleTableKeys . Count - 1 ; i >= 0 ; i --)
{
PSModuleInfo module = null ;
2020-11-13 04:39:25 +00:00
if (! Context . EngineSessionState . ModuleTable . TryGetValue ( Context . EngineSessionState . ModuleTableKeys [ i ], out module ))
2016-03-30 14:20:52 -07:00
{
Dbg . Assert ( false , "ModuleTableKeys should be in sync with ModuleTable" );
}
else
{
2016-07-29 13:02:49 -07:00
bool isModuleMatch = false ;
if (! _isFullyQualifiedModuleSpecified )
2016-03-30 14:20:52 -07:00
{
isModuleMatch = SessionStateUtilities . MatchesAnyWildcardPattern ( module . Name , _modulePatterns , true );
}
2016-07-29 13:02:49 -07:00
else if ( _moduleSpecifications . Any ( moduleSpecification => ModuleIntrinsics . IsModuleMatchingModuleSpec ( module , moduleSpecification )))
2016-03-30 14:20:52 -07:00
{
isModuleMatch = true ;
}
if ( isModuleMatch )
{
if ( module . SessionState != null )
{
// Look in function table
if (( this . CommandType & ( CommandTypes . Function | CommandTypes . Filter | CommandTypes . Configuration )) != 0 )
{
2020-05-20 07:36:06 +02:00
foreach (( string functionName , FunctionInfo functionInfo ) in module . SessionState . Internal . GetFunctionTable ())
2016-03-30 14:20:52 -07:00
{
2020-05-20 07:36:06 +02:00
if ( matcher . IsMatch ( functionName ) && functionInfo . IsImported )
2016-03-30 14:20:52 -07:00
{
// make sure function doesn't come from the current module's nested module
2020-05-20 07:36:06 +02:00
if ( functionInfo . Module . Path . Equals ( module . Path , StringComparison . OrdinalIgnoreCase ))
yield return functionInfo ;
2016-03-30 14:20:52 -07:00
}
}
}
// Look in alias table
if (( this . CommandType & CommandTypes . Alias ) != 0 )
{
foreach ( var alias in module . SessionState . Internal . GetAliasTable ())
{
if ( matcher . IsMatch ( alias . Key ) && alias . Value . IsImported )
{
// make sure alias doesn't come from the current module's nested module
if ( alias . Value . Module . Path . Equals ( module . Path , StringComparison . OrdinalIgnoreCase ))
yield return alias . Value ;
}
}
}
}
}
}
}
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Determines if the specific command information has already been
2019-01-06 04:40:24 +05:00
/// added to the result from CommandSearcher.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="command">
/// The command information to check for duplication.
/// </param>
/// <returns>
/// true if the command is present in the result.
/// </returns>
private bool IsCommandInResult ( CommandInfo command )
{
bool isPresent = false ;
2020-12-17 01:26:47 +00:00
if ( command . Module is not null )
2016-03-30 14:20:52 -07:00
{
2020-12-17 01:26:47 +00:00
foreach ( CommandInfo commandInfo in _accumulatedResults )
2016-03-30 14:20:52 -07:00
{
2020-12-17 01:26:47 +00:00
if ( commandInfo . Module is null || commandInfo . CommandType != command . CommandType )
{
continue ;
}
// We do reference equal comparison if both command are imported. If either one is not imported, we compare the module path
if ((! commandInfo . IsImported || ! command . IsImported || ! commandInfo . Module . Equals ( command . Module ))
&& (( commandInfo . IsImported && command . IsImported ) || ! commandInfo . Module . Path . Equals ( command . Module . Path , StringComparison . OrdinalIgnoreCase )))
{
continue ;
}
// If the command has been imported with a prefix, then just checking the names for duplication will not be enough.
// Hence, an additional check is done with the prefix information
if ( commandInfo . Name . Equals ( command . Name , StringComparison . OrdinalIgnoreCase )
|| ModuleCmdletBase . RemovePrefixFromCommandName ( commandInfo . Name , commandInfo . Prefix ). Equals ( command . Name , StringComparison . OrdinalIgnoreCase ))
{
isPresent = true ;
break ;
}
2016-03-30 14:20:52 -07:00
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return isPresent ;
}
#endregion
#region Members
2020-11-05 03:36:23 +00:00
private readonly Dictionary < string , CommandInfo > _commandsWritten =
2016-03-30 14:20:52 -07:00
new Dictionary < string , CommandInfo >( StringComparer . OrdinalIgnoreCase );
2016-07-29 13:02:49 -07:00
private List < CommandInfo > _accumulatedResults = new List < CommandInfo >();
2016-03-30 14:20:52 -07:00
// These members are the collection of wildcard patterns for the "CmdletSet"
2016-07-29 13:02:49 -07:00
private Collection < WildcardPattern > _verbPatterns ;
private Collection < WildcardPattern > _nounPatterns ;
private Collection < WildcardPattern > _modulePatterns ;
2016-03-30 14:20:52 -07:00
2019-08-15 16:13:53 -07:00
#if LEGACYTELEMETRY
2016-04-28 13:41:25 -07:00
private Stopwatch _timer = new Stopwatch ();
2019-08-15 16:13:53 -07:00
#endif
2016-03-30 14:20:52 -07:00
#endregion
#region ShowCommandInfo support
// Converts to PSObject containing ShowCommand information.
private static PSObject ConvertToShowCommandInfo ( CommandInfo cmdInfo )
{
PSObject showCommandInfo = new PSObject ();
showCommandInfo . Properties . Add ( new PSNoteProperty ( "Name" , cmdInfo . Name ));
showCommandInfo . Properties . Add ( new PSNoteProperty ( "ModuleName" , cmdInfo . ModuleName ));
showCommandInfo . Properties . Add ( new PSNoteProperty ( "Module" , GetModuleInfo ( cmdInfo )));
showCommandInfo . Properties . Add ( new PSNoteProperty ( "CommandType" , cmdInfo . CommandType ));
showCommandInfo . Properties . Add ( new PSNoteProperty ( "Definition" , cmdInfo . Definition ));
showCommandInfo . Properties . Add ( new PSNoteProperty ( "ParameterSets" , GetParameterSets ( cmdInfo )));
return showCommandInfo ;
}
private static PSObject GetModuleInfo ( CommandInfo cmdInfo )
{
PSObject moduleInfo = new PSObject ();
string moduleName = ( cmdInfo . Module != null ) ? cmdInfo . Module . Name : string . Empty ;
moduleInfo . Properties . Add ( new PSNoteProperty ( "Name" , moduleName ));
return moduleInfo ;
}
private static PSObject [] GetParameterSets ( CommandInfo cmdInfo )
{
ReadOnlyCollection < CommandParameterSetInfo > parameterSets = null ;
try
{
if ( cmdInfo . ParameterSets != null )
{
parameterSets = cmdInfo . ParameterSets ;
}
}
catch ( InvalidOperationException ) { }
catch ( PSNotSupportedException ) { }
catch ( PSNotImplementedException ) { }
2020-07-30 16:06:38 -07:00
if ( parameterSets == null )
2016-03-30 14:20:52 -07:00
{
2019-03-10 06:53:31 +05:00
return Array . Empty < PSObject >();
2016-03-30 14:20:52 -07:00
}
List < PSObject > returnParameterSets = new List < PSObject >( cmdInfo . ParameterSets . Count );
foreach ( CommandParameterSetInfo parameterSetInfo in parameterSets )
{
PSObject parameterSetObj = new PSObject ();
parameterSetObj . Properties . Add ( new PSNoteProperty ( "Name" , parameterSetInfo . Name ));
parameterSetObj . Properties . Add ( new PSNoteProperty ( "IsDefault" , parameterSetInfo . IsDefault ));
parameterSetObj . Properties . Add ( new PSNoteProperty ( "Parameters" , GetParameterInfo ( parameterSetInfo . Parameters )));
returnParameterSets . Add ( parameterSetObj );
}
return returnParameterSets . ToArray ();
}
private static PSObject [] GetParameterInfo ( ReadOnlyCollection < CommandParameterInfo > parameters )
{
List < PSObject > parameterObjs = new List < PSObject >( parameters . Count );
foreach ( CommandParameterInfo parameter in parameters )
{
PSObject parameterObj = new PSObject ();
parameterObj . Properties . Add ( new PSNoteProperty ( "Name" , parameter . Name ));
parameterObj . Properties . Add ( new PSNoteProperty ( "IsMandatory" , parameter . IsMandatory ));
parameterObj . Properties . Add ( new PSNoteProperty ( "ValueFromPipeline" , parameter . ValueFromPipeline ));
parameterObj . Properties . Add ( new PSNoteProperty ( "Position" , parameter . Position ));
parameterObj . Properties . Add ( new PSNoteProperty ( "ParameterType" , GetParameterType ( parameter . ParameterType )));
bool hasParameterSet = false ;
IList < string > validValues = new List < string >();
2020-06-25 07:30:40 +01:00
var validateSetAttribute = parameter . Attributes . OfType < ValidateSetAttribute >(). LastOrDefault ();
2016-03-30 14:20:52 -07:00
if ( validateSetAttribute != null )
{
hasParameterSet = true ;
validValues = validateSetAttribute . ValidValues ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
parameterObj . Properties . Add ( new PSNoteProperty ( "HasParameterSet" , hasParameterSet ));
parameterObj . Properties . Add ( new PSNoteProperty ( "ValidParamSetValues" , validValues ));
parameterObjs . Add ( parameterObj );
}
return parameterObjs . ToArray ();
}
private static PSObject GetParameterType ( Type parameterType )
{
PSObject returnParameterType = new PSObject ();
2018-04-13 15:10:10 -07:00
bool isEnum = parameterType . IsEnum ;
bool isArray = parameterType . IsArray ;
2016-03-30 14:20:52 -07:00
returnParameterType . Properties . Add ( new PSNoteProperty ( "FullName" , parameterType . FullName ));
returnParameterType . Properties . Add ( new PSNoteProperty ( "IsEnum" , isEnum ));
returnParameterType . Properties . Add ( new PSNoteProperty ( "IsArray" , isArray ));
ArrayList enumValues = ( isEnum ) ?
new ArrayList ( Enum . GetValues ( parameterType )) : new ArrayList ();
returnParameterType . Properties . Add ( new PSNoteProperty ( "EnumValues" , enumValues ));
2020-12-02 03:25:56 +00:00
bool hasFlagAttribute = ( isArray ) && (( parameterType . GetCustomAttributes ( typeof ( FlagsAttribute ), true )). Length > 0 );
2016-03-30 14:20:52 -07:00
returnParameterType . Properties . Add ( new PSNoteProperty ( "HasFlagAttribute" , hasFlagAttribute ));
// Recurse into array elements.
object elementType = ( isArray ) ?
GetParameterType ( parameterType . GetElementType ()) : null ;
returnParameterType . Properties . Add ( new PSNoteProperty ( "ElementType" , elementType ));
bool implementsDictionary = (! isEnum && ! isArray && ( parameterType is IDictionary ));
returnParameterType . Properties . Add ( new PSNoteProperty ( "ImplementsDictionary" , implementsDictionary ));
return returnParameterType ;
}
#endregion
}
/// <summary>
/// </summary>
public class NounArgumentCompleter : IArgumentCompleter
{
/// <summary>
/// </summary>
public IEnumerable < CompletionResult > CompleteArgument ( string commandName , string parameterName , string wordToComplete , CommandAst commandAst , IDictionary fakeBoundParameters )
{
2020-07-30 16:06:38 -07:00
if ( fakeBoundParameters == null )
2016-07-29 13:02:49 -07:00
{
2020-05-19 19:57:52 +01:00
throw PSTraceSource . NewArgumentNullException ( nameof ( fakeBoundParameters ));
2016-07-29 13:02:49 -07:00
}
2016-03-30 14:20:52 -07:00
var commandInfo = new CmdletInfo ( "Get-Command" , typeof ( GetCommandCommand ));
2018-12-21 23:15:47 -08:00
var ps = System . Management . Automation . PowerShell . Create ( RunspaceMode . CurrentRunspace )
. AddCommand ( commandInfo )
. AddParameter ( "Noun" , wordToComplete + "*" );
2016-03-30 14:20:52 -07:00
if ( fakeBoundParameters . Contains ( "Module" ))
{
ps . AddParameter ( "Module" , fakeBoundParameters [ "Module" ]);
}
HashSet < string > nouns = new HashSet < string >();
var results = ps . Invoke < CommandInfo >();
foreach ( var result in results )
{
var dash = result . Name . IndexOf ( '-' );
if ( dash != - 1 )
{
nouns . Add ( result . Name . Substring ( dash + 1 ));
}
}
2021-04-13 00:44:05 +05:00
return nouns . OrderBy ( static noun => noun ). Select ( static noun => new CompletionResult ( noun , noun , CompletionResultType . Text , noun ));
2016-03-30 14:20:52 -07:00
}
}
}