如何支持多个互斥参数?

huangapple go评论50阅读模式
英文:

How to support multiple, mutually exclusive parameters?

问题

我必须编写一个脚本来获取Thing的实例。
每个Thing包含一个事件时间戳。
我需要允许用户指定一个时间戳范围。

  • 有四个(4)指定时间的参数
  • 没有一个是强制性的
  • $Since和$StartTimestamp是互斥的
  • $Until和$EndTimestamp是互斥的
  • 脚本将把$Since和$Until字符串转换为适当的[datetime]

如何使用ParameterSet禁用$Since和$StartTimestamp的使用,同时禁用$Until和$EndTimestamp的使用?

关于使用多个ParameterSet的帖子似乎随参数数量呈指数增长。这真的是正确的方法吗?

有关使用DynamicParam的帖子。我还没有看到在这种情况下使用DynamicParam是否合适。是否合适?

[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string] $ThingName

,[ValidateSet('Today', 'Yesterday', 'LastWeek')]
[string] $Since

,[datetime] $StartTimestamp

,[ValidateSet('Today', 'Now', 'Yesterday', 'LastWeek')]
[string] $Until

,[datetime] $EndTimestamp

)

英文:

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.

  • There are four (4) time-specifying parameters
  • None are mandatory
  • $Since and $StartTimestamp are mutually exclusive
  • $Until and $EndTimestamp are mutually exclusive
  • The script will translate $Since and $Until strings into an appropriate [datetime]

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
)

答案1

得分: 5

"*Mutually exclusive* parameters are indeed hard to implement using [parameter sets](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Parameter_Sets)**, as of this writing (PowerShell v7.3.4):

 * [GitHub issue #5175](https://github.com/PowerShell/PowerShell/issues/5175) is a long-standing feature request to make defining mutually exclusive parameters easier, but it has 
 * [GitHub issue #12818](https://github.com/PowerShell/PowerShell/issues/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`"

```plaintext
Resulting syntax diagram (invoke the script with `-?`):

```none
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](https://stackoverflow.com/users/7571258/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.
英文:

<!-- language-all: sh -->

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=&#39;ThingAlone&#39;)]
param (
    [Parameter(Mandatory, Position=0)]
    [string] $ThingName
    ,    
    [Parameter(Mandatory, ParameterSetName=&#39;SinceAlone&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartSinceEndUntil&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartSinceEndTimestamp&#39;)]
    [ValidateSet(&#39;Today&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39;)]
    [string] $Since
    ,
    [Parameter(Mandatory, ParameterSetName=&#39;StartTimestampAlone&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartTimestampEndUntil&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartTimestampEndTimestamp&#39;)]
    [datetime] $StartTimestamp
    ,
    [Parameter(Mandatory, ParameterSetName=&#39;UntilAlone&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartSinceEndUntil&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartTimestampEndUntil&#39;)]
    [ValidateSet(&#39;Today&#39;, &#39;Now&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39;)]
    [string] $Until
    ,
    [Parameter(Mandatory, ParameterSetName=&#39;EndTimeStampAlone&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartSinceEndTimestamp&#39;)]
    [Parameter(Mandatory, ParameterSetName=&#39;StartTimestampEndTimestamp&#39;)]
    [datetime] $EndTimestamp
)

$PSCmdlet.ParameterSetName

Resulting syntax diagram (invoke the script with -?):

YourScript.ps1 [-ThingName] &lt;string&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -Since &lt;string&gt; -EndTimestamp &lt;datetime&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -Since &lt;string&gt; -Until &lt;string&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -Since &lt;string&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -StartTimestamp &lt;datetime&gt; -EndTimestamp &lt;datetime&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -StartTimestamp &lt;datetime&gt; -Until &lt;string&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -StartTimestamp &lt;datetime&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -Until &lt;string&gt; [&lt;CommonParameters&gt;]
YourScript.ps1 [-ThingName] &lt;string&gt; -EndTimestamp &lt;datetime&gt; [&lt;CommonParameters&gt;]

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)
      &#39;Today&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39; -like &quot;$wordToComplete*&quot;
    })]
    [ValidateScript({
      if ($_ -notin &#39;Today&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39; -and $null -eq ($_ -as [datetime])) {
        throw &quot;Invalid -Since argument.&quot;
      }
      $true
    })]
    [object] $Since
    ,
    [ArgumentCompleter({
      param($cmd, $param, $wordToComplete)
      &#39;Today&#39;, &#39;Now&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39; -like &quot;$wordToComplete*&quot;
    })]
    [ValidateScript({
      if ($_ -notin &#39;Today&#39;, &#39;Now&#39;, &#39;Yesterday&#39;, &#39;LastWeek&#39; -and $null -eq ($_ -as [datetime])) {
        throw &quot;Invalid -Until argument.&quot;
      }
      $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 &quot;The -Since argument must predate the -Until argument.&quot; }

# Diagnostic output.
[pscustomobject] @{
  Since = $sinceTimestamp
  Until = $untilTimestamp
}

huangapple
  • 本文由 发表于 2023年6月11日 20:48:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76450537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定