powershellautocompletesingle-quotes

PowerShell - How to surround argumentcompleter auto-completed arguments (that contain spaces) with single quotes?


I'm doing a simple demo to illustrate the ArgumentCompleter attribute in a function, and this is the code I'm working with so far:

#Requires -Modules Hyper-V
#Requires -RunAsAdministrator
function Get-VMNameTest
{
    param (
        [ArgumentCompleter({
            param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)
            $VmNameArg = (Get-VM).Name | Where-Object {$_.Name -like "*$WordToComplete*"}
            *foreach ($vm in $VmNameArg)
            *{
            *    if ($vm -match '\s+')
            *    {
            *        "'" + $vm + "'"
            *    }
            *    else {$vm}
            *}
        })]
        [string[]]$Name
    )

    foreach ($vm in $Name)
    {
        Write-Output "VMName:  $vm"
    }
}

The -Name parameter will autocomplete any VM (name) that's found on the local computer. I added a foreach loop (indicated with *'s) to add single quotes around any VM names that contain spaces -- otherwise a VM name (that contains spaces) will show up without quotes on the command line.

When using CTRL + SPACE to see the autocomplete values, native cmdlets present the values (that contain spaces) without single quotes in the list, but as soon as you arrow-key over a value with spaces, it automatically adds the single quotes to the command line (in this case, it presents as a path using .\ and a trailing \. To illustrate:

PS C:\> Get-Item -Path '.\Program Files\'
$Recycle.Bin               System Volume Information
Documents and Settings     Users
Program Files              Windows
Program Files (x86)
ProgramData

("Program Files" is selected via arrow keys in this example).




This is how my code appears with the foreach loop - the single quotes appear in both the list and the command line:

PS C:\> Get-VMNameTest -Name 'Server Two'
ServerOne                  'Server Two'


Without the foreach loop, single quotes do not surround values with spaces (which is obviously problematic):

PS C:\> Get-VMNameTest -Name Server Two
ServerOne                  Server Two


I'm trying to remain consistent with the behavior of native cmdlets, so this is what I'm trying to achieve:

PS C:\> Get-VMNameTest -Name 'Server Two'
ServerOne                  Server Two



Clearly there's a difference in how native (compiled) cmdlets behave and the logic that adds the single quotes (or adds path separators, escapes necessary characters, etc). Although it's a minor cosmetic difference, I want to know if there's a better way to approach this. How do the pros do it?


Solution

  • If I understood correctly, you're looking to display completion items unquoted but when an item is selected it should be quoted, if that's the case, then you can accomplish it using the CompletionResult(String, String, CompletionResultType, String) overload:

    function Test-Completion {
        param(
            [ArgumentCompleter({
                param (
                    $Command,
                    $Parameter,
                    $WordToComplete,
                    $CommandAst,
                    $FakeBoundParams
                )
    
                # generate a list of items here for testing,
                # some can have spaces, others dont
                $list = 0..10 | ForEach-Object {
                    if($_ % 2) {
                        return "Item $_"
                    }
    
                    "Item$_"
                }
    
                foreach($item in $list) {
                    if($item -notlike "$wordToComplete*") {
                        continue
                    }
    
                    $completionText = $item
                    # if this item has a space
                    if($item -match '\s') {
                        $completionText = "'$item'"
                    }
    
                    [System.Management.Automation.CompletionResult]::new(
                        $completionText, $item,
                        [System.Management.Automation.CompletionResultType]::ParameterValue,
                        'some tooltip here if you want')
                }
            })]
            [Parameter(Mandatory)]
            [string] $Test
        )
    
        $Test
    }