Home PowerShell Internals Scope Using Closures and “Using:”
 

Using Closures and “Using:”

If you do not really know what scriptblocks are, you might enjoy reading this primer first.

Scriptblocks contain PowerShell code that does not execute until someone calls it. Sounds pretty simple, and it is:

  

$a = 'SomeValue'

$code = { "Value of a is: $a"}

& $code

  Scriptblocks contain code that has access to variables

But did you notice this: the scriptblock accessed variable $a which lives OUTSIDE the scriptblock.

So whenever you execute a scriptblock, the scriptblock will take a fresh look at the variables you use inside the scriptblock. Check this out:

  

$a = 'SomeValue'

$code = { "Value of a is: $a"}

& $code

$a = 'Different Value'

& $code

  Scriptblock code is the same but can behave differently

The result will look like this:

Value of a is: SomeValue
Value of a is: Different Value

So the code inside your scriptblock was the same in both calls, but the output was different – because the scriptblock evaluates its variables only when it is actually called.

Closures – "Freezing Scriptblocks"

Sometimes, the behavior just described isn't wanted. Maybe you want to make sure the scriptblock uses a certain set of variables. That's where "Closures" come in.

A closure is basically a scriptblock that uses the variable content at the point in time when the closure was generated. Sounds weird? It isn't. Just look at this:

  

$a = 'SomeValue'

$code = { "Value of a is: $a"}.GetNewClosure()

& $code

$a = 'Different Value'

& $code

  Creating a Closure – deep-freezes the variables

When you run this code, the result now is the same for both calls:

Value of a is: SomeValue
Value of a is: SomeValue

The scriptblock uses the variables in exactly the way how they were defined when the closure was generated.

Limits of Closures

Closures cannot be used to carry variable content over to other PowerShell threads or even out-of-process. They work only inside the current PowerShell host.

This example illustrates that the background job (running in another PowerShell session) will not receive the variable:

  

$a = 'SomeValue'
$code = { return $a }.GetNewClosure()

Start-Job -ScriptBlock $code | Wait-Job | Receive-Job 

  Closures do not work across hosts

Here is the result:

PS> $a = 'SomeValue'
$code = { return $a }.GetNewClosure()

Start–Job –ScriptBlock $code | Wait–Job | Receive–Job

PS>

That's why PowerShell 3.0 introduced "using". By declaring a variable with "using", you can easily carry it over to background jobs and remoting:

  

$a = 'SomeValue'
$code = { return $using:a }

Start-Job -ScriptBlock $code | Wait-Job | Receive-Job 

  Simply use "using" to carry variables across host boundaries

Again, here is the result:

PS> $a = 'SomeValue'
$code = { return $using:a }

Start–Job –ScriptBlock $code | Wait–Job | Receive–Job

SomeValue

PS> 

As you can see, this time the background job received the variable, and returned it back to the foreground caller. The same works for remoting, too.

Crossing Thread Boundaries

Closures cannot be used to carry variables across thread boundaries, either. Here is an example that illustrates this:

  

$a = 'SomeValue'
$code = { return $a }.GetNewClosure()

$ps = [PowerShell]::Create()
$null = $ps.AddScript($code)
$ps.Invoke()

  The background thread will return an empty value

The background thread will return $null because in its scope, there is no variable $a.

Unfortunately, with this low level code, you cannot resort to "using" like with background jobs and remoting.

What you can do, though, is remember that scriptblocks can receive parameters. And this is coincidentally also the solution for background jobs and remoting in PowerShell 2.0 (where there was no "using" yet).

So this will work:

  

$a = 'SomeValue'
$code = { param($someParameterreturn $someParameter }

$ps = [PowerShell]::Create()
$null = $ps.AddScript($code).AddArgument($a)
$ps.Invoke()

  Submtting a positional parameter

This example was handing over a positional (unnamed) parameter. You could also use parameter binding by name:

  

$a = 'SomeValue'
$code = { param($someParameterreturn $someParameter }

$ps = [PowerShell]::Create()
$null = $ps.AddScript($code).AddParameter('someParameter', $a)
$ps.Invoke()

  Submitting a named parameter