【问题标题】:Reading exit code from a process in PowerShell从 PowerShell 中的进程读取退出代码
【发布时间】:2021-05-20 22:09:48
【问题描述】:

我正在为我们的 JavaScript 项目构建构建环境。我们使用 require.js (r.js) 将不同的 js 模块组合成一个输出 js 文件。我们使用 TeamCity,我想配置调用 r.js 的 Powershell 构建步骤,读取它的标准输出和退出代码,然后将该退出代码传递回 TeamCity(通过使用此退出代码从 Powershell 退出),这样如果任务失败( r.js 返回退出代码 1) 它不会继续执行其他构建步骤。我还希望将 r.js 的标准输出保存在 TeamCity 日志中,以便开发人员快速查看导致 r.js 停止的错误。

这是我可以使用它的参数启动 r.js 进程、读取它的退出代码并使用它从 Powershell 退出的方式:

$process = start-process r.js.cmd -ArgumentList "-o build-dev.js" -PassThru -Wait
exit $process.ExitCode

如果我在退出前尝试以这种方式读取标准输出:

Write-Host $process.StandardOutput.ReadToEnd();

我收到此错误,这可能表明我无法以这种方式读取 StandardOutput,因为它是一个流:

您不能在空值表达式上调用方法。 在行:1 字符:45 + 写入主机 $process.StandardOutput.ReadToEnd

进程以代码 1 退出

现在我找到了一种运行进程的方法,这样我就可以读取标准输出但我无法读取退出代码:

$psi = New-object System.Diagnostics.ProcessStartInfo 
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true 
$psi.RedirectStandardError = $true 
#$psi.FileName = "r.js.cmd"
$psi.FileName = "C:\Users\Administrator\AppData\Roaming\npm\r.js.cmd"
$psi.Arguments = @("-o build-dev.js")
#$psi.WorkingDirectory = (Get-Location).Path;
$process = New-Object System.Diagnostics.Process 
$process.StartInfo = $psi 
$process.Start() | Out-Host
$process.WaitForExit()
$output = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
sleep(10)
$exit_code = $process.ExitCode

write-host "========== OUTPUT =========="
write-host $output
write-host "========== ERROR =========="
write-host $stderr
write-host "========== EXIT CODE =========="
write-host $exit_code
write-host "========== $LastExitCode =========="
#write-host $LastExitCode
#Exit $exit_code

但这再次返回控制台输出,但退出代码始终为 0,即使 r.js 返回 1,因为我的 js 脚本中有错误。

谁能建议我如何使用 start-process 读取标准输出或如何使用 New-object System.Diadnostics.ProcessStartInfo 读取退出代码

如果有助于回答问题,我可以附上我的 TC 构建步骤配置和输出保存到构建日志的更多详细信息的屏幕截图。

