如何正确验证 powershell 中的参数?

How to correctly validate parameter in powershell?

团队!

我的高级函数中有 PSObject[] 类型的变量。

[Parameter( Mandatory = $false, Position = 0, HelpMessage = "PsObject data." )]
[PSobject[]] $data,
...

但有时我输入的类型为 [string[]] 的 $data 正在转换为 [PSObject[]],我在使用对象 属性.

时发现错误

我正在尝试通过脚本验证它

[Parameter( Mandatory = $false, Position = 0, HelpMessage = "PsObject data." )]
[ValidateScript({ ( ( $_ -is [PSobject] ) -or ( $_ -is [PSobject[]] )  -or ( $_ -is [System.Object[]] ) ) })]
 $data,

但它没有效果,我看到类型为 [string[]] 的 $data,我继续寻找错误。

怎么了?

编辑:根据评论,听起来您真正的问题是:

How can I validate that I'm able to attach new properties to the input objects with Add-Member?

为此,您需要排除两种输入值:

  • 值类型的对象(数值类型,[datetime],任何在 .NET 中真正按值传递的对象)
  • 字符串

(作为 ,可以将属性添加到这些类型的本地副本 - 但 PowerShell 在管道中的相邻命令之间传递值时无法预测地“复活”它们,以及其他怪癖)

您可以验证输入对象不属于这些桶之一,如下所示:

[ValidateScript({$null -ne $_ -and $_.GetType().IsValueType -and $_ -isnot [string]})]
[psobject[]]$InputObject

PSObject 是 PowerShell 在内部使用的通用包装类型,用于跟踪扩展属性和附加到现有对象的成员。

因此,任何对象 都可以隐式转换为 PSObject - 事实上,每次对象从一个命令传递到另一个命令时,PowerShell 都会这样做| 在管道语句中 - 它在强制执行特定输入对象特征方面没有实际效果。

如果要确保对象具有特定的属性,最好的选择是使用 class 关键字定义特定的数据类型:

class MyParameterType 
{
  [string]$Name
  [int]$Value
}

function Test-MyParameterType
{
  param(
    [MyParameterType[]]$InputObject
  )

  $InputObject |ForEach-Object {
    $_.GetType() # this will output `[MyParameterType]`
    $_.Name # now you can be sure this property exists
  }
}

您现在可以将已声明类型的实例传递给函数参数:

$mpt = [MyParameterType]::new()
$mpt.Name = 'Name goes here'

Test-MyParameterType -InputObject $mpt

但如果自定义对象具有匹配的属性,PowerShell 也可以将它们隐式转换为所需的目标类型:

$arg = [pscustomobject]@{
  Name = 'A name'
  Value = Get-Random
}

# This will return [PSCustomObject]
$arg.GetType() 

# But once we reach `$_.GetType()` inside the function, it will have been converted to a proper [MyParameterType]
Test-MyParameterType -InputObject $arg 

如果您想验证特定属性的存在以及可能的它们的值 而无需显式键入,您必须访问对象的隐藏 psobject 成员集验证脚本 - 请注意,它将一次验证一项:

function Test-RequiredProperty
{
  param(
    [ValidateScript({ $_ -is [PSObject] -and ($prop = $_.psobject.Properties['RequiredProperty']) -and $null -ne $prop.Value })]
    [PSObject[]]$InputObject
  )
}

现在,如果我们传递一个具有某些值的 RequiredProperty 属性 的对象,则验证成功:

$arg = [pscustomobject]@{
  RequiredProperty = "Some value"
}

# This will succeed
Test-RequiredProperty -InputObject $arg

# This will fail because the property value is $null
$arg.RequiredProperty = $null
Test-RequiredProperty -InputObject $arg

# This will fail because the property doesn't exist
$arg = [pscustomobject]@{ ADifferentPropertyName = "Some value" }
Test-RequiredProperty -InputObject $arg

补充Mathias R. Jessen's helpful answer

虽然不建议修饰.NET value types and strings with ETS (Extended Type System) properties via Add-Member的实例,can do it,使用下面的成语。

# Works with any non-$null object.
# Note the use of -PassThru
# Add -Force to override an existing property.
$decoratedObject = $object | Add-Member -PassThru foo bar 

注意:Add-Member调用是Add-Member -PassThru -NotePropertyName foo -NotePropertyValue bar的缩写;也就是说,添加了一个名为 .foo 且值为 'bar' 的 属性。

-PassThru 只有在 $object 字符串 (类型 [string])时才严格需要。

演示:

$decoratedNumber = 42 | Add-Member -PassThru foo bar1 
$decoratedString = 'baz' | Add-Member -PassThru foo bar2
# Access the .foo property on each:
($decoratedNumber, $decoratedString).foo # -> 'bar1', 'bar2'

为什么不建议这样做:

装饰属性:

  • 在将装饰值类型实例传递或强制转换为相同类型的参数或修改变量值时被丢弃:

    PS> ++$decoratedNumber; $decoratedNumber.foo + '!'
    !  # !! Property was discarded. 
    
    PS> & { param([int] $num) $num.foo + '!' } $decoratedNumber
    !  # !! Property was discarded. 
    
    # Only *untyped* parameters preserve the decorations:
    PS> & { param($num) $num.foo + '!' } $decoratedNumber
    bar1!
    
  • 可以意外地出现在序列化上下文中;例如:

    PS> $decoratedNumber | ConvertTo-Json -Compress
    {"value":42,"foo":"bar1"}
    
    # Casting to the original type helps:
    PS> [int] $decoratedNumber | ConvertTo-Json -Compress
    42
    

此外,对于 [int] 个实例,0100 之间的值由 PowerShell 在内部 缓存 (出于性能原因),所以装饰可能会意外地出现在后来的无关变量中:

 PS> $decorated = 42 | Add-Member -PassThru foo bar1; $other = 42; $other.foo
 bar1 # !! Unrelated use of 42 is decorated too.

有关详细信息,请参阅