[scriptblock]::Create()生成的脚本块调用GetSteppablePipeline时绑定到哪个SessionState?
Jason Shirk 指出:
若使用{ ... }语法创建脚本块,它会立即绑定到SessionState;若通过其他方式(如[ScriptBlock]::Create())创建,则会在首次调用时绑定到当前活跃的SessionState。
这一描述是正确的,有相关讨论线程深入探讨了此行为,还有带注释的示例演示PowerShell作用域、.和&运算符、模块与SessionState之间的交互。
请考虑以下代码:通过[scriptblock]::Create()生成的两个相同脚本块实例,分别使用调用运算符&和SteppablePipeline进行调用:
function Invoke-Call { [CmdletBinding()] param( [scriptblock] $ScriptBlock, $a ) & ([scriptblock]::Create({ param ($sb,$__a) . $sb $__a })) ` $ScriptBlock $a } function Invoke-Steppable { [CmdletBinding()] param( [scriptblock] $ScriptBlock, $a ) $pipe = [scriptblock]::Create({ param ($sb,$__a) . $sb $__a }). 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 }}
该代码输出如下:
function x a __a -------- - - --- Invoke-Call c c c Invoke-Steppable s s
关于上述输出,有以下几点值得注意:
$a在两种调用-ScriptBlock的方式下都可访问。这是因为函数和-ScriptBlock都绑定到同一个SessionState,函数的参数$a所在的作用域是-ScriptBlock的祖先作用域,因此$a可见。- 当
-ScriptBlock通过Invoke-Call调用时,$__a是可访问的。这与前一点类似:[scriptblock]::Create()生成的脚本块调用时绑定到Invoke-Call的SessionState,与-ScriptBlock的SessionState相同,因此$__a可见。 - 当
-ScriptBlock通过Invoke-Steppable调用时,$__a不可访问。
这表明通过SteppablePipeline调用的[scriptblock]::Create()生成的脚本块,并未绑定到与-ScriptBlock相同的SessionState。
问题解答
通过SteppablePipeline调用的[scriptblock]::Create()生成的脚本块,会绑定到全局SessionState(即PowerShell最顶层的会话状态,而非函数的局部SessionState)。
原因在于:GetSteppablePipeline方法在创建可步进管道时,会将脚本块绑定到与当前执行上下文的顶层会话状态关联的作用域,而非调用该方法的函数的局部SessionState。在示例中,Invoke-Steppable函数的局部变量$__a属于函数的局部作用域,而通过可步进管道执行的脚本块处于全局SessionState,无法跨作用域访问函数的局部变量,因此$__a不可见。
这种差异的核心是:调用运算符&会在当前函数的局部SessionState上下文执行脚本块,而SteppablePipeline的创建逻辑会将脚本块绑定到全局会话状态,脱离了函数的局部作用域上下文。
内容的提问来源于stack exchange,提问作者alx9r

