Adding tab completion to your PowerShell parameters (or arguments) is a great way to enhance the user experience. You provide the user with possible values or build a dynamic list of values so the user knows what they can use. This article will teach you five ways to add tab completion to PowerShell functions.
Using ValidateSet Attribute
The simplest and easiest method of implementing tab argument completion for a function is with ValidateSet
. ValidateSet
provides a valid set of values you can use for a given parameter. The value set provides tab completion so the user can see which values are valid for the parameter.
For example, here is a function named New-Widget
with two parameters: $Color
and $Size
. Each parameter uses the ValidateSet
attribute to define which values the user can use for each. For $Color
, the valid values are Red, Green, and Yellow. For $Size
, the values are Small, Medium, and Large.
function New-Widget {
param (
[Parameter(Mandatory = $true)]
[ValidateSet('Red', 'Green', 'Yellow')]
[string]
$Color,
[Parameter(Mandatory = $true)]
[ValidateSet('Small', 'Medium', 'Large')]
[string]
$Size
)
"Creating a $Size widget of color $Color."
}
When the user calls the New-Widget
function, they can tab through the possible values for each parameter, like this:

ValidateSet
validation occurs any time the variable is assigned within the script or function, not just as a parameter. Here’s the New-Widget
function again, where the last line of code tries to set $Color
to Blue, which is invalid.
function New-Widget {
param (
[Parameter(Mandatory = $true)]
[ValidateSet('Red', 'Green', 'Yellow')]
[string]
$Color,
[Parameter(Mandatory = $true)]
[ValidateSet('Small', 'Medium', 'Large')]
[string]
$Size
)
"Creating a $Size widget of color $Color."
$Color = 'Blue'
}
Calling the function results in this error message:
The variable cannot be validated because the value Blue is not a valid value for the Color variable.

Generating Dynamic ValidateSet Values with Classes
In the example above, you set a static list of possible values for the parameters. You can also dynamically generate values for ValidateSet
at runtime using classes. Your custom class uses the IValidateSetValuesGenerator
class, which was introduced in PowerShell 6.0.
In the example below, the custom class LogFileName
searches three file system paths for log files: C:\logs, C:\Windows\logs, and C:\temp\logs. The class verifies if the path is valid using the Test-Path
command, and if the path is valid, it gathers any file names in the directory. The class then returns the file names.
Class LogFileName : System.Management.Automation.IValidateSetValuesGenerator {
[string[]] GetValidValues() {
$LogFilePaths = 'C:\logs',
'C:\Windows\logs',
'C:\temp\logs'
$LogFileName = ForEach ($LogFilePath in $LogFilePaths) {
If (Test-Path $LogFilePath) {
(Get-ChildItem $LogFilePath -File).Name
}
}
return [string[]] $LogFileName
}
}
In the function definition, input the class name in the ValidateSet
attribute. PowerShell dynamically generates values for you to tab through when you execute the function command.
function Get-LogFile {
Param(
[ValidateSet([LogFileName])]
[string]$FileName
)
"Here is the LogFile file you chose: $FileName"
}

Adding Suggested Values with ArgumentCompletions
The ArgumentCompletions
attribute is very similar to ValidateSet
. Both attributes provide a list of values when the user presses the Tab key. However, ArgumentCompletions
does not perform validation on the parameter value if it is not in the list of values. The list is more of a suggestion rather than a hard rule. Microsoft introduced ArgumentCompletions
in PowerShell 6.0
Let’s look at the New-Widget
example function again, this time with ArgumentCompletions
instead of ValidateSet
. Note in the GIF following the code example, the first command uses the suggested values (Yellow and Large), while the second command allows the user to input their own values (Blue and Jumbo) without error.
function New-Widget {
param (
[Parameter(Mandatory = $true)]
[ArgumentCompletions('Red', 'Green', 'Yellow')]
[string]
$Color,
[Parameter(Mandatory = $true)]
[ArgumentCompletions('Small', 'Medium', 'Large')]
[string]
$Size
)
"Creating a $Size widget of color $Color."
}

