powershellparameter-passingparameter-sets

Parameters - Requiring Some But Not Others - Correct Use of Parameter Sets


So I'm experimenting with PowerShell and having a little trouble understanding parameters. From what I've read if I specify a parameter to be at the same position as another but place it in a separate ParameterSet PowerShell will only require one of these parameters to be present.

In this example that works as expected -

[CmdletBinding(DefaultParameterSetName='MultiUser')]

Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Token,

[Parameter(Mandatory=$True,Position=2, ParameterSetName="MultiUser")]
[string]$UsernamesFile,

[Parameter(Mandatory=$True,Position=2, ParameterSetName="SingleUser")]
[string]$SingleUsername,

[Parameter(Mandatory=$False)]
[switch]$SpecialCase
)

But if I wanted to expand on this so that as above you have to specify a token and then you must specify either a single username or a usernames files but now I would like to specify a group the user was going to be in.

Now let's assume the user has to go into one of two groups and I don't want to worry about dealing with different ways a user may input a group name so I use two switches. I only want the user to be in a single group rather than both so the switches should be at the same position but in different parameter sets (based on what I've read and that the above example works).

So my second example looks like this -

[CmdletBinding(DefaultParameterSetName='MultiUser')]
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Token,

[Parameter(Mandatory=$True,Position=2, ParameterSetName="MultiUser")]
[string]$UsernamesFile,

[Parameter(Mandatory=$True,Position=2, ParameterSetName="SingleUser")]
[string]$SingleUsername,

[Parameter(Mandatory=$True,Position=3, ParameterSetName="GroupA")]
[switch]$GroupA,

[Parameter(Mandatory=$True,Position=3, ParameterSetName="GroupB")]
[switch]$GroupB,

[Parameter(Mandatory=$False)]
[switch]$SpecialCase
)

This doesn't work as expected however, this gives me an error -

New Parameters Can't Be Found

Could someone explain why this doesn't work and correct my understanding of PowerShell parameters?

Thanks!


Solution

  • See bottom for an explanation of the problem with the OP's approach.

    To get exactly what you're asking for, you'd have to use the following:

    # - Make sure that parameters are NON-positional unless explicitly marked otherwise.
    # - Specify the default parameter set.
    [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName='MultiUserA')]
    Param(
    
      # Belongs to all parameter sets.
      [Parameter(Mandatory, Position=1)]
      [string]$Token,
    
      # Mandatory and positional both when combined with -GroupA or -GroupB.
      [Parameter(Mandatory, Position=2, ParameterSetName='MultiUserA')]
      [Parameter(Mandatory, Position=2, ParameterSetName='MultiUserB')]
      [string] $UsernamesFile,
    
      # Mandatory - but not positional - both when combined with -GroupA or -GroupB.
      [Parameter(Mandatory, ParameterSetName='SingleUserA')]
      [Parameter(Mandatory, ParameterSetName='SingleUserB')]
      [string] $SingleUsername,
    
      # Mandatory, whether combined with -UsernamesFile or -SingleUsername
      [Parameter(Mandatory, ParameterSetName='SingleUserA')]
      [Parameter(Mandatory, ParameterSetName='MultiUserA')]
      [switch] $GroupA,
    
      # Mandatory, whether combined with -UsernamesFile or -SingleUsername
      [Parameter(Mandatory, ParameterSetName='SingleUserB')]
      [Parameter(Mandatory, ParameterSetName='MultiUserB')]
      [switch] $GroupB,
    
      # Belongs to all parameter sets. Non-mandatory by default.
      [switch] $SpecialCase
    
    )
    

    As you can see,

    When you invoke your script with -? (or pass it to Get-Help) you'll see the resulting syntax diagram:

    script.ps1 [-Token] <string> [-UsernamesFile] <string> -GroupA [-SpecialCase] [<CommonParameters>]
    script.ps1 [-Token] <string> [-UsernamesFile] <string> -GroupB [-SpecialCase] [<CommonParameters>]
    script.ps1 [-Token] <string> -SingleUsername <string> -GroupB [-SpecialCase] [<CommonParameters>]
    script.ps1 [-Token] <string> -SingleUsername <string> -GroupA [-SpecialCase] [<CommonParameters>]
    

    However, this approach is ill-advised for the following reasons:


    As pointed out in Mathias R. Jessen's helpful answer, the better approach is to use a single parameter for the target group that accepts only a value from a given set of values, which the [ValidationAttribute] can ensure:

    [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName='MultiUser')]
    Param(
    
      [Parameter(Mandatory, Position=1)]
      [string] $Token,
    
      [Parameter(Mandatory, Position=2, ParameterSetName='MultiUser')]
      [string] $UsernamesFile,
    
      [Parameter(Mandatory, ParameterSetName='SingleUser')]
      [string] $SingleUsername,
    
      # Single -Group parameter that only accepts values 'GroupA' and 'GroupB'
      # Input validation is case-INsensitive, as usual.
      [Parameter(Mandatory)]
      [ValidateSet('GroupA', 'GroupB')]
      [string] $Group,
    
      [switch] $SpecialCase
    
    )
    

    This gives us the following syntax diagrams (note that the set of valid values for -Group is not reflected):

    script.ps1 [-Token] <string> [-UsernamesFile] <string> -Group <string> [-SpecialCase] [<CommonParameters>]
    script.ps1 [-Token] <string> -SingleUsername <string> -Group <string> [-SpecialCase] [<CommonParameters>]
    

    As for problems with your original approach: