Validation PowerShell:GetNewClosure()和带有验证的cmdlet
我试图了解.GetNewClosure()如何在PowerShell 2中的脚本cmdlet上下文中工作 本质上,我有一个函数,它返回如下对象:Validation PowerShell:GetNewClosure()和带有验证的cmdlet,validation,powershell,closures,powershell-2.0,cmdlet,Validation,Powershell,Closures,Powershell 2.0,Cmdlet,我试图了解.GetNewClosure()如何在PowerShell 2中的脚本cmdlet上下文中工作 本质上,我有一个函数,它返回如下对象: function Get-AnObject { param( [CmdletBinding()] [Parameter(....)] [String[]]$Id .. [ValidateSet('Option1','Option2')] [String[]]$Options ) ... $T
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”时出现异常:“无法添加属性,因为这将导致具有值的变量选项无效。”
可能闭包试图捕获Cmdlet调用的上下文。由于参数“Options”根本没有绑定,这与参数验证不符
我认为可以通过将验证作为代码放在Cmdlet主体中而不是使用[Validate*()]装饰器来避免这种情况——但这似乎很讨厌,也很模糊。有没有办法融合这两种思想 我相信这可能有效:
function Get-AnObject {
param(
[CmdletBinding()]
[Parameter(....)]
[String[]]$Id
..
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
...
$sb = [scriptblock]::create('$this | Get-ExpensiveStuff')
$T = New-Object PSCustomObject -Property @{ ..... }
$T | Add-Member -MemberType ScriptProperty -Name ExpensiveScriptProperty -Value $sb
.. }
这会将脚本块的创建延迟到运行时。此“无法添加属性”消息是(或曾经是)PowerShell错误,我已将其提交给Microsoft。这个特定的问题似乎已经解决了(可能是在V5.1左右)。但是任何对Powershell闭包感兴趣的人都可能会发现下面的信息很有趣
在早期版本中有一种解决方法,但首先这里有一个简化的复制案例,它会产生相同的错误:
function Test-ClosureWithValidation {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('Option1','Option2')]
[String[]]$Options
)
[scriptblock] $closure = {"OK"}.GetNewClosure();
$closure.Invoke()
}
Test-ClosureWithValidation -Options Option1
解决方法取决于以下事实:GetNewClosure()通过迭代调用脚本上下文中的局部变量,将这些局部变量绑定到脚本上下文中来解决此问题。出现此错误是因为它复制了$Options变量(包括验证属性)。您可以通过创建仅包含所需局部变量的新上下文来解决此错误。在e简单的重新编程,这是一个单线解决方案:
[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
伟大的侦察;我已经更新了错误报告的链接,指向它在windowsserver.uservoice.com的新家-不幸的是,它被错误地标记为“已完成”尽管问题在PowerShell v5.1.14393.693/PowerShell Core v6.0.0-alpha.15中仍然存在。因此,在撰写本文时,您无法对错误报告进行投票;您只能留下评论。感谢@mklement0,很高兴您发现这一点很有帮助。我在5.1.14393.693下运行了为错误报告编写的测试用例教育结果(“OK”)was输出,没有错误,这可能就是它被标记为已完成的原因。如果PowerShell Core 6对此有错误恢复,最好打开一个新问题,而不是回收旧问题。是的,您的测试用例通过了,但我希望它始终通过,因为它通过了一个有效的
-Options
值问题-当你根本没有在Windows PowerShell和PowerShell Core中传递任何-选项值时,这个bug就会出现。但是我明白你关于打开一个新问题的观点:你认为在哪里发布它更有意义:windowsserver.uservoice.com或开源GitHub项目?好吧,听起来我的bug修复程序没有c改变GetNewClosure()只捕获局部变量(而不是参数)的事实。如果是这样的话,我提到的另一个解决方法[将GetNewClosure()放入脚本块中,并将参数重新分配给局部变量]可能仍然是需要的。我想归档它的任何一个地方都可以工作,但在内部它可以作为“按设计”,所以使用解决方案可能是你最好的选择。更改它可能会破坏现有的脚本。如果我误解了,可能最好编写一个新的测试用例和stackoverflow线程。我不再为Microsoft工作,但如果你在文中@提到我,我会查看它。