powershellparameter-sets

PowerShell Parameter Set Requires Named Parameter


I have the following snippet of a functions parameters and their sets

function Test {
    [CmdletBinding(DefaultParameterSetName='StringConsole')]

    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'ObjectFile')]
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName,
                   ParameterSetName = 'StringFile')]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ParameterSetName='StringFile',
                   Position = 0)]
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ParameterSetName='StringConsole',
                   Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Message,

        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ParameterSetName='ObjectFile',
                   Position = 0)]
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ParameterSetName='ObjectConsole',
                   Position = 0)]
        [ValidateNotNullOrEmpty()]
        [object]
        $Object,
 
        [Parameter(ParameterSetName='StringFile')]
        [Parameter(ParameterSetName='StringConsole')]
        [ValidateSet('Information', 'Verbose', 'Warning', 'Error', 'Object')]
        [string]
        $Severity = 'Information',

        [Parameter(ParameterSetName='StringFile')]
        [Parameter(ParameterSetName='StringConsole')]
        [switch]
        $NoPreamble,

        [Parameter(ParameterSetName = 'StringConsole')]
        [Parameter(ParameterSetName = 'ObjectConsole')]
        [switch]
        $Console
    )

}

If I call the function using

Test 'Hello, World'

it properly uses the StringConsole default parameter set from CmdletBinding

If I call the function using

Test -Message 'Hello, World' -Path C:\SomeFile.txt

It properly uses the StringFile parameter set

But if I call the function using

Test 'Hello, World' -Path C:\SomeFile.txt

I get this error and the function doesn't execute:

Parameter set cannot be resolved using the specified named parameters

The error specifically states it couldn't resolve the parameter set using the NAMED parameters. If a parameter gets bound by position does it not also satisfy the "named" parameter? Or do you have to specifically bind the parameter using the name?

Is there anyway I could design the parameter sets to make my last example work and not throw an error?


Solution

  • The logic used for your parameter sets looks perfectly fine but the issue is that you have 2 parameters with Position = 0 (-Message and -Object), normally this wouldn't be a problem but one of them is of the type System.Object and since all objects inherit from this class, no matter what you pass as argument in position 0 it will match this parameter. Since the other parameter on Position = 0 is of type System.String then 'Hello, World' (a string but also an object) matches both parameter sets and the binder has no idea which one did you mean to use.

    A very easy way of seeing this, without changing your current code and just adding $PSCmdlet.ParameterSetName to the function's body, would be to pass an integer as positional parameter and everything works as expected:

    function Test {
        [CmdletBinding(DefaultParameterSetName='StringConsole')]
        param(
            # same param block here
        )
    
        'Using: ' + $PSCmdlet.ParameterSetName
    }
    
    Test 0 -Path C:\SomeFile.txt # => Using: ObjectFile