ValidateSet
parameter attribute is helpful to tab through the specified values of a particular parameter in a CmdLet or a Function and it only accepts the values specified in the validate set and it cannot generate the dynamic values by default, whereas the ArgumentCompleter
parameter attribute is also similar to ValidateSet
attribute but in addition to it, it has an in-built scriptblock to generate the dynamic values to pass the value to a parameter with tab completion and also it accepts the values other than the values available with tab completion. And both the attributes allow tab completion with wildcard matching values.
We can create a separate
Class
to generate the dynamic values with theValidateSet
attribute.
Using the ValidateSet
, the values cannot be influenced by the other parameter values whereas using the ArgumentCompleter
we can generate the dynamic values of a parameter based on the other parameter values.
ArgumentCompleter
produces the tab completion values as defined in the scriptblock with certain optional and positional parameters, and executes it each time when we hit the Tab
, and of course it is the same with dynamic ValidateSet
attribute. PowerShell requires the tab-completion values from the scriptblock in the form of an array to navigate through when we hit the Tab
each time.
The function below Get-Country
will demonstrate how it works with the ValidateSet
parameter attribute.
Function Get-Country
{
[CmdLetBinding()]
param
(
[parameter(Mandatory = $true)]
[ValidateSet('India', 'USA', 'UK', 'Canada', 'Australia')]
[string] $CountryName
)
## Write your code here
Write-Host $CountryName
}
In the command-line when you write Get-Country -CountryName <Tab>
, it will navigate through the values defined in the ValidateSet
, but you can’t enter the values other than in the ValidateSet
.
To generate the dynamic values to use with the ValidateSet attribute, you need to define the functionality in a class and then use it with the ValidateSet
attribute.
class SupportedCountries : System.Management.Automation.IValidateSetValuesGenerator
{
[string[]] GetValidValues()
{
## Write your code here
$Countries = @('India', 'USA', 'UK', 'Canada', 'Australia')
return $Countries
}
}
Function Get-Country
{
[CmdLetBinding()]
param
(
[parameter(Mandatory = $true)]
[ValidateSet([SupportedCountries])]
[string] $CountryName
)
## Write your code here
Write-Host $CountryName
}
As mentioned in the above, the scriptblock in the ArgumentCompleter
has few optional and positional parameters that can be used if required, and now let’s see what are those parameters…
$CommandName
(Position 0) : This parameter is set to the name of the command for which the script block is providing tab completion.$ParameterName
(Position 1) : This parameter is set to the parameter whose value requires tab completion.$WordToComplete
(Position 2) : This parameter is set to value the user has provided before they pressed Tab. Your script block should use this value to determine tab-completion values.$CommandAst
(Position 3) : This parameter is set to the Abstract Syntax Tree (AST) for the current input line.$FakeBoundParameters
(Position 4) - This parameter is set to a hashtable containing the $PSBoundParameters for the cmdlet before the user pressed Tab.The parameter names can be anything but should be in the same position.
The Get-Country
function below will accept the values specified in the scriptblock through tab completion and also allows other values by passing manually…
Function Get-Country
{
[CmdLetBinding()]
param
(
[parameter(Mandatory = $true)]
[ArgumentCompleter( {
param ( $CommandName,
$ParameterName,
$WordToComplete,
$CommandAst,
$FakeBoundParameters )
# Write your code here
return @('India', 'USA', 'UK', 'Canada', 'Australia')
})]
[string] $CountryName
)
Write-Host $CountryName
}
In the above example when you type Get-Country -CountryName <tab>
it will navigate through the country names specified in the scriptblock and also accepts other country names as well. If you use the wildcard it will navigate through the matching values only and of course it is also applicable to the ValidateSet
attribute as well.
Now, let’s see another example that will dynamically generate the tab completion values based on the other parameter value…
Function Get-Country
{
[CmdLetBinding()]
param
(
[parameter(Mandatory = $false)]
[ValidateSet('''North America''', 'Europe', 'Asia', 'Oceania')]
[string] $Continent,
[parameter(Mandatory = $true)]
[ArgumentCompleter( {
param ( $CommandName,
$ParameterName,
$WordToComplete,
$CommandAst,
$FakeBoundParameters )
$CountriesByContinent = @{
'North America' = @('USA', 'Canada')
Europe = @('UK', 'Germany')
Asia = @('India', '''Sri Lanka''')
Oceania = @('''New Zealand''', 'Australia')
}
if ($fakeBoundParameters.ContainsKey('Continent'))
{
$CountriesByContinent[$fakeBoundParameters.Continent] | Where-Object { $_ -like "$wordToComplete*" }
}
else
{
$CountriesByContinent.Values | ForEach-Object { $_ }
}
})]
[string] $CountryName
)
Write-Host $CountryName
}
In the above example, I have not used all the parameters though, but the values of the parameters will be as follows…
$CommandName is Get-Country
$ParameterName is CountryName
$WordToComplete, here in this example it is empty but if you use Get-Country -CountryName Ger<Tab>
then the value of this parameter is Ger
$CommandAst is Get-Country -CountryName
$FakeBoundParameters, if you run Get-Country -Continent Asia -CountryName India
then the value of this parameter is @{Continent = ‘Asia’} which is a hashtable, if there are any other bound parameters then they will also be returned in the form of hashtable itself.
To give an ability to tab through the valid values of a parameter in a CmdLet or a Function, you can use the Register-ArgumentCompleter
CmdLet to register the valid argument completers to navigate through between the Tab
hits.
The Register-ArgumentCompleter
will also accept the script block as it is in the ArgumentCompleter
parameter attribute with all the optional and positional parameters to register the argument completers of a parameter in a CmdLet or a Function.
Now let’s see how it works…
Function Get-Country
{
[CmdLetBinding()]
param
(
[parameter(Mandatory = $false)]
[ValidateSet('''North America''', 'Europe', 'Asia', 'Oceania')]
[string] $Continent,
[parameter(Mandatory = $true)]
[string] $CountryName
)
## Write your code here
Write-Host $CountryName
}
In the above function, there is no ValidateSet
or ArgumentCompleter
parameter attributes to CountryName
parameter, and now we will register the argument completers using Register-ArgumentCompleter
CmdLet to work with tab completion, and we will use the same script that we have used in the above example.
$ScriptBlock = [scriptblock]::Create({
param ( $CommandName,
$ParameterName,
$WordToComplete,
$CommandAst,
$FakeBoundParameters )
$CountriesByContinent = @{
'North America' = @('USA', 'Canada')
Europe = @('UK', 'Germany')
Asia = @('India', '''Sri Lanka''')
Oceania = @('''New Zealand''', 'Australia')
}
if ($fakeBoundParameters.ContainsKey('Continent'))
{
$CountriesByContinent[$fakeBoundParameters.Continent] | Where-Object { $_ -like "$WordToComplete*" }
}
else
{
$CountriesByContinent.Values | ForEach-Object { $_ }
}
})
Register-ArgumentCompleter -CommandName Get-Country -ParameterName CountryName -ScriptBlock $ScriptBlock
You can also register the argument completers to any CmdLets or Functions from any vender and to any native applications as well.
$ScriptBlock = [scriptblock]::Create({
param ( $CommandName,
$ParameterName,
$WordToComplete,
$CommandAst,
$FakeBoundParameters )
$Shares = Get-SmbShare | Where-Object {$_.Name -like "$WordToComplete*"}
$Shares | ForEach-Object {
New-Object -Type System.Management.Automation.CompletionResult -ArgumentList $_.Name,
$_.Name,
"ParameterValue",
$_.Name
}
})
Register-ArgumentCompleter -CommandName Set-SmbShare -ParameterName Name -ScriptBlock $ScriptBlock