Using ArgumentCompleter Tab Completion
The ArgumentCompleter
attribute (not to be confused with ArgumentCompletions
) adds tab completion values to a specific parameter. You define an ArgumentCompleter
attribute for each parameter that needs tab completion. ArgumentCompleter
needs a script block that determines the parameter’s possible values.
You define the script block using the function keyword along with a specific set of parameters. The parameter names do not matter as the values are provided positionally. The syntax for ArgumentCompleter
looks like this:
function ArgumentCompleterExample {
param(
[Parameter(Mandatory)]
[ArgumentCompleter( {
param ( $commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters )
# Calculate tab completed values here
} )]
$ParamName
)
}
Each parameter provides a different value for determining the tab completion values:
- $commandName (Position 0): The name of the PowerShell command the script block provides the tab completion values.
- $parameterName (Position 1): The parameter requiring tab completion values.
- $wordToComplete (Position 2): The parameter value the user has provided before pressing the Tab key. The script block uses this value to determine tab completion values.
- $commandAst (Position 3): The parameter is set to the Abstract Syntax Tree (AST) for the current input line.
- $fakeBoundParameters (Position 4): This parameter contains a hashtable with the cmdlet’s $PSBoundParameters value before the user presses Tab.
The ArgumentCompleter
script block uses ForEach-Object
, Where-Object
, or any other suitable method to iterate through the values. The script block returns an array of values that PowerShell uses as one tab completion value.
The example below defines a function named Get-FruitVariety
with two parameters: $Fruit
and $Variety
. The $Fruit
parameter uses the ValidateSet
attribute to limit to three types of fruits: Apple, Banana, and Orange. The $Variety
parameter uses the ArgumentCompleter
attribute and script block VarietyArgumentCompleter
to provide tab completion values.
If the user provides a fruit type using the $Fruit
parameter, then the script block returns the possible varieties for the $Variety
parameter. This logic is found in the first part of the if
statement. If the user provides the $Variety
parameter first, the script block returns all possible values using the else
block.
function VarietyArgumentCompleter {
param ( $commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters )
$possibleValues = @{
Apple = @('Gala', 'Honeycrisp', 'GrannySmith')
Banana = @('Cavendish', 'LadyFinger', 'Manzano')
Orange = @('Mandarin', 'Navel', 'Blood')
}
if ($fakeBoundParameters.ContainsKey('Fruit')) {
$possibleValues[$fakeBoundParameters.Fruit] | Where-Object {
$_ -like "$wordToComplete*"
}
}
else {
$possibleValues.Values | ForEach-Object { $_ }
}
}
function Get-FruitVariety {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateSet('Apple', 'Banana', 'Orange')]
$Fruit,
[Parameter(Mandatory = $true)]
[ArgumentCompleter({ VarietyArgumentCompleter @args })]
$Variety
)
"You selected the fruit $Fruit and variety of $Variety."
}
Watch the GIF below where the Fruit
parameter is used first, and Tab completion provides the correct fruit varieties for the Variety
parameter.

ArgumentCompleter Script Block Code Improvements
However, the ArgumentCompleter
script block implementation above has a few issues. First, if you specify the Variety
parameter first, using Tab completion does not match the correct Fruit
type, like this:

Second, if you use the Variety
parameter first and start entering a variety type, then pressing Tab doesn’t try to match a particular variety. Instead, it starts iterating through the possible keys starting with “Gala” since it is the first value in the first item in the list.

The updated example below resolves these issues. First, the VarietyArgumentCompleter
adds a Where-Object
clause to find a match based on the $wordToComplete
variable value.
function VarietyArgumentCompleter {
param ( $commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters )
$possibleValues = @{
Apple = @('Gala', 'Honeycrisp', 'GrannySmith')
Banana = @('Cavendish', 'LadyFinger', 'Manzano')
Orange = @('Mandarin', 'Navel', 'Blood')
}
if ($fakeBoundParameters.ContainsKey('Fruit')) {
$possibleValues[$fakeBoundParameters.Fruit] | Where-Object {
$_ -like "$wordToComplete*"
}
}
else {
$possibleValues.Values | ForEach-Object { $_ } | Where-Object { $_ -like "$wordToComplete*" }
}
}
Second, the Fruit
parameter gets its own ArgumentCompleter
script block that searches for a matching fruit if the Variety
parameter is already specified and Fruit
does not have a value. If neither conditions are true, then the script block returns the Fruit
parameter’s possible values.
function FruitArgumentCompleter {
param ( $commandName,
$parameterName,
$wordToComplete,
$commandAst,
$fakeBoundParameters )
$possibleValues = @{
Apple = @('Gala', 'Honeycrisp', 'GrannySmith')
Banana = @('Cavendish', 'LadyFinger', 'Manzano')
Orange = @('Mandarin', 'Navel', 'Blood')
}
if ($fakeBoundParameters.ContainsKey('Variety') -and [string]::IsNullOrEmpty($fakeBoundParameters['Fruit'])) {
$fruitMatchesArray = [System.Collections.ArrayList]::new()
foreach ($key in $possibleValues.Keys) {
if ($possibleValues[$key] -contains $fakeBoundParameters['Variety']) {
$fruitMatchesArray.Add($key) | Out-Null
}
}
$fruitMatchesArray
}
else {
$possibleValues.Keys | ForEach-Object { $_ } | Where-Object { $_ -like "$wordToComplete*" }
}
}
Finally, the function definition is updated to include the new Fruit ArgumentCompleter script block.
function Get-FruitVariety {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ArgumentCompleter({ FruitArgumentCompleter @args })]
$Fruit,
[Parameter(Mandatory = $true)]
[ArgumentCompleter({ VarietyArgumentCompleter @args })]
$Variety
)
"You selected the fruit $Fruit and variety of $Variety."
}
The above update example is a bit more complicated as each parameter’s possible values can be dependent on the value found in the other parameter.
Using Register-ArgumentCompleter for Tab Completion
The Register-ArgumentCompleter
cmdlet registers a custom argument completer. The difference between this ArgumentCompleter
and the prior example is this ArgumentCompleter
script block can be used for any command, not just the ones you write.
For example, let’s say you want to configure the Id
parameter of Set-TimeZone
to tab complete the time zones in your region. By default, the Id
parameter does not have tab completion, but you can write a customer ArgumentCompleter
to return only the time zones you are interested in.
In the example below, you define a variable named $timeZoneIdScriptBlock
. In the script block, you use the same default parameters used earlier in the ArgumentCompleter script block example ($commandName
, $parameterName
, $wordToComplete
, $commandAst
, $fakeBoundParameters
).
Next, write the logic to build the tab completion values for the parameter. In this case, the script block searches for time zones where the DisplayName
property contains “(US & Canada)“. The script block returns the Id
property of the matching time zones.
Finally, you use the Register-ArgumentCompleter
cmdlet and specify the CommandName
to apply the ArgumentCompleter
to (Set-TimeZone
), the ParameterName
to configure (Id
), and the ScriptBlock
to use ($timeZoneIdScriptBlock
).
$timeZoneIdScriptBlock = {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
(Get-TimeZone -ListAvailable |
Where-Object -Property DisplayName -like -Value "*(US & Canada)*").Id |
ForEach-Object { "'$_'" }
}
Register-ArgumentCompleter -CommandName Set-TimeZone -ParameterName Id -ScriptBlock $timeZoneIdScriptBlock
With the ArgumentCompleter
registered, you can now tab through the possible Id
values for the Set-TimeZone
cmdlet that matches your criteria. In this case, tab completion should show four primary time zones for the US and Canada: Eastern, Central, Mountain, and Pacific.

Two things to note about this method:
- The user can still specify other values for the parameter. Registering the argument completer only provides the suggested values for using the parameter.
- The
ArgumentCompleter
script block is only valid for the current PowerShell session. If you want to use this all the time, you must include it in your PowerShell profile.
PowerShell Tab Completion Summary
Providing tab completion values for your parameters (or arguments) is a great way to enhance your custom advanced functions. Tab completion values provide the function user more context on what is expected for the command and are a great way to show off your advanced PowerShell function writing abilities.
Enjoyed this article? Check out more PowerShell content here!