Microsoft Scripting Guy Ed Wilson here. The first weekend after vacation—ah! During my one-on-one meeting with my manager this past week, Steve asked me, “How is it going?” I replied, “I need a vacation from my vacation.” That tends to be the rule with me rather than the exception. I seldom get to vacate on vacation. This year was no exception; between woodworking classes, visits with old high school buddies, surprise birthday parties for cousins, and visits with my brother, nephew, and mom, I did not get to finish Alexandre Dumas’ book, Twenty Years After. In addition, all the hustle and bustle of vacation seriously hampered my Windows PowerShell script writing.

It is the weekend, and there’s no vacation to disturb my leisure. The Scripting Wife is planning on heading over to her friend’s house for some kind of party. I did not bother to ask if I was invited for fear that I might actually be expected to make an appearance. Therefore, after a quick breakfast, with teapot in hand I made a beeline to my office.

Windows PowerShell 2.0 introduced many new cmdlets, but the changes were extended under the covers as well. One such addition is the GetNewClosure method from the ScriptBlock class. A script block in Windows PowerShell is code inside curly brackets. The If statement, foreach statement, switch statement, and functions all use script blocks. But it can be easier than that to create. Here, for example is a very simple script block.

PS C:\> {"hi"} | gm
TypeName: System.Management.Automation.ScriptBlock
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetNewClosure Method scriptblock GetNewClosure()
GetPowerShell Method powershell GetPowerShell(Params System.Object[] a...
GetSteppablePipeline Method System.Management.Automation.SteppablePipeline Ge...
GetType Method type GetType()
Invoke Method System.Collections.ObjectModel.Collection[psobjec...
InvokeReturnAsIs Method System.Object InvokeReturnAsIs(Params System.Obje...
ToString Method string ToString()
Attributes Property System.Collections.Generic.List`1[[System.Attribu...
File Property System.String File {get;}
IsFilter Property System.Boolean IsFilter {get;set;}
Module Property System.Management.Automation.PSModuleInfo Module ...
StartPosition Property System.Management.Automation.PSToken StartPositio...
PS C:\>

You will notice that when you have the curly brackets, you have an instance of a System.Management.Automation.ScriptBlock .NET Framework class, and the members of that class are available to you. One of the methods is the GetNewClosure method. But what does it actually do?

The easiest way to figure out what the GetNewClosure method does is to illustrate it with a bit of code. The first code will run without the GetNewClosure, and the second run will be with the GetNewClosure.

Get-WithoutNewClosure.ps1

function Test-Function ([int]$x)
{
Write-Debug "In Function value of `$x $x"
Return {
param([int]$y) $y + $x
write-debug "In Script Block `$x $x `$y $y"}
}
Write-Debug "Preparing to call test-function and pass value of 2"
$m2 = Test-Function 2
write-debug "The value of `$m2 after call function $m2. The value of `$x $x `$y $y"
Write-Debug "Preparing to call test-function and pass value of 5"
$m5 = Test-Function 5
Write-debug "The value of `$m5 after call function $m5. The value of `$x $x `$y $y"
Write-Debug "Invoke `&`$m2 and pass vlaue of 3"
&$m2 3 #expected: 5 actual: 3
Write-debug "After invoke `&`$m2 and pass value of 3 `$x equals $x `$y equals $y"
Write-debug "Invoke `&`$m5 and pass value of 6"
&$m5 6 #expected: 11 actual: 6
Write-debug "After invoke `&`$m5 and pass value of 6 `$x equals $x `$y equals $y"

When the script is run, the following output is displayed:

PS C:\Users\ed.NWTRADERS> C:\data\ScriptingGuys\2010\HSG_7_5_10\Get-WithoutNewClosure.ps1
DEBUG: Preparing to call test-function and pass value of 2
DEBUG: In Function value of $x 2
DEBUG: The value of $m2 after call function
param([int]$y)
$y + $x
write-debug "In Script Block `$x $x `$y $y". The value of $x $y
DEBUG: Preparing to call test-function and pass value of 5
DEBUG: In Function value of $x 5
DEBUG: The value of $m5 after call function
param([int]$y)
$y + $x
write-debug "In Script Block `$x $x `$y $y". The value of $x $y
DEBUG: Invoke &$m2 and pass vlaue of 3
3
DEBUG: In Script Block $x $y 3
DEBUG: After invoke &$m2 and pass value of 3 $x equals $y equals
DEBUG: Invoke &$m5 and pass value of 6
6
DEBUG: In Script Block $x $y 6
DEBUG: After invoke &$m5 and pass value of 6 $x equals $y equals

Before running the script, it is important to set the $DebugPreference variable to “continue” as shown here:

$DebugPreference = "continue"

What happens is the Test-Function function returns a script block. Inside the script block, the variables $x and $y do not have any value. When the $m2 and $m5 variables are invoked, the script block code that was returned the first time is executed. The value of $x is still null, but the value of $y contains the value that was passed to it. This is shown here:

DEBUG: Invoke &$m2 and pass vlaue of 3
3
DEBUG: In Script Block $x $y 3
DEBUG: After invoke &$m2 and pass value of 3 $x equals $y equals
DEBUG: Invoke &$m5 and pass value of 6
6
DEBUG: In Script Block $x $y 6
DEBUG: After invoke &$m5 and pass value of 6 $x equals $y equals

When the GetNewClosure method is added to the script block, the value that was passed the first time the Test-Function was called is retained. The Get-WithNewClosure.ps1 script is shown here.

Get-WithNewClosure.ps1

function Test-Function ([int]$x)
{
Write-Debug "In Function value of `$x $x"
Return {
param([int]$y) $y + $x
write-debug "In Script Block `$x $x `$y $y"}.GetNewClosure()
}
Write-Debug "Preparing to call test-function and pass value of 2"
$m2 = Test-Function 2
write-debug "The value of `$m2 after call function $m2. The value of `$x $x `$y $y"
Write-Debug "Preparing to call test-function and pass value of 5"
$m5 = Test-Function 5
Write-debug "The value of `$m5 after call function $m5. The value of `$x $x `$y $y"
Write-Debug "Invoke `&`$m2 and pass vlaue of 3"
&$m2 3 #expected: 5 actual: 3
Write-debug "After invoke `&`$m2 and pass value of 3 `$x equals $x `$y equals $y"
Write-debug "Invoke `&`$m5 and pass value of 6"
&$m5 6 #expected: 11 actual: 6
Write-debug "After invoke `&`$m5 and pass value of 6 `$x equals $x `$y equals $y"

The results of running the script are shown here:

PS C:\Users\ed.NWTRADERS> C:\data\ScriptingGuys\2010\HSG_7_5_10\Get-WithNewClosure.ps1
DEBUG: Preparing to call test-function and pass value of 2
DEBUG: In Function value of $x 2
DEBUG: The value of $m2 after call function
param([int]$y)
$y + $x
write-debug "In Script Block `$x $x `$y $y". The value of $x $y
DEBUG: Preparing to call test-function and pass value of 5
DEBUG: In Function value of $x 5
DEBUG: The value of $m5 after call function
param([int]$y)
$y + $x
write-debug "In Script Block `$x $x `$y $y". The value of $x $y
DEBUG: Invoke &$m2 and pass vlaue of 3
5
DEBUG: In Script Block $x 2 $y 3
DEBUG: After invoke &$m2 and pass value of 3 $x equals $y equals
DEBUG: Invoke &$m5 and pass value of 6
11
DEBUG: In Script Block $x 5 $y 6
DEBUG: After invoke &$m5 and pass value of 6 $x equals $y equals

When the GetNewClosure method is added to the script block, it causes the script block to remember the values that were passed the first time. This is shown here:

DEBUG: Invoke &$m2 and pass vlaue of 3
5
DEBUG: In Script Block $x 2 $y 3
DEBUG: After invoke &$m2 and pass value of 3 $x equals $y equals
DEBUG: Invoke &$m5 and pass value of 6
11
DEBUG: In Script Block $x 5 $y 6
DEBUG: After invoke &$m5 and pass value of 6 $x equals $y equals

The following image shows the Windows PowerShell ISE after the Get-WithNewClosure.ps1 script has run.

Image of Windows PowerShell ISE after script has run


This technique can be really useful when creating functions that contain other script blocks. For example, it could be useful when writing a function that creates another function on the fly. In addition, it can solve problems that in Windows PowerShell 1.0 necessitated polluting the global variable namespace.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys