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,
You need to define 4 parameter sets that amount to all users-file-vs.-single-user and groupA-vs.-group-B combinations.
You need to assign each parameter to multiple parameter sets, choosing the appropriate subset.
- Note that each
[Parameter(...)]
attribute can only specify 1 parameter set, and whatever other attributes you specify there - Mandatory
, Position
, ... - apply to the parameter only in the context of the given parameter set.
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:
You shouldn't have mandatory [switch]
parameters, as they're by definition optional.
When you invoke the script without parameters for interactive argument entry, PowerShell won't let you specify a switch value (no values I've tried work: not true
, false
, 1
, 0
, ... - try with ./script someToken someFile
)
./script.ps1 someToken -SingleUsername someUser
gives a generic error message (Parameter set cannot be resolved using the specified named parameters.
) rather than specifically pointing out that either -GroupA
or -GroupB
is missing, because PowerShell cannot know whether you meant parameter set SingleUserA
or SingleUserB
.
- By contrast,
./script someToken someFile
- with implied -UsernamesFile
- is unambiguous, because MultiUserA
is the default parameter set, but due to the -GroupA
switch being mandatory, you still get a prompt for its value).
Last, but not least, as Mathias R. Jessen points out, using distinct, mutually exclusive switches (-GroupA
vs. -GroupB
) doesn't scale well, as adding more -Group*
switches quickly makes the number of combinations that must each be reflected in their own parameter set unmanageable - see below for how to avoid that.
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>]
This reduces the number of required parameter sets to 2.
It supports interactive entry of the group name (although, somewhat unfortunately, supplying an invalid name aborts the invocation).
If you omit -Group
, both the ./script.ps someToken someFile
and the ./script.ps someToken -SingleUserName someUser
now behave the same: they prompt for the -Group
value.
While having to type -Group
and a value is perhaps slightly more cumbersome on invocation than having distinct switches -GroupA
and -GroupB
,
- it is the more extensible approach, should the number of groups grow over time,
- tab completion does work to expand / cycle through the valid values.
As for problems with your original approach:
As Clijsters points out, your attempt to invoke ./script.ps1 -Token a -UsernamesFile someFile -GroupA
failed, because:
PowerShell needs to unambiguously resolve the given combination of parameters to a parameter set.
-GroupA
only belongs to parameter set GroupA
, whereas -UsersnameFile
only belongs to parameter set MultiUser
, so these parameters are in effect mutually exclusive, and PowerShell cannot determine what parameter set to use.
All non-switch parameters are positional by default - unless you explicitly deactivate that with [CmdletBinding(PositionalBinding=$False,...)]
- then only individual [Parameter(...)]
attributes explicitly marked with a Position
attribute become positional.
Also, there is no point in making a [switch]
parameter positional, as they're by definition non-positional: you always have to specify their name (unambiguously), and that allows them to be placed anywhere.