How to add parameter TAB completion to your PowerShell functions

I work as System Administrator for more than 15 years now and I love to make my life easier by automating work & personal stuff via PowerShell (even silly things like food recipes list generation).
I think we all can agree that TAB completion is very useful and time saving feature that a lot of native PowerShell functions offer. But did you know that you can make this useful feature available even for your own custom functions?
In this post, I will show you two ways to make this work ๐
PS: By TAB completion I mean pressing TAB key to get available parameter values 
1. TAB completion defined globally for list of functions
The key here is to use Register-ArgumentCompleter PowerShell function.
Be noted that effect of using Register-ArgumentCompleter won't outlast console closure. To make it "permanent" place the code into your PowerShell profile script.
Register-ArgumentCompleter has three important parameters:
- 1.CommandName
- List of functions/cmdlets this
TABcompletion will be applied to
- List of functions/cmdlets this
- 2.ParameterName
CommandNameparameter name thisTABcompletion will be applied to
- 3.ScriptBlock
- Special crafted scriptBlock, whose output will be shown as
TABcompletion
- Special crafted scriptBlock, whose output will be shown as
Basic example
# some dumb function with at least one parameter (fullName in this example)
function Open-DesktopFile {
param ($fullName)
# open given file
& $fullName
}
# scriptblock for Register-ArgumentCompleter
$openDesktopSb = {
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParams)
$DesktopPath = [Environment]::GetFolderPath("Desktop")
Get-ChildItem -Path $DesktopPath -File | Where-Object { $_.Name -like "*$WordToComplete*" } | Select-Object -ExpandProperty FullName | ForEach-Object { "'$_'" }
}
# activate `TAB` completion for 'Open-DesktopFile' functions parameter 'fullName'
# what pressing of `TAB` returns, is defined in openDesktopSb scriptblock
Register-ArgumentCompleter -CommandName Open-DesktopFile -ParameterName fullName -ScriptBlock $openDesktopSb
# now call Open-DesktopFile -fullName (and hit `TAB` key to fill it)
Silly example above shows basic scriptBlock for filling path to quoted files on your desktop.
As you can see, the Register-ArgumentCompleter scriptBlock operates with five automatically filled parameters: $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParams (their names doesn't matter, just their position!)
commandName- contains name of the function to which this completion will be applied to
parameterName- contains name of the parameter to which
TABwill be applied to
- contains name of the parameter to which
wordToComplete- contains string user have entered (if any)
commandAst- contains AST object of the current input line
fakeBoundParams- contains hashtable containing the
$PSBoundParametersfor thecommandNamefunction, before the user pressedTAB
- contains hashtable containing the
PS:
scriptBlockwill be run with each hit of TAB key! So make it as resource efficient as you can.
Example of leveraging fakeBoundParams parameter
- We all use Get-WmiObject and Get-CimInstance cmdlets, but wouldn't be nice to have
TABcompletion forClassname? Moreover for different Namespaces? ๐
$wmiClassSb = {
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParams)
if ($fakeBoundParams.Namespace) {
# use user entered namespace if available
$Namespace = $FakeBoundParams.Namespace
} else {
# use default namespace
$Namespace = "ROOT\cimv2"
}
Get-WmiObject -List -namespace $Namespace | ? {$_.Name -like "*$WordToComplete*"} | select -exp name
}
# make `TAB` completion work for Class parameter of Get-WmiObject, Get-CimInstance commands
Register-ArgumentCompleter -CommandName Get-WmiObject, Get-CimInstance -ParameterName Class -ScriptBlock $wmiClassSb
The result will look like this