【问题讨论】:

  • 你不能用$result = r.js.cmd -o build-dev.js; $exit_code = $LastExitCode吗?
  • 'r.js.cmd' 是 CMD 脚本吗?如果是这样,您确定它实际上会生成退出代码吗?如果 CMD 脚本正在启动执行构建的其他进程,它不仅需要报告退出代码,还需要将其冒泡,例如exit %buildProcessExitCode%
  • 如果下面的链接是 r.is.cmd,那么它不会生成退出代码。需要进行修改才能这样做。或者用PS重写。 code.google.com/p/yarse/source/browse/node_modules/.bin/…
  • @KeithHill 如果我尝试这个:$result = r.js.cmd -o build-dev.js; $exit_code = $LastExitCode 它给了我这个:Result: Error: Line 14: Unexpected token { ... 这很好,因为我故意在 js 文件中创建了一个错误以强制 r.js 失败并返回错误详细信息和退出代码 1。所以你可以看到我得到了标准输出。但它给了我退出代码 0 而不是 1。当我使用 start-process 时,我得到正确的退出代码。
  • @andyb 好建议,我会试一试。我注意到根据link 使用start-process-PassThru -Wait-PassThru 时正确读取退出代码为cmdlet 启动的每个进程返回一个进程对象。默认情况下,此 cmdlet 不会生成任何输出。 这可能是为什么 powershell 仍然能够从运行节点进程的 cmd 脚本中读取退出代码的原因,而节点进程本身并没有通过它。如果我删除 -PassThru,我将无法正确读取退出代码。

标签: powershell requirejs teamcity build-process exit-code


【解决方案1】:

我通常使用问题中提到的 start-process 命令的变体来解决这种情况。

$outputLog = "outputFile.log"
$errLog = "errorFile.log"
$process = Start-Process r.js.cmd -ArgumentList "-o build-dev.js" -PassThru -RedirectStandardOutput $outputLog -RedirectStandardError $errLog -Wait
$exitCode = $process.ExitCode

现在日志文件 $outputLog 和 $errorLog 将具有标准输出和错误内容。

【讨论】:

    【解决方案2】:

    这就是你可以做到的。然而,Cmd 文件不是应用程序,它们与 cmd 相关联,因此您可能必须以 cmd.exe 为目标并将 cmd 文件的路径作为参数。此外,UseShellExecute 用于进程启动信息会停止输出并记录错误,因此如果您需要输出,请不要启用它。 UseShellExecute 就像将 $Path 放入 Win+R 窗口一样。例如,如果它是一个 png,windows 会找到一个应该打开它的应用程序并用它打开该文件。

    $Path = "C:\whatever.exe"
    $WorkingDirectory = "C:\"
    $CreateNoWindow = $true #$true to not create another window for console application
    $Parameters = "-paremeters for -the app"
    $WindowStyle = [Diagnostics.ProcessWindowStyle]::Normal
    
    # prepare process start info
    $processStartInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' -ErrorAction 'Stop'
    $processStartInfo.FileName = $Path
    $processStartInfo.WorkingDirectory = $WorkingDirectory
    $processStartInfo.ErrorDialog = $false
    $processStartInfo.RedirectStandardOutput = $true
    $processStartInfo.RedirectStandardError = $true
    $processStartInfo.CreateNoWindow = $CreateNoWindow
    If ($Parameters) { $processStartInfo.Arguments = $Parameters }
    $processStartInfo.WindowStyle = $WindowStyle
    
    #create a process and assign the process start info object to it
    $process = New-Object -TypeName 'System.Diagnostics.Process' -ErrorAction 'Stop'
    $process.StartInfo = $processStartInfo
    
    #Add event handler to capture process's standard output redirection
    [scriptblock]$processEventHandler = { If (-not [string]::IsNullOrEmpty($EventArgs.Data)) { $Event.MessageData.AppendLine($EventArgs.Data) } }
    #create string builders to store the output and errors in
    $stdOutBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList ''
    $stdOutEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'OutputDataReceived' -MessageData $stdOutBuilder -ErrorAction 'Stop'
    $stdErrBuilder = New-Object -TypeName 'System.Text.StringBuilder' -ArgumentList ''
    $stdErrEvent = Register-ObjectEvent -InputObject $process -Action $processEventHandler -EventName 'ErrorDataReceived' -MessageData $stdErrBuilder -ErrorAction 'Stop'
    
    #start the process
    $null = $process.Start()
    #begin reading the output and errors
    $process.BeginOutputReadLine()
    $process.BeginErrorReadLine()
    #Instructs the Process component to wait indefinitely for the associated process to exit.
    $process.WaitForExit()
    #HasExited indicates that the associated process has terminated, either normally or abnormally. Wait until HasExited returns $true.
    While (-not ($process.HasExited)) { $process.Refresh(); Start-Sleep -Seconds 1 }
    
    ## Get the exit code for the process
    Try {
        [int32]$returnCode = $process.ExitCode
    }
    Catch [System.Management.Automation.PSInvalidCastException] {
        #  Catch exit codes that are out of int32 range
        [int32]$returnCode = 1
    }
    
    #unregister the events
    If ($stdOutEvent) { Unregister-Event -SourceIdentifier $stdOutEvent.Name -ErrorAction 'Stop'; $stdOutEvent = $null }
    If ($stdErrEvent) { Unregister-Event -SourceIdentifier $stdErrEvent.Name -ErrorAction 'Stop'; $stdErrEvent = $null }
    $stdOut = $stdOutBuilder.ToString() -replace $null,''
    $stdErr = $stdErrBuilder.ToString() -replace $null,''
    ## Free resources associated with the process, this does not cause process to exit
    If ($process) { $process.Dispose() }
    # check if we have output and return it
    If ($stdOut) {
        Write-Output "Process output:$stdOut"
    }
    
    If ($stdErr) {
        Write-Output "Process errors:$stdErr"
    }
    
    # return exit code
    Write-Output "Exit code:$returnCode"
    Start-Sleep -Seconds 5
    Exit $returnCode
    

    【讨论】:

      猜你喜欢
      • 2019-12-19
      • 1970-01-01
      • 2021-03-17
      • 1970-01-01
      • 2017-02-04
      • 1970-01-01
      • 1970-01-01
      • 2021-08-16
      • 1970-01-01
      相关资源
      最近更新 更多