A handy PowerShell feature most people take advantage of is parameter value tab completion. Parameter value PowerShell tab completion allows you to tab through available values you can pass to PowerShell commands.
Parameter value tab-completion is a feature most commonly exposed through the ValidateSet parameter attribute and PowerShell dynamic parameters. But there’s another, less known method that many people overlook, argument completers. Argument completers allow you to define tab completion on any of your PowerShell functions with just a few lines of code.
In this article, you’re going to learn how to create argument completers using the Register-ArgumentCompleter
cmdlet. You’ll then learn how to apply the argument completers and integrate them with two key examples.
You’ll put Register-ArgumentCompleter
to work and save time digging for valid parameter values by having parameter values readily available as part of the command itself.
Simple Introduction to ArgumentCompleters
You can add argument completers to any PowerShell command to get PowerShell tab completion; either a default, built-in cmdlet, or a custom function. Let’s first cover how to create an argument for a custom function called Do-Thing
.
The Do-Thing
function is a simple function with a single parameter Name
. This function simply returns the value it received via the Name
parameter as shown below.
function Do-Thing {
[CmdletBinding()]
param(
[Parameter()]
[string]$Name
)
$Name
}
Let’s say that you’d like to support values for the Name
parameter as foo
and bar
.
Creating a Scriptblock to Provide Values
You can register an argument completer to provide both the foo
and the bar
values by first creating a scriptblock. The scriptblock is what’s executed when you hit the tab key. Every time you hit the tab key, the scriptblock will execute the return a single string element in the array below.
$scriptBlock = { 'foo','bar' }
Once you have the array of items to provide as values, you’ll then register the argument completer providing the command name and parameter name to associated the argument completer with. Notice below you will also provide the scriptblock just created.
PS51> Register-ArgumentCompleter -CommandName Do-Thing -ParameterName Name -ScriptBlock $scriptBlock
Now when you type Do-Thing -Name <tab>
, PowerShell will begin scrolling through all of the values defined in the scriptblock, in this case foo
and bar
.
For each parameter, you are only expecting a certain number of items. For FoodType
, you’d like to support values of Fruit
and Vegetable
. Each food type category then has the name of the food under each like below.
Dynamic Parameter Values with ArgumentCompleters
The previous example could easily be accomplished with the ValidateSet parameter validation attribute but what if you need dynamic values? Maybe the values you want to provide can change at any time like files in a folder.
In that case, you simply need to change the code inside of the scriptblock. Since the code inside of the scriptblock is executed at run time, it doesn’t have to be a static list. It can dynamically return lists.
Below is another example you can try out.
function Get-ProgramFilesFolder {
[CmdletBinding()]
param(
[Parameter()]
[string]$FolderName
)
}
$scriptBlock = { Get-ChildItem -Path 'C:\Program Files' | Select-Object -ExpandProperty Name }
Register-ArgumentCompleter -CommandName Get-ProgramFilesFolder -ParameterName FolderName -ScriptBlock $scriptBlock
Passing Parameter Values to the Scriptblock
The previous examples did not rely on any input into the scriptblock. They both simply executed the scriptblock when the user hit the Tab key. Taking argument completers a step further, you can also pass values into the scriptblock to change the behavior.
When you associate a scriptblock to a command with Register-ArgumentCompleter
, PowerShell will pass each parameter value provided with Register-ArgumentCompleter
via positional parameters and the parameters passed when the associated command executes via positional parameters.
To create a scriptblock to accept input, you first must pay attention to how you pass parameters to the Register-ArgumentCompleter
upon creation. Both the way you register the argument completer once and how you call the associated command is important.
Positional Parameters and the Scriptblock
This concept can get confusing so let’s break it down with an example.
You’ll first need to create a scriptblock with at least two parameters regardless of how many parameters your associated command has. Below you can see I’ve defined a script with two parameters. The parameter placeholders can be any name. Why are they called $commandName
and $parameterName
? Because they correlate to the value of the CommandName
and ParameterName
parameters passed to the Register-ArgumentCompleter
command.
The scriptblock below will just return the value of $commandName
and $parameterName
when the user hits the tab key.
$scriptblock = {
param($commandName,$parameterName)
$commandName,$parameterName
}
Once you’ve got the scriptblock created, run Register-ArgumentCompleter
using our previous example command of Get-ProgramFilesFolder
.
Register-ArgumentCompleter -CommandName Get-ProgramFilesFolder -ParameterName FolderName -ScriptBlock $scriptBlock
Type Get-ProgramFilesFolder -FolderName <tab>
. Notice that the tab-completion will cycle through the values of the name of the associated command and the parameter name. It’s pulling the values for Register-ArgumentCompleter
‘s CommandName
and ParameterName
parameters.
Remember that if you want to pass parameters to the scriptblock, you must always add placeholders for all parameter values passed when you run
Register-ArgumentCompleter
.
Passing User-Provided Values to the Scriptblock
Now that you understand the role that Register-ArgumentCompleter
parameters play when passing values to the scriptblock, let’s know uncover how to pass user-provided values to it.
Let’s use the Get-ProgramFilesFolder
function as an example again.
You’d like to allow the user to type a character as the FolderName
parameter value, hit Tab and only return folder names that match that string. For example, if the user types an ‘a’ and starts hitting tab, you’d like to only return folders that start with ‘a’.
To build this behavior, you’ll need to add an additional parameter value placeholder to the scriptblock. Below I’ve called the placeholder $stringMatch
. $stringMatch
will hold the value of the FolderName
parameter before the user hits the tab key.
$scriptblock = {
param($commandName,$parameterName,$stringMatch)
Get-ChildItem -Path "C:\Program Files\$stringMatch*" | Select-Object -ExpandProperty Name
}
Register-ArgumentCompleter -CommandName Get-ProgramFilesFolder -ParameterName FolderName -ScriptBlock $scriptBlock
If you’ll now type Get-ProgramFilesFolder -FolderName a<tab>
, you’ll see that you are cycling through all of the folder names that start with ‘a’.
Adding PowerShell Tab Completion to Get-CimInstance CIM Classes
A real-world example of using argument completers is with the ClassName
parameter on the Get-CimInstance
cmdlet. Previously, you learned how to apply argument completers to custom functions but built-in cmdlets work just as well.
I can never remember all of the CIM classes when I’m querying WMI using the ClassName
parameter with Get-CimInstance
. When I type Get-CimInstance -ClassName <tab>
, I want to cycle through all of the CIM classes.
Gather all CIM Classes
The first step is to come up with the code to find all CIM classes and return them as strings in a list.
Get-CimClass | Where-Object { $_.CimClassName -notlike '__*' } | Sort-Object -Property CimClassName | Select-Object -ExpandProperty CimClassName
Create the Scriptblock
Next, create the scriptblock that will be executed when you attempt to tab-complete the ClassName
parameter value.
$scriptBlock = {
Get-CimClass | Where-Object { $_.CimClassName -notlike '__*' } | Sort-Object -Property CimClassName | Select-Object -ExpandProperty CimClassName
}
Register the ArgumentCompleter
Finally, provide the command name, the parameter name and the scriptblock to execute when you attempt to tab-complete the ClassName
parameter.
Register-ArgumentCompleter -CommandName Get-CimInstance -ParameterName ClassName -ScriptBlock $scriptBlock
Try it Out!
Now type Get-CimInstance -ClassName <tab>
. You’ll notice that you can now cycle through all of the available CIM classes!
Note: Argument completers only last for y0ur current session. This means if you close your PowerShell console, they will be gone! To get around this, I recommend adding them to your PowerShell profile to ensure they get registered in every session.
Summary
PowerShell argument completors may not be the most widely used or best known but they can sure come in handy.
By adding on your own tab-completion functionality to any command inside of PowerShell will potentially save you tons of research time.