Reputation: 16226
I must write a script to get instances of Thing. Each Thing contains an event timestamp. I need to allow the user to specify a timestamp range.
How can I use a ParameterSet to disable the use of both $Since and $StartTimestamp AND disable the use of both $Until and $EndTimestamp?
Posts about using multiple ParameterSets appear to grow exponentially with the number of parameters. Is this really the way?
There are posts about using a DynamicParam. I do not yet see where a DynamicParam would be appropriate for this. Is it?
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string] $ThingName
,[ValidateSet('Today', 'Yesterday', 'LastWeek')]
[string] $Since
,[datetime] $StartTimestamp
,[ValidateSet('Today', 'Now', 'Yesterday', 'LastWeek')]
[string] $Until
,[datetime] $EndTimestamp
)
Upvotes: 3
Views: 242
Reputation: 437062
Mutually exclusive parameters are indeed hard to implement using parameter sets, as of this writing (PowerShell v7.3.4):
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:
[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
}
Upvotes: 6