PowerShell : GetNewClosure() and Cmdlets with validation

落花浮王杯 提交于 2019-12-02 01:22:44

问题


I'm trying to understand how .GetNewClosure() works within the context of a script cmdlet in PowerShell 2.

In essence I have a function that returns an object like so:

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() 

..
}

Providing I do not have the validate set options the closure appears to work fine. If it is included however the new closure fails with the following error.

Exception calling "GetNewClosure" with "0" argument(s): "Attribute cannot be added because it would cause the variable Options with value to become invalid."

Presumably the closure is trying to capture the context of the call to the Cmdlet. Since the parameter "Options" is not bound at all this is not nicely with the parameter validation.

I imagine it's possible to avoid this by placing validation as code within the body of the Cmdlet instead of making use of the [Validate*()] decorators -- but this seems nasty and quite obscure. Is there a way of fusing these two ideas?


回答1:


The "Attribute cannot be added" message is (or was) a PowerShell bug, I've submitted it to Microsoft with this bug report. That particular issue seems to have been fixed, (perhaps around V5.1. but anyone interested in Powershell Closures may still find info below interesting.

There is a workaround which works in earlier versions, but first here's a simplified repro case that produces the same error:

function Test-ClosureWithValidation {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('Option1','Option2')]
        [String[]]$Options
    )
    [scriptblock] $closure = {"OK"}.GetNewClosure();
    $closure.Invoke()
}

Test-ClosureWithValidation -Options Option1

The workaround depends on the fact that GetNewClosure() works by iterating over the local variables in the calling script's context, binding these local variables into the script's context. The bug occurs because its copying the $Options variable including the validation attribute. You can work around the bug by creating a new context with only the local variables you need. In the simple repro above, it is a one-line workaround:

    [scriptblock] $closure = &{ {"OK"}.GetNewClosure();}

The line above now creates a scope with no local variables. That may be too simple for your case; If you need some values from the outer scope, you can just copy them into local variables in the new scope, e.g:

    [scriptblock] $closure = &{ 
        $options = $options; 
        {"OK $options"}.GetNewClosure();
    }

Note that the second line above creates a new $options variable, assigning it the value of the outer variable, the attributes don't propagate.

Finally, I'm not sure in your example why you need to call GetNewClosure at all. The variable $this isn't a normal local variable, it will be available in your script property whether or not you create a closure. Example:

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



回答2:


I believe this might work:

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 

.. }

That delays creation of the script block until run time.



来源:https://stackoverflow.com/questions/19775779/powershell-getnewclosure-and-cmdlets-with-validation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!