PowerShell : GetNewClosure() and Cmdlets with validation

前端 未结 2 1971
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-19 21:18

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

相关标签:
2条回答
  • 2021-01-19 21:40

    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.

    0 讨论(0)
  • 2021-01-19 21:57

    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
    
    0 讨论(0)
提交回复
热议问题