Home HowTo Parameters Dynamic Argument Completion in PSv3
 

Dynamic Argument Completion in PSv3

A couple days ago, we illustrated how your functions can support the new PowerShell 3.0 Intellisense features. But what if you want dynamic Intellisense for your parameter values?

Dynamic argument completion in your own functions

Dynamic argument completion in your own functions

What Dynamic Completion Is

Normally, completion relies on static lists. They can come from enumeration types or validation sets, but however you do it, the autocompletion list for your function will always be the same. Dynamic argument completion, in contrast, uses code to dynamically put together the completion list.

You can see dynamic parameter value completion in action, because many built-in cmdlets use it:

Stop-Process -Name #<-- press TAB or CTRL+SPACE here!

Can Functions Use Dynamic Completion, Too?

Yes, they can! PowerShell 3.0 has updated its internal autocompletion mechanism and uses an internal function called tabexpansion2. So whenever the user invokes tabcompletion or opens Intellisense menus, PowerShell calls tabcompletion2. This function, in turn, returns autocompletion results that are picked up by the shell or by the ISE editor.

This all sounds pretty technical, and it really is. But: The PowerShell team has added a parameter called -Options to tabexpansion2. It takes custom completion handlers. So to support any type of tabcompletion for your function – including dynamic parameter value completion – you can submit your completion handlers.

You can download all source code here:

Dynamic Argument Value Completion
Dynamic Argument Value Completion
dynamic_argument_completion.ps1
Version: 1.0
1.6 KiB
495 Downloads
Details...

Here is a custom completion handler that would complete parameter values with process names:

$completion_Process = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)

    Get-Process | Sort-Object -Property Name -Unique | Where-Object { $_.Name -like "$wordToComplete*" } |ForEach-Object {
        New-Object System.Management.Automation.CompletionResult $_.Name, $_.Name, 'ParameterValue', ('{0} ({1})' -f $_.Description, $_.ID) 
    }
}

Note how the completer uses plain PowerShell statements to put together the autocompletion list. The important part is to return the completion information as CompletionResult object. You can do tons of cool things with it. It takes the (a) completion text, (b) the text displayed in an Intellisense Menu, (c) the type of icon to assign to the completion entry, and (d) the tooltop text for it.

In the example above, menu and completion text both are the process name. The tooltip text is comprised of the process description (if available) and the process ID.

Here is another completer that would improve parameter value completion for Stop-Process. By default, the -Name parameter of Stop-Process completes to a list of all services. A better approach would be to only list running services, and this completer would do that:

$completion_RunningService = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)

    Get-Service | Where-Object { $_.Status -ne 'Stopped' } | Sort-Object -Property Name | Where-Object { $_.Name -like "$wordToComplete*" } |ForEach-Object {
        New-Object System.Management.Automation.CompletionResult $_.Name, $_.Name, 'ParameterValue', ('{0} ({1})' -f $_.DisplayName, $_.Status) 
    }
}

Getting New Completers Into TabExpansion2

The ultimate challenge however is to let tabexpansion2 know that there are new completers. While it has a parameter -Options, you can hardly use it because you don’t call tabexpansion2. PowerShell calls it internally, and it won’t use that parameter.

So currently, the only way to make this work is manipulating tabexpansion2 slightly and feed in your own completers as new options. Here’s how:

if (-not $global:options) { $global:options = @{CustomArgumentCompleters = @{};NativeArgumentCompleters = @{}}}
$global:options['CustomArgumentCompleters']['Test-DynamicArguments:ProcessName'] = $Completion_Process
$global:options['CustomArgumentCompleters']['Stop-Service:Name'] = $Completion_RunningService

$function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}'

First, your completers need to be organized in a special hash table. Note how your completers are hooked up with specific functions and their parameters: The completers are added to the hash table with a key of this format: functionname:parametername. You can even use wildcards, for example, if you want to complete the -Something parameter of any function.

In the example, $Completion_Process is added to the ProcessName parameter of Test-DynamicArgument, which is just a sample function, illustrating that this works for any function, including your own:

function Test-DynamicArguments
{
    [CmdletBinding()]
    param
    (
        $ProcessName
    )

    "Hello $ProcessName"
}

The $Completion_RunningService is tied to the -Name parameter of Stop-Service.

Challenges

The challenge remains: is there a way to feed in custom completers into PowerShell without fiddling with tabcompletion2? Or would we have to chain tabcompletion2 to not overwrite changes from other sources? $PSDefaultParameters would have been a great way for dealing with this. Unfortunately, it does not apply to tabexpansion2. Finally: how would module authors submit completion information to PowerShell in a civilized manner?

Let us know what you think and share your feedback please!

 

facebooktwittergoogle_pluspinterestlinkedinfacebooktwittergoogle_pluspinterestlinkedin  rssrss
Dynamic Argument Value Completion
Dynamic Argument Value Completion
dynamic_argument_completion.ps1
Version: 1.0
1.6 KiB
495 Downloads
Details...
The following two tabs change content below.

Tobias

PowerShell Wizard at scriptinternals
I love to create things and master challlenges. That's why I love PowerShell so much. I am a 10+ year Microsoft MVP, have written more than a 100 IT-related books with Microsoft Press and others, and like to expain complex things in a simple and understandable way. I am giving PowerShell trainings throughout Europe and organize the annual German PS Community Conference.

Latest posts by Tobias (see all)