powershellscopescriptblock

access parent scriptblock scope in powershell


As part of learning its basics i am implementing a ternary operator cmdlet in pws. I have it taking scriptblocks, to emulate the conditional evaluation ternary operators usually have. And in most instances it works fine.

function valOf($var){
    if($var -is [scriptblock]){
        return & $var   }
    else {
        return $var    }
}

function ternary([bool]$condition, $t, $f){
    if($condition){
        return valOf($t)    }
    else{
        return valOf($f)    }
    #return @($t,$f)[!$condition]
}

I got in trouble when i started nesting scriptblocks:

$i=56;
&{
   $i=0
   ternary($true) {$script:i+=2} {write-host "onFalse"}
   $i #wanted:2 #reality: 58
   <# without '$script: $i' is indeed 0, but cannot be edited #>
}      
$i #wanted:56 #reality:58

How can i access the middle scope?

browsing the documentation as well as the forum this seems to be quite a common issue, but the theme is anything but clear x.x
Perhaps an invokeCommand that optsOut from the copyOnWrite behaviour..?


Solution

  • An alternative to Santiago's helpful answer that makes it unnecessary to use special syntax in the script-block arguments passed to your ternary function:

    You can combine dynamic modules with dot-sourcing:

    Note: For brevity:

    # Create (and implicitly import) a dynamic module that
    # hosts the ternary function.
    $null = New-Module {
      # Define the ternary function.
      function ternary {
        param(
          [bool] $Condition,
          [scriptblock] $trueBlock,
          [scriptblock] $falseBlock
        )
        # Dot-source the appropriate script block,
        # which runs directly in the *caller's* scope,
        # given that's where it was created and given that the
        # module's code runs in a separate scope domain ("session state")
        if ($Condition) { . $trueBlock } else { . $falseBlock }
      }
    }
    
    $i=56;
    & {
       $i=0
       # Now you can use $i as-is
       # in order to refer to the current scope's $i.
       ternary $true { $i+=2 } {write-host "onFalse"}
       $i # -> 2 
    }
    $i # -> 56
    

    Note: