从模块导出函数时,延迟绑定脚本块不起作用
Delay-bind script block does not work when function is exported from module
我有以下功能:
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject,
[Object] $ScriptBlock
)
process {
$value = Invoke-Command -ScriptBlock $ScriptBlock
Write-Host "Script: $value"
}
}
当我直接在脚本中定义此函数并通过管道输入到其中时,我得到了以下预期结果:
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name }
# Outputs: "Script: Test"
但是当我在模块中定义这个函数并用 Export-ModuleMember -Function PipeScript
导出它时,脚本块中的管道变量 $_
总是 null
:
Import-Module PipeModule
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name }
# Outputs: "Script: "
可在以下位置获得完整复制:https://github.com/lpatalas/DelayBindScriptBlock
有人可以解释这种行为吗?
感谢 PetSerAl 的帮助。
这是一个简单的解决方案,但请注意它在调用者的范围内直接运行脚本块,即它实际上是“点源”,允许修改调用者的变量。
相比之下,您对 Invoke-Command
的使用在调用方范围的 child 范围内运行脚本块 - 如果确实如此,请参阅变体解决方案下面。
“点源”脚本块也是 Where-Object
和 ForEach-Object
等标准 cmdlet 所做的。
# Define the function in an (in-memory) module.
# An in-memory module is automatically imported.
$null = New-Module {
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject
,
[scriptblock] $ScriptBlock
)
process {
# Use ForEach-Object to create the automatic $_ variable
# in the script block's origin scope.
$value = ForEach-Object -Process $ScriptBlock -InputObject $InputObject
# Output the value
"Script: $value"
}
}
}
# Test the function:
$var = 42; @{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name; ++$var }
$var # -> 43 - the script block ran in the caller's scope.
以上输出字符串 Script: Test
和 43
之后,证明输入对象被视为 $_
并且点源有效($var
已成功递增在调用者的范围内)。
这里有一个 变体,通过 PowerShell SDK,在 child 范围内运行脚本块调用者的范围.
如果您不希望脚本块的执行意外修改调用者的变量,这会很有帮助。
这与您使用引擎级延迟绑定脚本块和计算的 属性 功能获得的行为相同 - 尽管 it's unclear whether that behavior was chosen intentionally.
$null = New-Module {
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject
,
[scriptblock] $ScriptBlock
)
process {
# Use ScriptBlock.InvokeContext() to inject a $_ variable
# into the child scope that the script block runs in:
# Creating a custom version of what is normally an *automatic* variable
# seems hacky, but the docs do state:
# "The list of variables may include the special variables
# $input, $_ and $this." - see https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock.invokewithcontext
$value = $ScriptBlock.InvokeWithContext(
$null, # extra functions to define (none here)
[psvariable]::new('_', $InputObject) # actual parameter type is List<PSVariable>
)
# Output the value
"Script: $value"
}
}
}
# Test the function:
$var = 42
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name; ++$var }
$var # -> 42 - unaltered, because the script block ran in a child scope.
以上输出字符串 Script: Test
,后跟 42
,证明脚本块将输入对象视为 $_
和变量 $var
- 虽然 在脚本块中看到,未修改,因为在子范围中运行。
ScriptBlock.InvokeWithContext()
方法已记录 here。
至于为什么你的尝试没有成功:
通常,脚本块被绑定到它们创建的范围和范围域(除非它们被明确创建为unbound 脚本块,[scriptblock]::Create('...')
).
模块外的范围是默认范围域的一部分。每个模块都有自己的作用域,除了全局作用域,所有作用域中的所有作用域都可以看到,不同作用域中的作用域彼此看不到。
您的脚本块是在默认范围域中创建的,当模块定义的函数调用它时,$_
在原始范围中查找 ,即在(非模块)caller 作用域中,它未定义,因为自动 $_
变量是由 PowerShell 按需创建的在 local 范围内,它在封闭模块的范围域中。
通过使用 .InvokeWithContext()
,脚本块在调用者范围的 child 范围内运行(与 [=28 一样) =] 和 Invoke-Command
默认情况下),上面的代码 向其中注入自定义 $_
变量 以便脚本块可以引用它。
为这些场景提供更好的 SDK 支持 正在 GitHub issue #3581 中讨论。
我有以下功能:
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject,
[Object] $ScriptBlock
)
process {
$value = Invoke-Command -ScriptBlock $ScriptBlock
Write-Host "Script: $value"
}
}
当我直接在脚本中定义此函数并通过管道输入到其中时,我得到了以下预期结果:
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name }
# Outputs: "Script: Test"
但是当我在模块中定义这个函数并用 Export-ModuleMember -Function PipeScript
导出它时,脚本块中的管道变量 $_
总是 null
:
Import-Module PipeModule
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name }
# Outputs: "Script: "
可在以下位置获得完整复制:https://github.com/lpatalas/DelayBindScriptBlock
有人可以解释这种行为吗?
感谢 PetSerAl 的帮助。
这是一个简单的解决方案,但请注意它在调用者的范围内直接运行脚本块,即它实际上是“点源”,允许修改调用者的变量。
相比之下,您对 Invoke-Command
的使用在调用方范围的 child 范围内运行脚本块 - 如果确实如此,请参阅变体解决方案下面。
“点源”脚本块也是 Where-Object
和 ForEach-Object
等标准 cmdlet 所做的。
# Define the function in an (in-memory) module.
# An in-memory module is automatically imported.
$null = New-Module {
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject
,
[scriptblock] $ScriptBlock
)
process {
# Use ForEach-Object to create the automatic $_ variable
# in the script block's origin scope.
$value = ForEach-Object -Process $ScriptBlock -InputObject $InputObject
# Output the value
"Script: $value"
}
}
}
# Test the function:
$var = 42; @{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name; ++$var }
$var # -> 43 - the script block ran in the caller's scope.
以上输出字符串 Script: Test
和 43
之后,证明输入对象被视为 $_
并且点源有效($var
已成功递增在调用者的范围内)。
这里有一个 变体,通过 PowerShell SDK,在 child 范围内运行脚本块调用者的范围.
如果您不希望脚本块的执行意外修改调用者的变量,这会很有帮助。
这与您使用引擎级延迟绑定脚本块和计算的 属性 功能获得的行为相同 - 尽管 it's unclear whether that behavior was chosen intentionally.
$null = New-Module {
function PipeScript {
param(
[Parameter(ValueFromPipeline)]
[Object] $InputObject
,
[scriptblock] $ScriptBlock
)
process {
# Use ScriptBlock.InvokeContext() to inject a $_ variable
# into the child scope that the script block runs in:
# Creating a custom version of what is normally an *automatic* variable
# seems hacky, but the docs do state:
# "The list of variables may include the special variables
# $input, $_ and $this." - see https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock.invokewithcontext
$value = $ScriptBlock.InvokeWithContext(
$null, # extra functions to define (none here)
[psvariable]::new('_', $InputObject) # actual parameter type is List<PSVariable>
)
# Output the value
"Script: $value"
}
}
}
# Test the function:
$var = 42
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name; ++$var }
$var # -> 42 - unaltered, because the script block ran in a child scope.
以上输出字符串 Script: Test
,后跟 42
,证明脚本块将输入对象视为 $_
和变量 $var
- 虽然 在脚本块中看到,未修改,因为在子范围中运行。
ScriptBlock.InvokeWithContext()
方法已记录 here。
至于为什么你的尝试没有成功:
通常,脚本块被绑定到它们创建的范围和范围域(除非它们被明确创建为unbound 脚本块,
[scriptblock]::Create('...')
).模块外的范围是默认范围域的一部分。每个模块都有自己的作用域,除了全局作用域,所有作用域中的所有作用域都可以看到,不同作用域中的作用域彼此看不到。
您的脚本块是在默认范围域中创建的,当模块定义的函数调用它时,
$_
在原始范围中查找 ,即在(非模块)caller 作用域中,它未定义,因为自动$_
变量是由 PowerShell 按需创建的在 local 范围内,它在封闭模块的范围域中。通过使用
.InvokeWithContext()
,脚本块在调用者范围的 child 范围内运行(与 [=28 一样) =] 和Invoke-Command
默认情况下),上面的代码 向其中注入自定义$_
变量 以便脚本块可以引用它。
为这些场景提供更好的 SDK 支持 正在 GitHub issue #3581 中讨论。