启动进程后作业脚本块中的代码未执行

3
当我使用PowerShell 5.1创建自动化脚本时,我遇到了一个问题——在作业的脚本块中,Start-Process之后的代码将没有机会执行。这里是一个简单的重现:
第一步 >> 准备一个用于Start-Process的.cmd文件,callee.cmd中的代码如下:
@echo off
echo "Callee is executing ..."
exit /B 0

第二步 >> 准备 PowerShell 代码,

$scriptBlock = {
    $res = Start-Process -FilePath "cmd.exe" -Wait -PassThru -NoNewWindow -ArgumentList "/c .\callee.cmd"
    throw "ERROR!"
}

$job = Start-Job -ScriptBlock $scriptBlock
Wait-Job $job
Receive-Job $job
Write-Host($job.State)

第三步 >> 运行PowerShell脚本,屏幕上的输出结果是:

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
1      Job1            BackgroundJob   Completed     True            localhost            ...
Completed

预期值应该是“失败”。 我的代码有问题吗,还是我使用任务的方式不正确?

1
可能是如何捕获start-job的scriptblock中引发的异常?的重复问题。 - Hackerman
你传递的参数列表有误。应该是String[]类型。每个参数都需要新的[String]。另外,为什么每次运行都要抛出一个错误? - Maximilian Burszley
1
“throw”仅用于证明Start-Process后的代码不会被执行。谢谢大家。 - Timothy Liu
尝试使用 $res = Start-Process -FilePath "cmd.exe" -Wait -PassThru -NoNewWindow -ArgumentList ".\callee.cmd","/c",你会发现它可以工作。或者将 -NoNewWindow 更改为 -WindowStyle Hidden - TheMadTechnician
2个回答

3

Start-Job命令可以在单独的PowerShell进程中以所谓的“服务器模式”运行作业。在此模式下,PowerShell作业进程使用标准输入和输出流与父进程交换消息。

Start-Process cmdlet的-NoNewWindow参数指示将生成的控制台子进程连接到其父进程的标准流。

因此,在PowerShell作业内部使用Start-Process -NoNewWindow,您可以将生成的cmd.exe进程连接到与PowerShell作业进程用于交换消息的相同流中。

现在,当生成的cmd.exe向其标准输出流写入内容时,它会干扰PowerShell作业进程与其父进程之间的正常消息交换,从而导致一些意外的行为。


这就是导致 Receive-Job : There is an error processing data from the background process. Error reported: Cannot process an element with node type "Text". Only Element and EndElement node types are supported. 错误的原因,对吧?看起来你已经找到了解决方法:https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/14915283-job-cmdlets-fail-with-utf-8-codepage(在 SO 上重新发布:https://dev59.com/05Hea4cB1Zd3GeqPpnrP#43350250) - Ohad Schneider
据我所知,这些问题实际上并不相关。它们有不同的根本原因。 - user4003407
我很困惑,你从 https://dev59.com/boHba4cB1Zd3GeqPQ2zT 链接到这个问题,该问题讨论“仅支持元素和EndElement节点类型”的错误。 - Ohad Schneider
当错误信息非常独特和具体时,通常会有所作为,但我承认这并不是保证。那么在我提到的情况下,根本原因是什么? - Ohad Schneider
2
我想我明白你的意思。这个错误通常意味着一个由作业创建的PS进程无法以你上面解释的方式与父级PS进程通信。一个原因可能是进程直接写入输出流(正如在此线程中的情况)。另一个原因可能是父进程期望BOM,而子进程没有写入BOM(或者可能是相反的情况)。在我的情况下,Azure DevOps创建了一个期望BOM的PS会话,但这在Start-Job调用之间没有保留。 - Ohad Schneider
显示剩余3条评论

1

PetSerAl提供了很好的解释,但我花了一段时间才找到一个合适的解决方案。

首先 - 如一些人所提到的,不要使用-NoNewWindow,而是使用-WindowStyle Hidden

其次,将结果输出到文件中,并在脚本块中处理它们。有两个参数,一个用于输出,另一个用于错误-RedirectStandardOutput-RedirectStandardError。 由于某些原因,我确信第一个参数将处理所有输出,如果没有错误,我的代码可以正常工作,但在脚本块异常时没有输出而失败。

我创建了一个函数,也处理进程状态结果:

function RunProcesSafe($pathToExe, $ArgumentList)
{               
 Write-Host "starting $pathToExe $ArgumentList"
 $logFile = Join-Path $env:TEMP ([guid]::NewGuid())
 $errorLogFile = Join-Path $env:TEMP ([guid]::NewGuid())
 try
 {
    Write-Host "starting $pathToExe $ArgumentList"
    $proc = Start-Process "$pathToExe" -Wait -PassThru -RedirectStandardError $errorLogFile -RedirectStandardOutput $logFile -WindowStyle Hidden -ArgumentList $ArgumentList
    $handle = $proc.Handle          
    $proc.WaitForExit();
    if ($proc.ExitCode -ne 0) {
         Write-Host "FAILED with code:"+$proc.ExitCode
         Throw "$_ exited with status code $($proc.ExitCode)"
    }
 }
 Finally
 {
    Write-Host  (Get-Content -Path $logFile) 
    Write-Host  (Get-Content -Path $errorLogFile) 
    Remove-Item -Path $logFile -Force
    Remove-Item -Path $errorLogFile -Force
 }         
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接