diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs
index 0532c78d5..709c84b97 100644
--- a/src/System.Management.Automation/engine/GetCommandCommand.cs
+++ b/src/System.Management.Automation/engine/GetCommandCommand.cs
@@ -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
}
+ ///
+ /// Creates the syntax output based on if the command is an alias, script, application or command.
+ ///
+ ///
+ /// CommandInfo object containing the syntax to be output.
+ ///
+ ///
+ /// Syntax string cast as a PSObject for outputting.
+ ///
+ 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 aliasTable = SessionState.Internal.GetAliasTable();
+ foreach (KeyValuePair 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;
+ }
+
///
/// The comparer to sort CommandInfo objects in the result list.
///
@@ -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
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Command.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Command.Tests.ps1
index 5725aede1..2238d83a3 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Command.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Command.Tests.ps1
@@ -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]
+ }
+ }
+}