2. TAB completion defined in function param() section
- As you already know, parameters defined in PowerShell
param()block, can have various attributes like type, validation etc. One of this attributes isArgumentCompleterand is used to defineTABcompletion- The usage is like this:
function Invoke-Greeting {
param (
[ArgumentCompleter( {
param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
Get-LocalUser | Where-Object { $_.Name -like "*$WordToComplete*" }
})]
[string] $userName
)
"Say hi to $userName"
}
# to test this out, call Invoke-Greeting and just hit TAB to show local user names
- So for specific parameter (
$userNamein this case) you define[ArgumentCompleter({ <scriptBlock> })]- The
<scriptBlock>has same format as forRegister-ArgumentCompleterfunction, so check first section of this post for more details
- The
And the result will look like this

Various examples just for inspiration
Search AD computer by its name or description
very handy if you have a lot of servers, and you save their purpose to their description attribute!
$computerSB = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $searcher = New-Object System.DirectoryServices.DirectorySearcher (([adsi]"LDAP://OU=Computer_Accounts,DC=contoso,DC=com"), '(objectCategory=computer)', ('name', 'description')) ($searcher.findall() | ? { $_.properties.name -match $wordToComplete -or $_.properties.description -match $wordToComplete }).properties.name | Sort-Object | % { "'$_'" } $searcher.Dispose() } # TAB completion of AD computer names in computerName parameter of any command from module Scripts, .. Register-ArgumentCompleter -CommandName ((Get-Command -Module Scripts).name) -ParameterName computerName -ScriptBlock $computerSB Register-ArgumentCompleter -CommandName Enter-PSSession, Invoke-Command -ParameterName computername -ScriptBlock $computerSB # TAB completion of AD computer names in identity parameter of commands with "computer" in their name from module ActiveDirectory Register-ArgumentCompleter -CommandName ((Get-Command -Module ActiveDirectory -Noun *computer*).name) -ParameterName identity -ScriptBlock $computerSBSimilarly if you have functions related to server or clients, you can limit the completion like this
$serverSB = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $searcher = New-Object System.DirectoryServices.DirectorySearcher (([adsi]"LDAP://OU=Servers,OU=Computer_Accounts,DC=contoso,DC=com"), '(objectCategory=computer)', ('name', 'description')) ($searcher.findall() | ? { $_.properties.name -match $wordToComplete -or $_.properties.description -match $wordToComplete }).properties.name | Sort-Object | % { "'$_'" } $searcher.Dispose() } Register-ArgumentCompleter -CommandName Invoke-MSTSC -ParameterName computerName -ScriptBlock $serverSB $clientSB = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $searcher = New-Object System.DirectoryServices.DirectorySearcher (([adsi]"LDAP://OU=Clients,OU=Computer_Accounts,DC=contoso,DC=com"), '(objectCategory=computer)', ('name', 'description')) ($searcher.findall() | ? { $_.properties.name -match $wordToComplete -or $_.properties.description -match $wordToComplete }).properties.name | Sort-Object | % { "'$_'" } $searcher.Dispose() } Register-ArgumentCompleter -CommandName Assign-Computer -ParameterName computerName -ScriptBlock $clientSB
Or what about replacing name for samaccountname in identity parameter of all functions from ActiveDirectory module (that contains 'User' in noun part)?
$userSB = {
param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
$searcher = New-Object System.DirectoryServices.DirectorySearcher (([adsi]"LDAP://OU=User_Accounts,DC=contoso,DC=com"), '(objectCategory=user)', ('name', 'samaccountname'))
($searcher.findall() | ? { $_.properties.name -match $wordToComplete }).properties.samaccountname | Sort-Object | % { "'$_'" }
$searcher.Dispose()
}
Register-ArgumentCompleter -CommandName ((Get-Command -Module ActiveDirectory -Noun *user*).name) -ParameterName identity -ScriptBlock $userSB
Summary
As you can see, you have two options to define TAB completion. Inside the function param() block using [ArgumentCompleter()] or globally using Register-ArgumentCompleter.
In general I use the latter option in my PowerShell profile script mainly for built-in functions and the first one, for my own custom functions.
In either way, its very handy, time saving and cool feature! ๐
Do you have any other cool examples of TAB completion?





