The docs for ScriptBlock.GetNewClosure() state the following:
Returns a new scriptblock bound to a module. Any local variables in the callers context will be copied into the module.
I have been assuming that repeat invocations of GetNewClosure() would result in scriptblocks bound to different modules. However, this does not seem to be the case. The following code
$m = New-Module {}
foreach ($sb in @(
{ $v = 'normal assignment' }
{ Set-Variable v 'Set-Variable' }
{ Set-Variable v 'Set-Variable -Scope 1' -Scope 1 }
))
{
. ( $sb | . $m { process { $_.GetNewClosure() } } ) # set value
. ( {$v} | . $m { process { $_.GetNewClosure() } } ) # output value
}
outputs
Set-Variable -Scope 1
In other words, a variable set by $sb.GetNewClosure() is visible from {$v}.GetNewClosure(). The modules bound to each scriptblock seem to be somehow related, but not identical.
I'm hoping someone can shed some light on the nature of the module to which each of the scriptblocks output by repeat calls to GetNewClosure() is bound and how those modules are related to one another.
Written as of :
PowerShell Core 6.1.3 release
@alx9r When you're dotsourcing a module bound scriptblock, the current scope will be the ModuleScope (same as psm1) while it's being invoked. So "Scope 1" there is global.
Also, @alx9r, there's only _one_ module instance in your code, (where, as @SeeminglyScience points out, $m, and all your . invocations happen in its top-level scope-Scope 1 refers to the _global_ scope).
So "Scope 1" there is global.
Oh I see. Thanks @SeeminglyScience.
(For posterity, that link to "global" is more nuanced than I remembered: See "One last detail..." in #6139.)
there's only one module instance in your code, $m, and all your . invocations happen in its top-level scope
I count 7 module instances: $m plus the 6 produced by the 6 invocations of .GetNewClosure(). @mklement0 are you saying that those are all actually the same module?
Invoking
$m = New-Module {}
$m.Name
foreach ($sb in @(
{ $v = 'normal assignment' }
{ Set-Variable v 'Set-Variable' }
{ Set-Variable v 'Set-Variable -Scope 1' -Scope 1 }
))
{
$sb | . $m { process { $_.GetNewClosure() } } | % {$_.Module.Name}
{$v} | . $m { process { $_.GetNewClosure() } } | % {$_.Module.Name}
}
shows 7 different module names on my computer.
Thanks, @alx9r - clearly I had a misconception there.
I missed that .GetNewClosure() _creates_ a new module, correct?
I missed that .GetNewClosure() creates a new module, correct?
@mklement0 That's my current understanding of what happens.
As for the link to the global scope: it is possible - though I've never seen it in the wild - to create a module that _doesn't_ have that link - or, rather, it _does and it doesn't_. A bug?
@PetSerAl pointed me to this PSModuleInfo constructor that, when passed $false, purports to create a module that opts out of the link:
# WITH link to global scope.
PS> $global:var = 42; . ([psmoduleinfo]::new($true)) { $global:var }
42
# WITHOUT link to global scope.
PS> $global:var = 42; . ([psmoduleinfo]::new($false)) { $global:var }
# NO OUTPUT. Get-Variable -Scope Global var would fail.
However, _without a scope qualifier_ the global variable _is_ seen:
# WITHOUT link to global scope, but REFERENCE WITHOUT SCOPE QUALIFIER
PS> $global:var = 42; . ([psmoduleinfo]::new($false)) { $var }
42 # !! Unexpectedly still seen; ditto with `Get-Variable var` (without -Scope)
@mklement0 It seems like that might be a bug. It reminds me of the inconsistent behavior in #6378. That turned out to be a bug.
Thanks, @alx9r - I've created #9080.
Most helpful comment
@mklement0 It seems like that might be a bug. It reminds me of the inconsistent behavior in #6378. That turned out to be a bug.