Mutually exclusive parameters are indeed hard to implement using parameter sets, as of this writing (PowerShell v7.3.4):
- GitHub issue #5175 is a long-standing feature request to make defining mutually exclusive parameters easier, but it has
- GitHub issue #12818 is a more recent and more comprehensive feature covering other aspects of parameter sets as well.
A solution with the current features is possible, but cumbersome:
Note: I'm assuming that you want to allow only the following combinations (this complements the description of what you want to prevent in your question), and this positive formulation can expressed be via parameter sets:
-ThingName
only, or combined with any of the following:
-Since
only, -Until
only
-StartTimestamp
only, -EndTimeStamp
only
-Since
combined with -EndTimeStamp
-Until
combined with -StartTimeStamp
[CmdletBinding(DefaultParameterSetName='ThingAlone')]
param (
[Parameter(Mandatory, Position=0)]
[string] $ThingName
,
[Parameter(Mandatory, ParameterSetName='SinceAlone')]
[Parameter(Mandatory, ParameterSetName='StartSinceEndUntil')]
[Parameter(Mandatory, ParameterSetName='StartSinceEndTimestamp')]
[ValidateSet('Today', 'Yesterday', 'LastWeek')]
[string] $Since
,
[Parameter(Mandatory, ParameterSetName='StartTimestampAlone')]
[Parameter(Mandatory, ParameterSetName='StartTimestampEndUntil')]
[Parameter(Mandatory, ParameterSetName='StartTimestampEndTimestamp')]
[datetime] $StartTimestamp
,
[Parameter(Mandatory, ParameterSetName='UntilAlone')]
[Parameter(Mandatory, ParameterSetName='StartSinceEndUntil')]
[Parameter(Mandatory, ParameterSetName='StartTimestampEndUntil')]
[ValidateSet('Today', 'Now', 'Yesterday', 'LastWeek')]
[string] $Until
,
[Parameter(Mandatory, ParameterSetName='EndTimeStampAlone')]
[Parameter(Mandatory, ParameterSetName='StartSinceEndTimestamp')]
[Parameter(Mandatory, ParameterSetName='StartTimestampEndTimestamp')]
[datetime] $EndTimestamp
)
$PSCmdlet.ParameterSetName
Resulting syntax diagram (invoke the script with -?
):
YourScript.ps1 [-ThingName] <string> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -Since <string> -EndTimestamp <datetime> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -Since <string> -Until <string> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -Since <string> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -StartTimestamp <datetime> -EndTimestamp <datetime> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -StartTimestamp <datetime> -Until <string> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -StartTimestamp <datetime> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -Until <string> [<CommonParameters>]
YourScript.ps1 [-ThingName] <string> -EndTimestamp <datetime> [<CommonParameters>]
Taking a step back:
As zett42 points out, you can bypass the need for mutual exclusion if you provide only a single, polymorphous parameter for the start and end timestamp, respectively.
To that end, declare these parameters as [object]
(so they can accept a value of any type), and:
use a [ValidateScript()]
attribute to ensure that a value passed by the user can either be parsed as a [datetime]
instance or is one of the predefined symbolic names, such as Today
.
In order to also support tab-completion, use an [ArgumentCompleter()]
attribute that completes the symbolic names.
Note: The arrays of symbolic names are duplicated in the two attributes below:
- In a stand-alone script that cannot be avoided, but in a function (e.g. as part of a module) you could define the arrays only once.
[CmdletBinding()]
param (
[Parameter(Mandatory, Position=0)]
[string] $ThingName
,
[ArgumentCompleter({
param($cmd, $param, $wordToComplete)
'Today', 'Yesterday', 'LastWeek' -like "$wordToComplete*"
})]
[ValidateScript({
if ($_ -notin 'Today', 'Yesterday', 'LastWeek' -and $null -eq ($_ -as [datetime])) {
throw "Invalid -Since argument."
}
$true
})]
[object] $Since
,
[ArgumentCompleter({
param($cmd, $param, $wordToComplete)
'Today', 'Now', 'Yesterday', 'LastWeek' -like "$wordToComplete*"
})]
[ValidateScript({
if ($_ -notin 'Today', 'Now', 'Yesterday', 'LastWeek' -and $null -eq ($_ -as [datetime])) {
throw "Invalid -Until argument."
}
$true
})]
[object] $Until
)
# Translate the -Since and -Until arguments into [datetime] instances.
$i = 0
$sinceTimestamp, $untilTimestamp =
$Since, $Until | ForEach-Object {
switch ($_) {
$null { if ($i -eq 0) { Get-Date -Date 0 } else { Get-Date }; break }
Now { Get-Date; break }
Today { (Get-Date).Date; break }
Yesterday { (Get-Date).Date.AddDays(-1); break }
LastWeek { (Get-Date).Date.AddDays(-7); break }
Default { $_ -as [datetime] }
}
++$i
}
if ($untilTimestamp -lt $sinceTimestamp) { Throw "The -Since argument must predate the -Until argument." }
# Diagnostic output.
[pscustomobject] @{
Since = $sinceTimestamp
Until = $untilTimestamp
}