ValidateSet string with space(s)

I use PowerShell v4. Having written advanced function I want to use ValidateSet and give user two possible parameter values to choose from, one of these is a string with two spaces (Remote Desktop Users). However ValidateSet always throws an error. This is “old” topic but I want to know if there is any simple solution for this or it has been solved in PowerShell v5.

Can you share your code? I ran a simple test like this:

function test {
[CmdLetBinding()]
PARAM(
    [ValidateSet('Remote Desktop Users','Other Users')]
    [String]$param
)
write-host $param -ForegroundColor Green
}

test -param 'Remote Desktop Users'
test -param test

and this worked fine. I am using PowerShell v5 on Windows 10.

This works also in PowerShell v4 but in my case I have:

Get-LocalGroupMembership -ComputerName $pc -GroupName “Remote Desktop Users”

Since I have ValidateSet on GroupName parameter, tab completion automatically gives me two choices - if I choose one with spaces inside ValidateSet fails.

PowerShell v5 makes using argument completers easier (they’re available out of the box). You can use them in v3 and v4, but you and your users need to use the TabExpansionPlusPlus module (technically you could override a native PS function and bring the same functionality, but using the TabExpansionPlusPlus module is a much better solution). Argument completers let you control the output text a lot more. You could do something like this:

function Get-LocalGroupMembership {
    param(
        [string[]] $ComputerName,
        [string[]] $GroupName
    )

    process {
        $PSBoundParameters
    }
}

Register-ArgumentCompleter -CommandName Get-LocalGroupMembership -ParameterName GroupName -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    # This doesn't take $ComputerName into account (it can if you want; I don't know how
    # expensive that command would be, though)
    Get-CimInstance Win32_Group -Filter 'LocalAccount = TRUE' | select -ExpandProperty Name | where { $_ -like "*${wordToComplete}*" } | ForEach-Object {
        New-Object System.Management.Automation.CompletionResult (
            "'$_'",   # The text that will be put on the command line; I'm quoting everything here, but you could add logic to figure that out
            $_,       # The text that shows up in the IntelliSense list
            'ParameterValue', # What kind of completion result this is
            $_        # The tooltip
        )     
    }
}

TabExpansionPlusPlus has a helper function that makes creating the CompletionResult easier. I didn’t use it in case you run this from v5.

If you use completers, you can’t use ValidateSet. Well, you can, but the completion engine will present you with the ValidateSet completion results instead of your custom ones. That means you’d have to validate the group name in your code.

If you want to stick with ValidateSet, I think you might have to go with dynamic parameters. You could do something like the code below. It looks scary, but it’s just creating a ValidateSet with quotes if the command is being run internally (when PowerShell is running it without you knowing to get parameter information) and without quotes when the command is being run from the runspace (when you press the enter key):

function Get-LocalGroupMembership {
    [CmdletBinding()]
    param(
        [string[]] $ComputerName
    )

    DynamicParam {

        # Get valid groups
        $ExtraParams = @{}
        if ($ComputerName) {
            $ExtraParams['ComputerName'] = $ComputerName
        }
        $Groups = Get-CimInstance Win32_Group -Filter 'LocalAccount = TRUE' @ExtraParams | select -ExpandProperty Name

        # DynamicParam{} block is expected to return a dictionary of the parameters that
        # are created. Let's create that dictionary:
        $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        # Create the attributes we want to attach to the parameter
        $ParamAttributes = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParamAttributes.Add((New-Object Parameter))

        # Figure out if the string needs to be quoted (this can be simplified by using a RegEx, but I
        # like to let the parser tell me if it would have interpereted the string as more than one
        # item...)
        $ShouldQuote = {
            $tokens = $null
            $Text = $args[0]
            $null = [System.Management.Automation.Language.Parser]::ParseInput("echo $Text", [ref]$tokens, [ref]$null)
            if ($tokens.Length -ne 3) {
                $true
            }
            else {
                $false
            }
        }
        $ValidateSetStrings = $Groups | ForEach-Object {
            if ($MyInvocation.CommandOrigin -eq 'Internal' -and (& $ShouldQuote $_)) {
                    "'$($_ -replace "'", "''")'"
            }
            else {
                $_
            }
        }

        $ParamAttributes.Add((New-Object ValidateSet $ValidateSetStrings))

        # This generates the parameter and adds the attributes we created earlier. It also adds
        # it to the dictionary
        $ParameterName = 'GroupName'
        $ParamDictionary[$ParameterName] = New-Object System.Management.Automation.RuntimeDefinedParameter (
            $ParameterName,
            [string[]],
            $ParamAttributes
        )

        return $ParamDictionary
    }

    process {
        $PSBoundParameters
    }
}

Calling Get-CimInstance was quick on my local computer, but I have no idea how long it takes against a remote computer. If you don’t want dynamic results, you can take that call out and just hard code the valid groups you want.

It sounds better to “upgrade” PS to v5? I did that and there are no more problems with tab completion and ValidateSet.

I guess that’s what I get for not testing ValidateSet() with spaces before I answered :slight_smile:

This used to be a problem, even in v5. I’m not sure when it was fixed, but I’m glad to see that it was.

Tried single qoutes?

The examples you give is with double, that wil fail…