2

I want to write a function that accepts a scriptblock as a parameter and executes that scriptblock in the scope in which it was invoked.

The Measure-Command is an example of the behavior I would like. The scriptblock runs in with the same scope as the Measure-Command itself. If the scriptblock references a variable in this scope, the script can change it.

Attached is a sample scriptblock that increments the $a variable. When invoked by Measure-Command, the variable is incremented. But when invoked by the Wrapper functions, the variable will not increment -- unless I dot-source both the invocation of the Wrapper function and the Wrapper function itself uses dot-sourcing.

function Wrapper1
{
    param( $scriptBlock )
    $startTime = Get-Date
    Write-Output ( "{0:HH:mm:ss} Start script" -f $startTime )
    & $scriptBlock
    $endTime = Get-Date
    Write-Output ( "{0:HH:mm:ss} End script - {1:c} seconds elapsed" -f $endTime, ( $endTime - $StartTime ) )
}

function Wrapper2
{
    param( $scriptBlock )
    $startTime = Get-Date
    Write-Output ( "{0:HH:mm:ss} Start script" -f $startTime )
    . $scriptBlock
    $endTime = Get-Date
    Write-Output ( "{0:HH:mm:ss} End script - {1:c} seconds elapsed" -f $endTime, ( $endTime - $StartTime ) )
}

$a = 1
Write-Output "Initial state: `$a = $a"

Measure-Command { $a++ } | Out-Null
Write-Output "Measure-Command results: `$a = $a"

Wrapper1 { $a++ }
Write-Output "Wrapper1 results: `$a = $a"

. Wrapper1 { $a++ }
Write-Output "dot-sourced Wrapper1 results: `$a = $a"

Wrapper2 { $a++ }
Write-Output "Wrapper2 results: `$a = $a"

. Wrapper2 { $a++ }
Write-Output "dot-sourced Wrapper2 results: `$a = $a"

The result of running this code is:

Initial state: $a = 1
Measure-Command results: $a = 2
13:44:49 Start script
13:44:49 End script - 00:00:00 seconds elapsed
Wrapper1 results: $a = 2
13:44:49 Start script
13:44:49 End script - 00:00:00.0157407 seconds elapsed
dot-sourced Wrapper1 results: $a = 2
13:44:49 Start script
13:44:49 End script - 00:00:00 seconds elapsed
Wrapper2 results: $a = 2
13:44:49 Start script
13:44:49 End script - 00:00:00 seconds elapsed
dot-sourced Wrapper2 results: $a = 3

Although this last option works, I'd like to avoid the dot-source syntax invoking Wrapper2. Is this possible? The Measure-Command doesn't use the dot-source syntax so it seems that it would be possible.

bretth
  • 163
  • 8

1 Answers1

2

PetSerAl, as he is wont to do, has provided the crucial pointer in a terse comment on the question:

Putting the function in a module, along with dot-sourced invocation of the script-block argument, solves the problem:

$null = New-Module {
  function Wrapper {
    param($ScriptBlock)
    . $ScriptBlock
  }
}

$a = 1
Wrapper { $a++ }

$a

The above yields 2, proving that the script block executed in the caller's scope.

For an explanation of why this works and why it is necessary, see this answer to a related question.

Note: The above approach doesn't extend to pipeline use, where you'll want to pass script blocks that expect to use automatic variable $_ to reference the object at hand (e.g.,
1, 2, 3 | Wrapper { $_ ... }; to support this use case, a workaround is needed - see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775