12

How do you implement the parameter tab completion for PowerShell functions or cmdlets like Get-Service and Get-Process in PowerShell 3.0?

I realise ValidateSet works for a known list, but I want to generate the list on demand.

Adam Driscoll hints that it is possible for cmdlets but unfortunately hasn't elaborated.

Trevor Sullivan shows a technique for functions, but as I understand it, his code only generates the list at the time the function is defined.

3
  • Have you read here: powertheshell.com/dynamicargumentcompletion Commented Feb 13, 2013 at 6:55
  • No I hadn't found that. Very informative Commented Feb 13, 2013 at 10:57
  • You can also take a look to powertab.codeplex.com it's a 'dynamic intellissense ' from powershell v.2 but I'use it also in 3.0 very fine Commented Feb 13, 2013 at 10:59

3 Answers 3

7

I puzzled over this for a while, because I wanted to do the same thing. I put together something that I'm really happy with.

You can add ValidateSet attributes from a DynamicParam. Here's an example of where I've generated my ValidateSet on-the-fly from an xml file. See the "ValidateSetAttribute" in the following code:

function Foo() {
    [CmdletBinding()]
    Param ()
    DynamicParam {
        #
        # The "modules" param
        #
        $modulesAttributeCollection = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]

        # [parameter(mandatory=...,
        #     ...
        # )]
        $modulesParameterAttribute = new-object System.Management.Automation.ParameterAttribute
        $modulesParameterAttribute.Mandatory = $true
        $modulesParameterAttribute.HelpMessage = "Enter one or more module names, separated by commas"
        $modulesAttributeCollection.Add($modulesParameterAttribute)    

        # [ValidateSet[(...)]
        $moduleNames = @()
        foreach($moduleXmlInfo in Select-Xml -Path "C:\Path\to\my\xmlFile.xml" -XPath "//enlistment[@name=""wp""]/module") {
            $moduleNames += $moduleXmlInfo.Node.Attributes["name"].Value
        }
        $modulesValidateSetAttribute = New-Object -type System.Management.Automation.ValidateSetAttribute($moduleNames)
        $modulesAttributeCollection.Add($modulesValidateSetAttribute)

        # Remaining boilerplate
        $modulesRuntimeDefinedParam = new-object -Type System.Management.Automation.RuntimeDefinedParameter("modules", [String[]], $modulesAttributeCollection)

        $paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramDictionary.Add("modules", $modulesRuntimeDefinedParam)
        return $paramDictionary
    }
    process {
        # Do stuff
    }
}

With that, I can type

Foo -modules M<press tab>

and it will tab-complete "MarcusModule" if that module was in the XML file. Furthermore, I can edit the XML file and the tab-completion behavior will immediately change; you don't have to re-import the function.

Sign up to request clarification or add additional context in comments.

Comments

4

Check the TabExpansionPlusPlus module on github, written by a former PowerShell team magician.

https://github.com/lzybkr/TabExpansionPlusPlus#readme

Comments

2

Classically, I used regex.

for example,

function TabExpansion {

    param($line, $lastWord) 

    if ( $line -match '(-(\w+))\s+([^-]*$)' )
    {
    ### Resolve Command name & parameter name
        $_param = $matches[2] + '*'
        $_opt = $Matches[3].Split(" ,")[-1] + '*'
        $_base = $Matches[3].Substring(0,$Matches[3].Length-$Matches[3].Split(" ,")[-1].length)

        $_cmdlet = [regex]::Split($line, '[|;=]')[-1]

        if ($_cmdlet -match '\{([^\{\}]*)$')
        {
            $_cmdlet = $matches[1]
        }

        if ($_cmdlet -match '\(([^()]*)$')
        {
            $_cmdlet = $matches[1]
        }

        $_cmdlet = $_cmdlet.Trim().Split()[0]

        $_cmdlet = @(Get-Command -type 'Cmdlet,Alias,Function,Filter,ExternalScript' $_cmdlet)[0]

        while ($_cmdlet.CommandType -eq 'alias')
        {
            $_cmdlet = @(Get-Command -type 'Cmdlet,Alias,Function,Filter,ExternalScript' $_cmdlet.Definition)[0]
        }

    ### Currently target is Get-Alias & "-Name" parameter

        if ( "Get-Alias" -eq $_cmdlet.Name -and "Name" -like $_param )
        {
           Get-Alias -Name $_opt | % { $_.Name } | sort | % { $_base + ($_ -replace '\s','` ') }
           break;
        }
    }
}

Reference http://gallery.technet.microsoft.com/scriptcenter/005d8bc7-5163-4a25-ad0d-25cffa90faf5


Posh-git renames TabExpansion to TabExpansionBackup in GitTabExpansion.ps1.
And posh-git's redifined TabExpansion calls original TabExpansion(TabExpansionBackup) when completions don't match with git commands.
So all you have to do is redefine TabExpansionBackup.

(cat .\GitTabExpansion.ps1 | select -last 18)
============================== GitTabExpansion.ps1 ==============================

if (Test-Path Function:\TabExpansion) {
    Rename-Item Function:\TabExpansion TabExpansionBackup
}

function TabExpansion($line, $lastWord) {
    $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()

    switch -regex ($lastBlock) {
        # Execute git tab completion for all git-related commands
        "^$(Get-AliasPattern git) (.*)" { GitTabExpansion $lastBlock }
        "^$(Get-AliasPattern tgit) (.*)" { GitTabExpansion $lastBlock }

        # Fall back on existing tab expansion
        default { if (Test-Path Function:\TabExpansionBackup) { TabExpansionBackup $line $lastWord } }
    }
}

===============================================================================

Redefine TabExpansionBackup(original TabExpansion)

function TabExpansionBackup {
    ...

    ### Resolve Command name & parameter name

    ...

    ### Currently target is Get-Alias & "-Name" parameter

    ...
}

1 Comment

I see that posh-git has already defined this function in my environment. Is there a way to extend/sub-class any existing definition?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.