我具有以下功能:
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
有人可以解释这种行为吗?
答案 0 :(得分:0)
向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
-尽管 由于在子作用域中运行,因此未对 进行修改。
here中记录了ScriptBlock.InvokeWithContext()
方法。
关于为什么您的尝试不起作用:
通常,脚本块绑定到创建的作用域和作用域域(除非它们明确地创建为 unbound 脚本块, [scriptblock]::Create('...')
)。
模块外部的作用域是默认作用域域的一部分。每个模块都有自己的作用域,除了全局作用域(所有作用域中的所有作用域都可以看到)之外,不同作用域中的作用域不会相互看到。
您的脚本块是在默认范围域中创建的,当模块定义的函数调用它时,会在源范围内查找$_
,即 ,在(非模块)调用方范围内(未定义),因为自动$_
变量是由PowerShell根据需要在 local 中创建的作用域,它位于封闭模块的作用域域中。
通过使用.InvokeWithContext()
,脚本块在调用者作用域的 child 作用域中运行(与.Invoke()
和{{1}一样) }),上面的代码向其中注入一个自定义Invoke-Command
变量,以便脚本块可以引用它。
为这些情况提供更好的SDK支持。