2

I have the following code (simplified, this is not my actual use case):

$actions = @()

@(1,2,3) | ForEach-Object {
    $localCopy = $_
    $actions += {Write-Output $localCopy}
}

$actions | ForEach-Object { $_.Invoke() }

It outputs:

3
3
3

However, the output I want is:

1
2
3

In something like C#, assigning the $localCopy variable would be enough to create a new, local variable that the delegate would reference when called. However, Powershell has different scoping - I am using Powershell 3.0, so the delegate gets invoked in its own scope (see this question). I tried playing with the $local: prefix on $localCopy, but that didn't work (as expected given the scoping rules).

So, if a delegate is invoked in its own scope, is this just impossible to accomplish? How can I assign a temporary variable that is available when the delegate is executed? I understand there's probably a solution to this using NewScriptBlock, but that just seems excessively complex to what I'd think would be a simple operation.

Community
  • 1
  • 1
Eric
  • 316
  • 1
  • 3
  • 10

2 Answers2

4

You need to use GetNewClosure (http://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock.getnewclosure(v=vs.85).aspx). Here is an example:

$actions = @()
@(1,2,3) | ForEach-Object {
    $localCopy = $_
    $actions += {Write-Output $localCopy}.GetNewClosure()
}
$actions | % { $_.Invoke() }
Jason Shirk
  • 7,734
  • 2
  • 24
  • 29
1

You can do something like this to evaluate the local variable immediately:

$actions = @()

@(1,2,3) | ForEach-Object {
    $localCopy = $_
    $actions += [ScriptBlock]::Create("Write-Output $localCopy")
}

$actions | ForEach-Object { $_.Invoke() }
KevinD
  • 3,023
  • 2
  • 22
  • 26
  • 1
    This can work for some simple cases, but if the variable is a non-trivial type or even a string with spaces, this won't work very well.. – Jason Shirk Mar 04 '14 at 20:26