gpt4 book ai didi

powershell - 从模块导出函数时,延迟绑定(bind)脚本 block 不起作用

转载 作者:行者123 更新时间:2023-12-02 23:53:10 25 4
gpt4 key购买 nike

我有以下功能:

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 导出它时,脚本 block 中的管道变量 $_ 总是 null:

Import-Module PipeModule
@{ Name = 'Test' } | PipeScript -ScriptBlock { $_.Name }

# Outputs: "Script: "

完整的复制品可在以下网址获得:https://github.com/lpatalas/DelayBindScriptBlock

有人可以解释这种行为吗?

最佳答案

感谢 PetSerAl 的帮助。

这是一个简单的解决方案,但请注意它直接在调用者的范围内运行脚本 block ,即它有效地“点源",它允许修改调用者的变量。

相比之下,您对 Invoke-Command 的使用在调用方作用域的作用域中运行脚本 block - 如果确实如此,请参阅下面的变体解决方案。

“点源”脚本 block 也是 Where-ObjectForEach-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: Test43 之后,证明输入对象被视为 $_ 并且点源有效($var 在调用者的范围内成功递增)。


这是一个变体,通过 PowerShell SDK,它在调用方作用域的作用域中运行脚本 block

如果您不希望脚本 block 的执行意外修改调用者的变量,这会很有帮助。

这与您使用引擎级延迟绑定(bind)脚本 block 和计算属性功能获得的行为相同 - 尽管是 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://learn.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 ,证明脚本 block 将输入对象视为 $_ 和变量 $var - 尽管在脚本 block 中看到,但未修改,由于在子范围内运行。

ScriptBlock.InvokeWithContext() 方法记录在 here 中。


至于为什么您的尝试没有成功:

  • 通常,脚本 block 被绑定(bind)到它们被创建的范围和范围域(除非它们被明确地创建为未绑定(bind)脚本 block , [scriptblock]::Create('...'))。

  • 模块外的范围是默认范围域的一部分。每个模块都有自己的作用域,除了全局作用域,所有作用域中的所有作用域都可以看到,不同作用域中的作用域彼此看不到。

  • 您的脚本 block 是在默认范围域中创建的,当模块定义的函数调用它时,会在原始范围中查找 $_,即在 (非模块)caller 作用域,它没有被定义,因为自动 $_ 变量是由 PowerShell 根据需要在 local 作用域中创建的,它位于封闭模块的范围域。

  • 通过使用 .InvokeWithContext() ,脚本 block 在调用者范围的 child 范围内运行(默认情况下 .Invoke()Invoke-Command 就是这种情况),上面的代码 注入(inject)自定义 $_ 变量,以便脚本 block 可以引用它。


GitHub issue #3581 中讨论了为这些场景提供更好的 SDK 支持

关于powershell - 从模块导出函数时,延迟绑定(bind)脚本 block 不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53326531/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com