A handy PowerShell feature most people take advantage of is parameter value tab completion. Parameter value tab completion allows you to tab through available values you can pass to PowerShell commands.

PowerShell parameter value tab completion via argument completer

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; 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
}
Get-Food function

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 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.

Further Reading