我试图了解.GetNewClosure()在PowerShell 2中的脚本cmdlet上下文中如何工作。
本质上,我有一个返回像这样的对象的函数:
function Get-AnObject {
param(
[CmdletBinding()]
[Parameter(....)]
[String[]]$Id
..
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
...
$T = New-Object PSCustomObject -Property @{ ..... }
$T | Add-Member -MemberType ScriptProperty -Name ExpensiveScriptProperty -Value {
$this | Get-ExpensiveStuff
}.GetNewClosure()
..
}
如果我没有验证集选项,则关闭似乎可以正常工作。如果包含它,则新的关闭失败并显示以下错误。
“使用” 0“参数调用” GetNewClosure“的异常:”无法添加属性,因为这将导致带有值的变量Options变为无效。“
大概闭包正在尝试捕获对Cmdlet的调用的上下文。由于参数“Options”根本没有绑定(bind),因此在参数验证中效果不佳。
我想可以通过将验证作为代码放置在Cmdlet的正文中而不是使用[Validate *()]装饰器来避免这种情况,但这似乎很讨厌并且晦涩难懂。有没有办法融合这两个想法?
最佳答案
“无法添加属性”消息是(或曾经)是一个PowerShell错误,我已使用this bug report将其提交给Microsoft。该特定问题似乎已得到解决,(也许在V5.1左右。但是对Powershell封闭感兴趣的任何人仍然可以在下面找到有趣的信息。
有一种解决方法可以在早期版本中使用,但是首先这是一个简化的repro案例,该案例会产生相同的错误:
function Test-ClosureWithValidation {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
[scriptblock] $closure = {"OK"}.GetNewClosure();
$closure.Invoke()
}
Test-ClosureWithValidation -Options Option1
解决方法取决于以下事实:GetNewClosure()通过在调用脚本的上下文中迭代局部变量并将这些局部变量绑定(bind)到脚本的上下文中而起作用。发生该错误是因为其复制了包含验证属性的$ Options变量。您可以通过仅使用所需的局部变量创建新的上下文来解决该错误。在上面的简单复制中,这是一种单行解决方法:
[scriptblock] $closure = &{ {"OK"}.GetNewClosure();}
现在,上面的行创建了一个没有局部变量的范围。对于您的情况,这可能太简单了;如果您需要外部作用域中的某些值,则可以将它们复制到新作用域中的局部变量中,例如:
[scriptblock] $closure = &{
$options = $options;
{"OK $options"}.GetNewClosure();
}
请注意,上面的第二行创建了一个新的$ options变量,为其分配了外部变量的值,这些属性不会传播。
最后,我不确定您的示例中为什么根本需要调用GetNewClosure。变量$ this不是普通的局部变量,无论您是否创建闭包,该变量都将在您的脚本属性中可用。例:
function Test-ScriptPropertyWithoutClosure {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
[pscustomobject]@{ Timestamp= Get-Date} |
Add-Member ScriptProperty ExpensiveScriptProperty {
$this | get-member -MemberType Properties| % Name
} -PassThru
}
Test-ScriptPropertyWithoutClosure -Options Option1 | fl