Specifying an alias and -Syntax to Get-Command returns the aliased commands syntax (#10784)

@ChrisLGardner Thank you for your contribution and your patience.
This commit is contained in:
Chris Gardner
2020-04-13 11:01:08 -07:00
committed by GitHub
parent b1e998046e
commit 8869e7a4cd
2 changed files with 179 additions and 25 deletions
@@ -531,9 +531,7 @@ namespace Microsoft.PowerShell.Commands
{
if (!string.IsNullOrEmpty(result.Syntax))
{
PSObject syntax = PSObject.AsPSObject(result.Syntax);
syntax.IsHelpObject = true;
PSObject syntax = GetSyntaxObject(result);
WriteObject(syntax);
}
@@ -571,6 +569,81 @@ namespace Microsoft.PowerShell.Commands
#endif
}
/// <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];
IDictionary<string, AliasInfo> aliasTable = SessionState.Internal.GetAliasTable();
foreach (KeyValuePair<string, AliasInfo> tableEntry in aliasTable)
{
if ((Array.Exists(this.Name, name => name.Equals(tableEntry.Key, StringComparison.InvariantCultureIgnoreCase)) &&
tableEntry.Value.Definition == command.Name) ||
(_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;
}
syntax = PSObject.AsPSObject(replacedSyntax);
}
syntax.IsHelpObject = true;
return syntax;
}
/// <summary>
/// The comparer to sort CommandInfo objects in the result list.
/// </summary>
@@ -1229,33 +1302,37 @@ namespace Microsoft.PowerShell.Commands
if (isCommandMatch)
{
if (ArgumentList != null)
if (Syntax.IsPresent && current is AliasInfo ai)
{
AliasInfo ai = current as AliasInfo;
if (ai != null)
// 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.
if (current == null)
{
// If the matching command was an alias, then use the resolved command
// instead of the alias...
current = ai.ResolvedCommand;
if (current == null)
{
return false;
}
}
else if (!(current is CmdletInfo || current is IScriptCommandInfo))
{
// 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));
return false;
}
}
if (ArgumentList != null && !(current is CmdletInfo || current is IScriptCommandInfo))
{
// 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));
}
// 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
@@ -103,3 +103,80 @@ Describe "Get-Command Feature tests" -Tag Feature {
}
}
}
Describe "Get-Command" -Tag CI {
BeforeAll {
Import-Module Microsoft.PowerShell.Management
}
Context "-Syntax tests" {
It "Should return a string object when -Name is an alias and -Syntax is specified" {
$Result = Get-Command -Name del -Syntax
$Result | Should -BeOfType [String]
$Result | Should -Match 'del \[-Path\]'
}
It "Should replace commands with aliases in matching commands when using a wildcard search" {
$Result = Get-Command -Name sp* -Syntax
$Result | Should -BeOfType [String]
$Result -join '' | Should -Match 'sp \(alias\) -> Set-ItemProperty'
$Result -join '' | Should -Match 'sp \[-Path\]'
}
It "Should not add the alias (alias) -> command decorator for non-alias commands" {
$Result = Get-Command -Name sp* -Syntax
$Result -join '' | Should -Not -Match 'Split-Path \(alias\) -> Split-Path'
$Result -join '' | Should -Match 'Split-Path \[-Path\]'
}
It "Should only replace aliases when given multiple entries including a command and an alias" {
$Result = Get-Command -Name get-help, del -Syntax
$Result -join '' | Should -Match 'del \(alias\) -> Remove-Item'
$Result -join '' | Should -Match 'del \[-Path\]'
$Result -join '' | Should -Match 'Get-Help \[\[-Name\]'
$Result -join '' | Should -Not -Match 'del \(alias\) -> Get-Help'
}
It "Should return the path to an aliased script when -Syntax is specified" {
# First, create a script file
$TestGcmSyntax = @'
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string[]]
$Name
)
process {
"Processing ${Name}"
}
'@
Set-Content -Path TestDrive:\Test-GcmSyntax.ps1 -Value $TestGcmSyntax
# Now set up an alias for that file
New-Alias -Name tgs -Value TestDrive:\Test-GcmSyntax.ps1
$Result = Get-Command -Name tgs -Syntax
$Result | Should -Match "tgs \(alias\) -> $([Regex]::Escape((Get-Item TestDrive:\\Test-GcmSyntax.ps1).FullName))"
}
}
Context "-Name tests" {
It "Should return a AliasInfo object when -Name is an alias" {
$Result = Get-Command -Name del
$Result | Should -BeOfType [System.Management.Automation.AliasInfo]
$Result.DisplayName | Should -Be 'del -> Remove-Item'
}
It "Should return a CommandInfo object when -Name is a command" {
$Result = Get-Command -Name Remove-Item
$Result | Should -BeOfType [System.Management.Automation.CommandInfo]
}
}
}