【问题标题】:How to correctly validate parameter in powershell?如何正确验证powershell中的参数?
【发布时间】:2021-11-28 08:41:27
【问题描述】:

团队!

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

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

但有时我的输入 $data 类型为 [string[]] 正在转换为 [PSObject[]] 并且我在使用对象属性时遇到错误。

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

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

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

怎么了?

【问题讨论】:

  • PowerShell 中的每个对象都(透明地)包裹在PSObject 中,因此转换为PSObject 没有实际效果。你希望执行什么?如果输入对象需要具有特定属性,您可能需要定义自定义 class

标签: powershell properties add-member


【解决方案1】:

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

如何验证我是否能够使用Add-Member 将新属性附加到输入对象?

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

  • 值类型的对象(数值类型,[datetime]'s,任何在 .NET 中通过值传递的东西)
  • 字符串

(作为mklement0's excellent answer shows,可以将属性添加到这些类型的本地副本中 - 但当在管道中的相邻命令之间传递值时,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

【讨论】:

  • @Alex 对不起,我一定误解了你的问题。您说“我在使用对象属性时发现错误。” - 但您毕竟不想测试输入对象是否具有该特定属性?
  • 谢谢您的完整回答!我的情况是我只想知道基本类型。不测试属性类型。
  • 目前我通过验证我的代码中的类型来做到这一点powershell if ( ( $Data -is [PSobject] ) -or ( $Data -is [PSobject[]] ) -or ( $Data -is [System.Object[]] ) ){ $ValidDataType = $true } Else { Add-ToLog -Message "Data should be of type [PSObject[]]! But type is [$( $data.gettype() )]. " -Display -Status "error" $ValidDataType = $false }
  • 我想用 ValidateScript 做同样的事情。
  • 但是为什么?如果不能确保它具有您期望使用的正确成员/属性,那么验证类型的意义何在? :)
【解决方案2】:

补充Mathias R. Jessen's helpful answer

虽然不建议使用 ETS (Extended Type System) 属性装饰 .NET value types字符串 的实例 通过Add-Member可以做到,使用以下成语。

# 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.

有关详细信息,请参阅this answer

【讨论】:

    猜你喜欢
    • 2013-09-16
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-21
    • 1970-01-01
    • 1970-01-01
    • 2021-01-16
    相关资源
    最近更新 更多