我正在为我们的JavaScript项目开发构建环境。我们使用require.js(r.js)将不同的js模块组合成一个输出js文件。我们使用TeamCity,我想配置Powershell构建步骤,该步骤将调用r.js,读取它的标准输出并退出代码并将该退出代码传递回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 + Write-Host $ process.StandardOutput.ReadToEnd<<<< (); + CategoryInfo:InvalidOperation:(ReadToEnd:String)[],Runtime 例外 + FullyQualifiedErrorId:InvokeMethodOnNull
使用代码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构建步骤配置的更多详细信息,并将输出保存到构建日志中,如果这有助于回答问题。
答案 0 :(得分:1)
我通常使用问题中提到的启动过程命令的变体来解决这种情况。
$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将具有标准输出和错误内容。
答案 1 :(得分:0)
这就是你可以做到的。然而,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