I am trying to create an alias (named which) of the Get-Command cmdlet in a way that it doesn't run if I'm not sending any arguments (because if it's run without arguments it outputs all the commands that are available).
I know this can be done using a function but I would like to keep the tab completion functionality without having to write a sizeable function that is to be placed into my $PROFILE.
In short, I only want the alias to work if it is being passed arguments.
You can create an alias for a cmdlet, such as Set-Location . You cannot create an alias for a command with parameters and values, such as Set-Location -Path C:\Windows\System32 . To create an alias for a command, create a function that includes the command, and then create an alias to the function.
The New-Alias cmdlet creates a new alias in the current PowerShell session. Aliases created by using New-Alias are not saved after you exit the session or close PowerShell. You can use the Export-Alias cmdlet to save your alias information to a file.
The New-Alias cmdlet creates a new alias in the current PowerShell session. Aliases created by using New-Alias are not saved after you exit the session or close PowerShell. You can use the Export-Alias cmdlet to save your alias information to a file.
You can't do it with an alias, because PowerShell aliases can only refer to another command name or path, and can therefore neither include arguments nor custom logic.
Therefore you do need a function, but it can be a short and simple one:
function which { if ($args.count) { Get-Command @args } else { Throw "Missing command name." } }
Note that while passing -?
for showing Get-Command
's help does work, tab completion of arguments does not.
In order to get tab completion as well, you'll need to write a wrapper (proxy) function or at least replicate Get-Command
's parameter declarations - which then does make the function definition sizable.
If the concern is just the size of the $PROFILE
file itself, you can write a proxy script instead - which.ps1
- which you can invoke with just which
as well, assuming you place it in one of the directories listed in $env:Path
[1]; see next section.
Defining a wrapper (proxy) function or script is a nontrivial undertaking, but allows you to implement a robust wrapper that supports tab completion and even forwarding to the original command's help.
Note:
Bug alert: As zett42 points out, as of PowerShell [Core] 7.1, System.Management.Automation.ProxyCommand.Create
neglects to include dynamic parameters if the target command is an (advanced) function or script; however, compiled cmdlets are not affected; see GitHub issue #4792 and this answer for a workaround.
For simplicity, the following creates a wrapper script, which.ps1
, and saves it in the current directory. As stated, if you place it in one of the directories listed in $env:PATH
, you'll be able to invoke it as just which
.
The code below can easily be adapted to create a wrapper function instead: simply take the contents of the $wrapperCmdSource
variable below and enclose it in function which { ... }
.
As of PowerShell Core 7.0.0-preview.5, there are some problems with the auto-generated code, which may or may not affect you; they will be fixed at some point; to learn more and to learn how to manually correct them, see GitHub issue #10863.
# Create the wrapper scaffolding as source code (outputs a single [string])
$wrapperCmdSource =
[System.Management.Automation.ProxyCommand]::Create((Get-Command Get-Command))
# Write the auto-generated source code to a script file
$wrapperCmdSource > which.ps1
Note:
Even though System.Management.Automation.ProxyCommand.Create
requires a System.Management.Automation.CommandMetadata
instance to identify the target command, the System.Management.Automation.CommandInfo
instances output by Get-Command
can be used as-is.
Re comment-based help: By default, the proxy function simply forwards to the original cmdlet's help; however, you can optionally pass a string to serve as the comment-based help as the 2nd argument.
[System.Management.Automation.ProxyCommand]::GetHelpComments()
in combination with output from Get-Help
, you could start with a copy of the original command's help and modify it:
[System.Management.Automation.ProxyCommand]::GetHelpComments((Get-Help Get-Command))
You now have a fully functional which.ps1
wrapper script that behaves like Get-Command
itself.
You can invoke it as follows:
./which # Same as: Get-Command; tab completion of parameters supported.
./which -? # Shows Get-Command's help.
You can now edit the script file to perform the desired customization.
Note: The auto-generated source code contains a lot of boilerplate code; however, typically only one or two places need tweaking to implement the custom functionality.
Specifically, place the following command as the first statement inside the begin { ... }
block:
if (-not $MyInvocation.ExpectingInput -and -not ($Name -or $CommandType -or $Module -or $FullyQualifiedModule)) {
Throw "Missing command name or filter."
}
This causes the script to throw an error if the caller didn't provide some way of targeting a specific command or group of commands, either by direct argument or via the pipeline.
If you invoke the modified script without arguments now, you should see the desired error:
PS> ./which.ps1
Missing command name or filter.
...
Other common types of customizations are:
Removing parameters from the wrapper, by simply removing the parameter declaration.
Adding additional parameters to the invocation of the wrapped command, by modifying the following line in the begin
block:
# Add parameters, as needed.
$scriptCmd = { & $wrappedCmd @PSBoundParameters }
Preprocessing pipeline input before passing it to the wrapped command, by customizing the process
block and replacing $_
with your preprocessed input in the following line:
# Replace $_ with a preprocessed version of it, as needed.
$steppablePipeline.Process($_)
[1] Caveat for Linux users: since the Linux file-system is case is case-sensitive, invocation of your script won't work case-insensitively, the way commands normally work in PowerShell. E.g., if your script file name is Get-Foo.ps1
, only Get-Foo
- using the exact same case - will work, not also get-foo
, for instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With