powershell - 从模块导出函数时,延迟绑定脚本块不起作用
问题描述
我有以下功能:
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
范围内运行脚本块- 如果这确实是意图,请参阅下面的变体解决方案。
“点源”脚本块也是标准 cmdletWhere-Object
和ForEach-Object
执行的操作。
# 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,在调用者作用域的子作用域中运行脚本块。
如果您不希望脚本块的执行意外修改调用者的变量,这会很有帮助。
这与使用引擎级延迟绑定脚本块和计算属性功能获得的行为相同 - 尽管尚不清楚该行为是否是故意选择的。
$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.
上面的输出 string Script: Test
,后跟42
,证明脚本块将输入对象视为$_
和该变量$var
- 尽管在脚本块中看到,但由于在子范围内运行而没有修改。
该ScriptBlock.InvokeWithContext()
方法记录在这里。
至于为什么你的尝试没有奏效:
通常,脚本块绑定到创建它们的作用域和作用域域(除非它们被明确创建为未绑定的脚本块,带有
[scriptblock]::Create('...')
)。模块之外的范围是默认范围域的一部分。每个模块都有自己的作用域域,除了全局作用域外,所有作用域域中的所有作用域都可以看到,不同作用域域中的作用域相互看不到。
您的脚本块是在默认范围域中创建的,当模块定义的函数调用它时,会在 origin 范围内
$_
查找,即在(非模块)调用方范围内,它没有被定义,因为自动变量是由 PowerShell 在本地范围内按需创建的,它位于封闭模块的范围域中。$_
通过使用
.InvokeWithContext()
,脚本块在调用者作用域的子.Invoke()
作用域中运行(与Invoke-Command
默认情况下一样),上面的代码将自定义$_
变量注入其中,以便脚本块可以引用它。
GitHub 问题 #3581中正在讨论为这些场景提供更好的 SDK 支持。
推荐阅读
- c# - 以不同用户身份从备份恢复 SQL 数据库
- excel - 使用 VBA 将 ListColumn 从一个表复制到另一个表时没有错误也没有数据
- python - Pyinstaller - 错误消息:名称 Actor 未定义
- android - 如何在我的项目结构中设置位置?
- java - 如何在 mockito 中模拟地图?
- angular - 如果列表为空,如何禁用 mat-form-field 或 mat-autocomplete?
- python - 为什么这在 c++ 中不起作用但在 python 中起作用
- visual-studio - 在 CMake 中添加 gRPC::grpc++ 库依赖后,libprotocd.lib 的意外路径
- django - 将模型传递给模板时,类视图错误“未定义名称‘上下文’”
- asp.net-core - 连接到本地 Identity Server 4 的新 Blazor 项目