3
& { 
    $action = & { $y = 100
        return { write-host "Value: $y" }.getnewclosure()
    }

    [void] (register-engineevent -sourcei "foo" -action $action)
    [void] (new-event -sourcei "foo")
}

The code above prints Value:, while I'd expect it to print Value: 100. Is something missing here?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
guillermooo
  • 7,915
  • 15
  • 55
  • 58

1 Answers1

3

The engine is calling GetNewClosure() on it, which is a better thing for it to do than not.

When I ran the above, I got an output of only Value:, but then I globally declared $y = 25 and ran [void](new-event -sourcei 'foo') again, the output was Value: 25. Then I changed its value again, $y = 38, and ran the event again: Value: 38.

If what you want is to bake in the value of $y at the time you create the action, one way to do that is to create the scriptblock in such a way that the value of $y becomes a literal or part of a literal:

$action = &{ $y = 100
    return (invoke-expression "{write-host 'Value: $y'}")
}

This first parses the string to insert the value, so Invoke-Expression ends up doing the equivalent of this:

{write-host 'Value: 100'}

I'm not sure if there are any other ways to bake in the value other than composing the entire content of the scriptblock in a string and passing it through Invoke-Expression.


In response to your comment, further explanation of closures:

> $action100 = &{$y = 100; return {write-host "Value: $y"}.GetNewClosure()}
> &$action100
Value: 100

That's the result you expect, and that's what you get, because the scriptblock is still "closed" around the value of $y in the scope where GetNewClosure() was called.

> $y = 25
> &$action100
Value: 100

Still closed around the $y that is 100.

> $action25 = $action100.GetNewClosure()
> &$action25
Value: 25

This produces a new scriptblock that encloses variables in the current scope. It makes it re-evaluate what $y is in that scriptblock, and in this context, $y is now 25.

> $y = 38
> &$action100
Value: 100

> &$action25
Value: 25

> &($action100.GetNewClosure())
Value: 38

At this point, because $y is declared globally now, when you call New-Event it will use GetNewClosure() and re-evaluate $y to 38, and print Value: 38. You've been getting Value: because in the context where the engine events call GetNewClosure() the variable $y is not defined, so "Value: $y" becomes "Value: ".

Emperor XLII
  • 13,014
  • 11
  • 65
  • 75
Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
  • If you take the `$action` part above alone and run in in the console, you do get the expected result. That's what's confusing me. I don't understand why it should be different to calling `Register-EngineEvent`. – guillermooo Aug 02 '11 at 15:55
  • 1
    [This Q&A](http://stackoverflow.com/q/4058721/22211) also goes over some simple examples of how `GetNewClosure()` changes things. – Joel B Fant Aug 02 '11 at 17:24
  • That makes perfect sense, thank you. And the linked post helped me understand why wrapping the `-Action` in additional braces won't help. – guillermooo Aug 02 '11 at 18:10