ArgumentCompleter vs ValidateSet and Register-ArgumentCompleter


ValidateSet vs ArgumentCompleter

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

Using ValidateSet Attribute

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.

Using dynamic ValidateSet Attribute

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
}

ArgumentCompleter script block

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.

Using ArgumentCompleter Attribute

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.

Using Register-ArgumentCompleter CmdLet

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

Share it on     |   |   |   | 
  Prev:  

Get All The aliases By CmdLet

  :Next  

PSCustomObject Using Select-Object

comments powered by Disqus