It sounds like you want to use a script block to create a closure over the state of your $name
variable, meaning that the value of $name
should be locked in at the time of creating the closure with .GetNewClosure()
, without being affected by later changes to the value of the $name
variable in the caller's scope.
The problem is that PowerShell uses a dynamic module to implement the closure, and - like all modules - the only ancestral scope a dynamic module shares with an outside caller is the global scope.
In other words: the dynamic module returned by .GetNewClosure()
does not know about your Say-Hello
function, because it was created in a child scope of the global scope, which is where scripts and functions run by default.
As an aside: If you were to dot-source your script from the global scope, the problem would go away, but that is undesirable, because you would then pollute the global scope with all the variable, function, ... definitions in your script.
Selectively defining your function as function global:Say-Hello { ... }
is a "less polluting" alternative, but still suboptimal.
Solution:
Redefine the function in the context of the script block for which the closure will be created.
Here's a simplified, stand-alone example:
& { # Execute the following code in a *child* scope.
$name = 'before' # The value to lock in.
function Say-Hello { "Hello $name" } # Your function.
# Create a script block from a *string* inside of which you can redefine
# function Say-Hello in the context of the dynamic module.
$scriptBlockWithClosure =
[scriptblock]::Create("
`${function:Say-Hello} = { ${function:Say-Hello} }
Say-Hello `$name
").GetNewClosure()
$name = 'after'
# Call the script block, which still has 'before' as the value of $name
& $scriptBlockWithClosure # -> 'Hello before'
}
${function:Say-Hello}
is an instance of namespace variable notation - see this answer for general background information.
On getting an expression such as ${function:Say-Hello}
, the targeted function's body is returned, as a [scriptblock]
instance.
On assigning to ${function:Say-Hello}
, the targeted function is defined; the assignment value can either be a script block or a string containing the function's source code (without enclosing it in { ... }
)
In the above code, an expandable (double-quoted) string ("..."
), i.e. string interpolation is used to embed the stringified source code of the script block returned by ${function:Say-Hello}
in the string passed to [scriptblock]::Create()
By enclosing the ${function:Say-Hello}
reference in { ... }
, the stringified script block - which stringifies without the { ... }
enclosure - becomes a script block literal in the source code from which the script block is constructed.