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

Posted

技术标签:

【中文标题】如何正确验证powershell中的参数?【英文标题】:How to correctly validate parameter in 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 【参考方案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。

【讨论】:

以上是关于如何正确验证powershell中的参数?的主要内容,如果未能解决你的问题,请参考以下文章

Python、Powershell 和 Popen

Python的pow函数

Windows Powershell 中的 Flutter Doctor 返回错误

PowerShell:使用参数验证而不抛出异常

如何修复此“Set-SecureBootUEFI:不正确的身份验证数据:0xC0000022”错误?

如何从我的 Powershell 脚本中的 Get-ADUser 过滤器获取结果,以便验证该用户是不是存在?