StephenP
StephenP

Reputation: 4081

Postional Binding and ParameterSets

I'm struggling to understand the behavior of parameter sets and positional binding. Here is the sample function.

function Test-PositionBinding {
    [CmdletBinding(DefaultParameterSetName = 'ParamSet2')]
    param (
        [Parameter(ParameterSetName='ParamSet1',Position = 0)]
        [int]
        $param1,

        [Parameter(ParameterSetName='ParamSet1',Position = 1)]
        [int]
        $param2,

        [Parameter(ParameterSetName='ParamSet2',Position = 0)]
        [int]
        $param3
    )    
    process {
        $PSCmdlet.ParameterSetName
        $param1
        $param2
        $param3
    }
}

Based on the help output it would seem that calling the function with a single int should use paramset1 and calling it with 2 int should use paramset2 which is what i would expect.

SYNTAX
    Test-PositionBinding [[-param3] <int>] [<CommonParameters>]

    Test-PositionBinding [[-param1] <int>] [[-param2] <int>] [<CommonParameters>]

However, I receive the following output.

PS C:\> Test-PositionBinding 1
ParamSet2
0
0
1
PS C:\> Test-PositionBinding 1 2

Test-PositionBinding: A positional parameter cannot be found that accepts argument '2'.

I would have expected the following.

PS C:\> Test-PositionBinding 1 2
ParamSet1
1
2
0

If we change the type of param1 to a string the function works as expected.

PS C:\> Test-PositionBinding 1
ParamSet2

0
1
PS C:\> Test-PositionBinding 'abc'
ParamSet1
abc
0
0
PS C:\> Test-PositionBinding 'abc' 2
ParamSet1
abc
2
0

What is it that I am misunderstanding about the way PS interprets parameter sets?

Ultimately my question, is why is PS unable to differentiate between 2 parameter sets with positional binding and different parameter counts if the first parameter is of the same type between sets.

Upvotes: 1

Views: 925

Answers (2)

postanote
postanote

Reputation: 16096

As per the MS Docs:

Parameter set requirements The following requirements apply to all parameter sets.

Each parameter set must have at least one unique parameter. If possible, make this parameter a mandatory parameter.

A parameter set that contains multiple positional parameters must define unique positions for each parameter. No two positional parameters can specify the same position.

Only one parameter in a set can declare the ValueFromPipeline keyword with a value of true. Multiple parameters can define the ValueFromPipelineByPropertyName keyword with a value of true.

If no parameter set is specified for a parameter, the parameter belongs to all parameter sets.

This is a good write up on this topic that kept around that helped me. Snipping it here, vs just giving you the link to bounce over to. Thogh this is not parameterset specific, the same rules apply.

Specifying PowerShell Parameter Position

It makes sense for some commands to allow you to pass things by position rather than by name, especially in cases where there would be little confusion if the names of the parameters are left out (as in this example).

function Test-Position
{
    [CmdletBinding()]
    Param
    (
        [parameter(Position=0)]$parm1,
        [parameter(Position=1)]$parm2,
        [parameter(Position=2)]$parm3,
        [parameter(Position=3)]$parm4
    )
}

What should I do?

> According to the help (about_Functions_CmdletBindingAttribute), you should use the PositionalBinding optional argument to the CmdletBinding() attribute, and set it to $false.

That will cause all parameters to default to not be allowed by position. Then, > you can specify the Position for any (hopefully only one or two) parameters you wish to be used by position.

For instance, this will only allow $parm1 to be used by position:

function Test-Position
{
    [CmdletBinding(PositionalBinding = $false)]
    Param([parameter(Position=0)]$parm1,
                                    $parm2,
                                    $parm3,
                                    $parm4)
}

But wait, it gets easier

Even though the help says that all parameters are positional by default, it turns out that using Position on one parameter means that you have to use it on any parameters you want to be accessed by position.

For instance, in this version of the function I haven’t specified ositionalBinding=$False in the CmdletBinding attribute, but only the first parameter is available by position.

function Test-Position2
{
    [CmdletBinding()]
    Param
    (
        [parameter(Position=0)]$parm1, 
                                $parm2, 
                                $parm3, 
                                $parm4
    )
}

That’s interesting to me, as it seems to contradict what’s in the help. Specifically, the help says that all parameters are positional. It then says that in order to disable this default, you should use the PositionalBinding parameter. This shows that you don’t need to do that, unless you don’t want any positional parameters.

As a final example, just to make sure we understand how the Position value is used, consider the following function and syntax help:

function Test-Position3
{
    [CmdletBinding()]
    Param
    (
        $parm1,
        $parm2,
        [parameter(Position=1)]$parm3,
        [parameter(Position=0)]$parm4
    )
}

By including Position on 2 of the parameters, we’ve ensured that the other two parameters are only available by name. Also, the assigned positions differ from the order that the parameters are defined in the function, and that is reflected in the syntax help.

Upvotes: 1

Thom Schumacher
Thom Schumacher

Reputation: 1583

If you add an IF you can see when you choose param 1 or 2 you are in paramset 1

if you choose param1 or 2 you can't select param 3 or 4

if you run Test-postionbinding 1

Then since param1 is defined as 0 it will powershell will automatically bind the first parameter passed to the function to position 0.
if you pass 0 -param4 1
Param3 will have a value of 0 and param4 will be 1 and you fall into paramset2.

If you specify param1 then your only other parameter is param2 because its a member of Paramset1

If you choose param3 then the only other available param is param4 because it's part of paramset2

function Test-PositionBinding {
    [CmdletBinding(DefaultParameterSetName = 'ParamSet2')]
    param (
        [Parameter(ParameterSetName='ParamSet1',Position = 0)]
        [int]
        $param1,

        [Parameter(ParameterSetName='ParamSet1',Position = 1)]
        [int]
        $param2,

        [Parameter(ParameterSetName='ParamSet2',Position = 0)]
        [int]
        $param3
    )    
    process {
        if($PSCmdlet.ParameterSetName -eq "paramset1")
        {
            write-output "Paramset1"
            $param1
            $param2
        }

        if($PSCmdlet.ParameterSetName -eq "paramset2")
        {
            write-output "Paramset2"
            $param3
            $param4
        }
    }
}

Observing position 2 binding

Test-PositionBinding -param1 1 10
Paramset1
1
10

Observing paramset2

Test-PositionBinding -param3 10
Paramset2
10

using no parameter names and forcing powershell to use the binding specified: Test-PositionBinding 0 1 Test-PositionBinding : A position

al parameter cannot be found that accepts argument '1'.
At line:1 char:1
+ Test-PositionBinding 0 1
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Test-PositionBinding], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Test-PositionBinding


PS C:\WINDOWS\system32> Test-PositionBinding 0
Paramset2
0

Upvotes: 0

Related Questions