Powershell 枚举参数不会阻止传递多个参数

Powershell enum parameter does not prevent multiple parameters being passed

提问人:Lieven Keersmaekers 提问时间:4/15/2023 最后编辑:Lieven Keersmaekers 更新时间:4/20/2023 访问量:104

问:

我假设枚举参数默认情况下只接受一个参数,并且在尝试传递两个或多个参数时会抛出异常。

enum Test {one; two; three}

function Test-Enum {[CmdletBinding()] param (
[Test]$number
) $number}
这按预期工作
Test-Enum -number one
Test-Enum -number two
Test-Enum -number three
我本来以为会有一个异常,试图传递两个参数
Test-Enum -number one, two     # outputs two
Test-Enum -number two, one     # outputs two
Test-Enum -number one, three   # outputs three
test-Enum -number three, one   # outputs three
这些确实会触发异常
Test-Enum -number two, three   # Cannot convert value error
Test-Enum -number three, two   # Cannot convert value error

我在这里遗漏了一些明显的东西,但我无法弄清楚。如何防止使用多个参数调用 Test-Enum?

PS F:\> $PSVersionTable

Name                           Value                                                            
----                           -----                                                            
PSVersion                      5.1.19041.2673                                                   
PSEdition                      Desktop                                                          
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                          
BuildVersion                   10.0.19041.2673                                                  
CLRVersion                     4.0.30319.42000                                                  
WSManStackVersion              3.0                                                              
PSRemotingProtocolVersion      2.3                                                              
SerializationVersion           1.1.0.1                         
PowerShell 枚举 参数传递

评论


答:

2赞 mklement0 4/15/2023 #1

TL的;博士

  • 至少在 PowerShell 7.3.4 (撰写本文时是最新的) 中,PowerShell 的参数绑定器将具有 [Flags] 属性的 [枚举] 派生类型(这使它们成为可以组合其值的位字段)视为具有 [Flags] 属性的类型。

  • 这使得它总是接受多个值,这不适用于枚举类型。[Flags]


遗憾的是,PowerShell 参数绑定器允许将多个值传递给类型化参数,即使特定类型不是 [Flags] 修饰的类型(在这种情况下,接受多个值很有帮助且合适)。[enum]

如果给定多个值,则这些值是位或值,如果生成的数字与定义的值不对应,则会发生错误。


您可以从字符串强制转换以获得等效的行为,以了解参数绑定器的作用(枚举值的数值隐式为 、 for 和 for):0one1two2three

# -> Same as: [Test] (0 -bor 1) -> two
[Test] 'one, two'  

# -> Same as [Test] (1 -bor 2), which FAILS, because 1 -bor 2 is 3, 
#    and 3 isn't a defined value.
[Test] 'two, three'  

虽然此行为适用于基于枚举的类型,但不适用于那些不适用的枚举类型。[Flags]


Windows PowerShell 的解决方法

解决方法很麻烦:

  • 将参数键入为 ,这将自动阻止传递多个值。[string]

  • 使用属性验证传递的字符串是否是枚举中值的名称。[ValidateScript({ … })][Test]

  • 为了也支持 Tab 自动补全,请添加一个属性:[ArgumentCompleter()]

enum Test { one; two; three }

function Test-Enum {
  [CmdletBinding()] param (
    [ValidateScript({ 
        if ($_ -as [Test] -ne $null) { return $true } 
        throw "$_ is not a valid value. Try one of: $([enum]::GetNames([Test]))" 
      })]
    [ArgumentCompleter({ 
        param($unused1, $unused2, $wordToComplete) 
        [enum]::GetNames([Test]) -like "$wordToComplete*" 
      })]
    [string] $number
  ) 
  # Now it is safe to convert the [string] value to [Test]
  $enumVal = [Test] $number
  $enumVal
}

备选方案

  • 如果非必须使用 -派生类型,则可以使用 [ValidateSet()] 属性并将可接受的值枚举为其中的字符串
    但是,在以后使用参数值时,这并不能为您提供类型安全性。
    [enum]

PowerShell (Core) 7+ 的解决方法

使用实现 System.Management.Automation.IValidateSetValuesGenerator 接口接口的辅助,该接口也可以与该接口一起使用,并且可以在其中用于生成有效名称:[ValidateSet()][enum]::GetNames([Test])

# PS v7+ only

enum Test {one; two; three}

# Helper class that implements IValidateSetValuesGenerator
# and returns the valid enumeration values, to be used with [ValidateSet] below.
class ValidTestEnumNames : System.Management.Automation.IValidateSetValuesGenerator { 
  [string[]] GetValidValues() { 
    return [enum]::GetNames([Test])
  }
}

function Test-Enum {
  [CmdletBinding()]
   param (
    [ValidateSet([ValidTestEnumNames])] # Use the class defined above.
    [string] $number
  ) 
  $enumVal = [Test] $number
  $enumVal
}