Reputation: 4273
A script block is bound to a SessionState immediately if you use the { ... } syntax, or upon the first invocation if the script block was created some other way, e.g. [ScriptBlock]::Create(). The binding is to the active SessionState.
Indeed that seems to be correct. This thread contemplates that behavior extensively. For reference, here is an annotated example exercising the interactions between PowerShell scopes, .
and &
, modules, and SessionState
s.
Consider the following code in which two instances of identical scriptblocks produced by [scriptblock]::Create()
are invoked using the call operator &
and SteppablePipeline
, respectively:
function Invoke-Call {
[CmdletBinding()]
param(
[scriptblock]
$ScriptBlock,
$a
)
& ([scriptblock]::Create({ param ($sb,$__a) . $sb $__a })) <# This scriptblock gets bound to the SessionState that is active when the call operator is invoked. #> `
$ScriptBlock $a
}
function Invoke-Steppable {
[CmdletBinding()]
param(
[scriptblock]
$ScriptBlock,
$a
)
$pipe = [scriptblock]::Create({ param ($sb,$__a) . $sb $__a }). # To what SessionState does this scriptblock get bound?
GetSteppablePipeline(
$MyInvocation.CommandOrigin,
@($ScriptBlock,$a))
$pipe.Begin($PSCmdlet)
$pipe.End()
}
Invoke-Call -a 'c' -ScriptBlock { param($x) [pscustomobject]@{function = 'Invoke-Call'; x=$x; a=$a; __a=$__a }}
Invoke-Steppable -a 's' -ScriptBlock { param($x) [pscustomobject]@{function = 'Invoke-Steppable'; x=$x; a=$a; __a=$__a }}
That code outputs
function x a __a
-------- - - ---
Invoke-Call c c c
Invoke-Steppable s s
The following, I think, is noteworthy about that output:
$a
is accessible both ways -ScriptBlock
is invoked. That is because both the functions and -ScriptBlock
are bound to the same SessionState
. Both functions have parameter $a
in the function's scope. That scope becomes an ancestor scope to -ScriptBlock
when it is invoked so $a
is visible from -ScriptBlock
.$__a
is accessible when -ScriptBlock
is invoked by Invoke-Call
. This is a similar situation to (1). When the scriptblock from [scriptblock]::Create()
is invoked it is bound to the SessionState
of Invoke-Call
which is the same SessionState
as -ScriptBlock
. So $a
is visible from -ScriptBlock
.$__a
is not accessible when -ScriptBlock
is invoked by Invoke-Steppable
.This suggests the script block from [scriptblock]::Create()
that is invoked by SteppablePipeline
is not bound to the same SessionState
as -ScriptBlock
.
To what session state does that script block get bound?
Upvotes: 1
Views: 51
Reputation: 4273
None of the testing described below suggests anything unusual about the binding of the ScriptBlock
in [scriptblock]::Create().GetSteppablePipeline()
: It seems to behave as though it were bound to the same SessionState
as the command whose whose reference is passed to the SteppablePipeline
methods. In fact, Set-Variable
works exactly as expected for local variables that aren't in param()
.
Script block binding doesn't seem to explain why param()
variables aren't visible to descendant scopes.
ScriptBlock.SessionStateInternal
Consider the script binding.ps1
in this reference code which creates and invokes script block $sb
using the methods
# call
& $sb
# steppable
. {
[CmdletBinding()]param()
$pipe = $sb.GetSteppablePipeline($MyInvocation.CommandOrigin,@())
$pipe.Begin($PSCmdlet)
$pipe.End()
}
and extracts SessionStateInternal
from each script block before and after invokation using the function getSessionState
. That script produces the following results:
method provenance same before after
------ ---------- ---- ------ -----
call {} True yes yes
call [scriptblock]::Create()
call {}.Ast.GetScriptBlock()
steppable {} True yes yes
steppable [scriptblock]::Create()
steppable {}.Ast.GetScriptBlock()
same
in the table represents a reference-equality check of the before
and after
SessionStateInternal
.
So SteppablePipeline
does not seem to alter SessionStateInternal
of the script block from which it is derived.
SessionStateInternal
of Nested ScriptBlock
By the same the principle as the previous section the script block
([scriptblock]::Create({ . { getSessionState {} }}))
invoked using SteppablePipeline
reveals SessionStateInternal
for the nested {}
. The script steppableSessionState.ps1
in the reference code, does that and compares that SessionStateInternal
instance with others that are easily obtained. The instance of SessionStateInternal
from the above line invoked with SteppablePipeline
matches others according to the following table:
Other SessionStateInternal |
Matches |
---|---|
{ . { getSessionState {} }} invoked with SteppablePipeline |
yes |
{} defined locally |
yes |
{} defined in a module |
no |
So far none of these results suggest anything unusual about the binding of the ScriptBlock
in [scriptblock]::Create().GetSteppablePipeline()
: It seems to behave as though it were bound to the same SessionState
as the command whose whose reference is passed to the SteppablePipeline
methods.
Set-Variable
If there is a session state associated with the ScriptBlock
in [scriptblock]::Create().GetSteppablePipeline()
, then it should be possible to set a variable there and that variable should be visible from descendent scopes. Indeed, the code
$invoke = {
[CmdletBinding()]param(
$SubjectScriptBlock
)
$pipe = $SubjectScriptBlock.GetSteppablePipeline($MyInvocation.CommandOrigin,@())
$pipe.Begin($PSCmdlet)
$pipe.End()
}
. $invoke { . { begin { Set-Variable y why} } | & { $y }}
outputs why
. So there is a SessionState
that behave as normal with respect to setting local variables. But the very similar
$invoke = {
[CmdletBinding()]param(
$SubjectScriptBlock,
$a
)
$pipe = $SubjectScriptBlock.GetSteppablePipeline($MyInvocation.CommandOrigin,@($a))
$pipe.Begin($PSCmdlet)
$pipe.End()
}
. $invoke { param($__a) & { $__a }} -a aye
outputs nothing. None of the above seems to account for that difference.
Upvotes: 0