4

我试图了解 .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”的异常:“无法添加属性,因为它会导致具有值的变量选项变得无效。”

据推测,闭包正试图捕获对 Cmdlet 的调用的上下文。由于参数“Options”根本没有绑定,这与参数验证不太好。

我想可以通过将验证作为代码放置在 Cmdlet 的主体中来避免这种情况,而不是使用 [Validate*()] 装饰器——但这看起来很讨厌而且很模糊。有没有办法融合这两个想法?

4

2 回答 2

5

“无法添加属性”消息是(或曾经是)一个 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变量。您可以通过仅使用所需的局部变量创建新上下文来解决该错误。在上面的简单复制中,它是一种单行解决方法:

    [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
于 2014-07-15T18:58:24.810 回答
1

我相信这可能有效:

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 

.. }

这会将脚本块的创建延迟到运行时。

于 2013-11-05T00:29:12.847 回答