简体   繁体   中英

PowerShell : GetNewClosure() and Cmdlets with validation

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?

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, eg:

    [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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM