I am attempting to write a PowerShell function Set-MyRef
which uses ParameterSets to expose its available parameters. I would like the function to have parameter sets similar to:
Set-MyRef -Latest
Set-MyRef -LocalBranch
Set-MyRef [-Tag] <string>
Set-MyRef [-Env] <string> -LocalBranch
Set-MyRef [-Env] <string> -Latest
Set-MyRef [-Env] <string> [-Tag] <string>
That is, exactly one of the options -Latest
, -LocalBranch
or -Tag
may be given, with an optional -Env
as first positional parameter.
Importantly, I would expect Set-MyRef 'foo'
to be parsed as -Tag 'foo'
, and Set-MyRef 'foo' 'bar'
to be parsed as -Env 'foo' -Tag 'bar'
.
I attempted to implement it using ParameterSets:
function Set-MyRef {
[CmdletBinding()]
param (
[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'EnvTag')]
[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'EnvLatest')]
[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'EnvLocal')]
[string]$Env,
[Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Tag')]
[Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'EnvTag')]
[string]$Tag,
[Parameter(Mandatory = $true, ParameterSetName = 'Latest')]
[Parameter(Mandatory = $true, ParameterSetName = 'EnvLatest')]
[switch]$Latest,
[Parameter(Mandatory = $true, ParameterSetName = 'Local')]
[Parameter(Mandatory = $true, ParameterSetName = 'EnvLocal')]
[switch]$LocalBranch
)
Write-Host "{ Env: $Env, Tag: $Tag, Latest: $Latest, LocalBranch: $LocalBranch }"
}
Which gives the correct parsed syntax:
$ Get-Command Set-MyRef -Syntax
Set-MyRef [-Env] <string> -LocalBranch [<CommonParameters>]
Set-MyRef [-Env] <string> -Latest [<CommonParameters>]
Set-MyRef [-Env] <string> [-Tag] <string> [<CommonParameters>]
Set-MyRef [-Tag] <string> [<CommonParameters>]
Set-MyRef -Latest [<CommonParameters>]
Set-MyRef -LocalBranch [<CommonParameters>]
But fails to correctly parse the parameters. When calling it using only one position parameter it uses it for both $Env
and $Tag
:
$ Set-MyRef 'foo' # incorrect - expect Env = $null, Tag = 'foo'
{ Env: foo, Tag: foo, Latest: False, LocalBranch: False }
$ Set-MyRef -Tag 'foo' # correct
{ Env: , Tag: foo, Latest: False, LocalBranch: False }
How do I change my ParameterSet specification so PowerShell can correctly pick the Set-MyRef [Tag] <string>
ParameterSet, when only one positional parameter is given?
It looks like you've run into a bug, where a single positional argument is mistakenly bound to both -Tag
and -Env
, seemingly due to both having a Position=0
parameter-attribute property, despite those properties belonging to different parameter sets.
Workarounds:
-Tag
first, before -Env
:function Set-MyRef {
[CmdletBinding(DefaultParameterSetName='Tag')]
param (
[Parameter(Position = 1, Mandatory, ParameterSetName = 'EnvTag')]
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvLatest')]
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvLocal')]
[string]$Env,
[Parameter(Position = 0, Mandatory, ParameterSetName = 'Tag')]
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvTag')]
[string]$Tag,
[Parameter(Mandatory, ParameterSetName = 'Latest')]
[Parameter(Mandatory, ParameterSetName = 'EnvLatest')]
[switch]$Latest,
[Parameter(Mandatory, ParameterSetName = 'Local')]
[Parameter(Mandatory, ParameterSetName = 'EnvLocal')]
[switch]$LocalBranch
)
Write-Host "{ Env: $Env, Tag: $Tag, Latest: $Latest, LocalBranch: $LocalBranch }"
}
function Set-MyRef {
[CmdletBinding(DefaultParameterSetName='Env')]
param (
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvTag')]
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvLatest')]
[Parameter(Position = 0, Mandatory, ParameterSetName = 'EnvLocal')]
[string]$Env,
[Parameter(Position = 0, Mandatory, ParameterSetName = 'Tag')]
[Parameter(Position = 1, Mandatory = $false, ParameterSetName = 'EnvTag')]
[string]$Tag,
[Parameter(Mandatory, ParameterSetName = 'Latest')]
[Parameter(Mandatory, ParameterSetName = 'EnvLatest')]
[switch]$Latest,
[Parameter(Mandatory, ParameterSetName = 'Local')]
[Parameter(Mandatory, ParameterSetName = 'EnvLocal')]
[switch]$LocalBranch
)
# Compensate for the broken parameter binding, by inferring
# from the fact that the -Tag argument is the same as the -Env argument
# that only *one* positional argument was passed and that it was meant to
# bind to -Tag.
if ($PSCmdlet.ParameterSetName -eq 'EnvTag' -and $Env -eq $Tag) {
$Env = $null
}
Write-Host "{ Env: $Env, Tag: $Tag, Latest: $Latest, LocalBranch: $LocalBranch }"
}
Note:
-Env
and -Target
arguments happen to have the same value. With substantially more effort, the latter case could be handled too.