如何还原通过Start-Job传递的序列化ScriptBlock并正常解析变量
PowerShell 5.1中Start-Job传递ScriptBlock后参数解析问题解决方法
问题场景
现有函数Format-Text接收对象和格式化用的ScriptBlock,外部调用正常,但将其放入Start-Job中执行时,ScriptBlock里的$($args[0].cheese)无法正确解析,输出为空。简化代码及运行结果如下:
原示例代码
function Format-Text { [CmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject, [Parameter(Mandatory)][ScriptBlock]$Formatter) Write-Host ($Formatter.Invoke($InputObject)) } $formatter = {"My favourite cheese is: $($args[0].cheese)"} $testObject = [PSCustomObject]@{cheese = 'cheddar'} Format-Text -InputObject $testObject -Formatter $formatter # 作业内执行部分 $job = Start-Job -ArgumentList $testObject,$formatter.ToString() -ScriptBlock { param ([String]$Obj, [String]$Fmtr) function Format-Text { [CmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject, [Parameter(Mandatory)][ScriptBlock]$Formatter) Write-Host ($Formatter.Invoke($InputObject)) } $sb = [ScriptBlock]::Create($Fmtr) Format-Text -InputObject $Obj -Formatter $sb } # 作业清理 do { Start-Sleep -Milliseconds 500 } until ($job.State -ne 'Running') Receive-Job $job; Remove-Job $job
原运行输出
My favourite cheese is: cheddar My favourite cheese is:
问题原因
- 对象类型错误:作业内部参数将
$Obj定义为[String],导致传入的PSCustomObject被强制转换为字符串,丢失了原对象的属性结构,因此$args[0].cheese无法找到对应属性。 - ScriptBlock传递时的序列化问题可通过
[ScriptBlock]::Create()还原,核心问题在于对象传递后的类型丢失。
解决方案
无需重写现有Format-Text函数,只需修改作业内的两个关键点:
- 将作业参数中的
$Obj类型从[String]改为[Object],保留原对象的结构; - 传递ScriptBlock时无需手动调用
.ToString(),直接传入$formatter即可(跨作业边界时PowerShell会自动将ScriptBlock序列化为字符串)。
修改后的完整代码
function Format-Text { [CmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject, [Parameter(Mandatory)][ScriptBlock]$Formatter) Write-Host ($Formatter.Invoke($InputObject)) } $formatter = {"My favourite cheese is: $($args[0].cheese)"} $testObject = [PSCustomObject]@{cheese = 'cheddar'} Format-Text -InputObject $testObject -Formatter $formatter # 修改后的作业执行部分 $job = Start-Job -ArgumentList $testObject, $formatter -ScriptBlock { param ([Object]$Obj, # 改为Object类型,保留原对象结构 [String]$Fmtr) # 直接接收序列化后的ScriptBlock字符串 function Format-Text { [CmdletBinding()] param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject, [Parameter(Mandatory)][ScriptBlock]$Formatter) Write-Host ($Formatter.Invoke($InputObject)) } $sb = [ScriptBlock]::Create($Fmtr) Format-Text -InputObject $Obj -Formatter $sb } # 作业清理 do { Start-Sleep -Milliseconds 500 } until ($job.State -ne 'Running') Receive-Job $job; Remove-Job $job
验证输出
修改后运行代码,会得到一致的正确输出:
My favourite cheese is: cheddar My favourite cheese is: cheddar
内容的提问来源于stack exchange,提问作者user2871239
相关产品推荐
相关产品推荐

