【问题标题】:Why are my PowerShell exit codes always "0"?为什么我的 PowerShell 退出代码总是“0”?
【发布时间】:2013-03-24 12:53:37
【问题描述】:

我有一个 PowerShell 脚本如下

##teamcity[progressMessage 'Beginning build']
# If the build computer is not running the appropriate version of .NET, then the build will not run. Throw an error immediately.
if( (ls "$env:windir\Microsoft.NET\Framework\v4.0*") -eq $null ) {
    throw "This project requires .NET 4.0 to compile. Unfortunately .NET 4.0 doesn't appear to be installed on this machine."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Setting up variables']
# Set up variables for the build script
$invocation = (Get-Variable MyInvocation).Value
$directorypath = Split-Path $invocation.MyCommand.Path
$v4_net_version = (ls "$env:windir\Microsoft.NET\Framework\v4.0*").Name
$nl = [Environment]::NewLine

Copy-Item -LiteralPath "$directorypath\packages\NUnit.2.6.2\lib\nunit.framework.dll" "$directorypath\Pandell.Tests\bin\debug" -Force

##teamcity[progressMessage 'Using msbuild.exe to build the project']
# Build the project using msbuild.exe.
# Note we've already determined that .NET is already installed on this computer.
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Release
cmd /c C:\Windows\Microsoft.NET\Framework\$v4_net_version\msbuild.exe "$directorypath\Pandell.sln" /p:Configuration=Debug

# Break if the build throws an error.
if(! $?) {
    throw "Fatal error, project build failed"
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Build Passed']
# Good, the build passed
Write-Host "$nl project build passed."  -ForegroundColor Green


##teamcity[progressMessage 'running tests']
# Run the tests.
cmd /c $directorypath\build_tools\nunit\nunit-console.exe $directorypath\Pandell.Tests\bin\debug\Pandell.Tests.dll

# Break if the tests throw an error.
if(! $?) {
    throw "Test run failed."
    ##teamcity[buildStatus status='FAILURE' ]
}

##teamcity[progressMessage 'Tests passed']

据我所知,an uncaught Throw 将导致退出代码为1,但不幸的是,TeamCity 另有说明。

[19:32:20]Test run failed.
[19:32:20]At C:\BuildAgent\work\e903de7564e599c8\build.ps1:44 char:2
[19:32:20]+     throw "Test run failed."
[19:32:20]+     ~~~~~~~~~~~~~~~~~~~~~~~~
[19:32:20]    + CategoryInfo          : OperationStopped: (Test run failed.:String) [],
[19:32:20]   RuntimeException
[19:32:20]    + FullyQualifiedErrorId : Test run failed.
[19:32:20]
[19:32:20]Process exited with code 0
[19:32:20]Publishing internal artifacts
[19:32:20][Publishing internal artifacts] Sending build.finish.properties.gz file
[19:32:20]Build finished

注意我的Execution Mode 设置为Execute .ps1 script with "-File" argument 可能也很重要。

我尝试将其更改为Put script into PowerShell stdin with "-Command -" arguments,但即使通过了测试,它也会以1 的退出代码失败。我确信以-File 运行它会是正确的方式。

如果我打开位于 C:\BuildAgent\work\e903de7564e599c8\build.ps1 的脚本并在 CMD 中手动运行它,它会做同样的事情......即,失败的测试失败,%errorlevel% 仍然是 0

然而,如果我在 PowerShell 中运行它并调用 $LASTEXITCODE,它每次都会返回正确的代码。

【问题讨论】:

  • 我什至尝试在每个throw 之后添加[Environment]::Exit(1),但还是不行。
  • throw 之后的任何代码都不会执行。
  • 如果您将构建步骤的错误级别从“警告”更改为“错误”,会有所不同吗?
  • 我遇到了同样的问题。只需用简单的“exit -1”替换你的 throw。

标签: powershell teamcity exit-code


【解决方案1】:

这是 PowerShell 的一个已知问题。使用-file 执行脚本会返回退出代码 0,而它不应该这样做。

(更新:以下链接不再有效。请在 PowerShell: Hot (1454 ideas) – Windows Server 上查找或报告此问题)

由于使用-command 不适合您,您可以尝试在脚本顶部添加一个陷阱:

trap
{
    write-output $_
    ##teamcity[buildStatus status='FAILURE' ]
    exit 1
}

当抛出异常时,上述内容应该会产生正确的退出代码。

【讨论】:

  • 你能解释一下“为什么”这个陷阱会起作用吗?明天我回到项目时测试它。
  • 完美,成功了。有趣的是,这就是正在发生的事情。实际上有点令人沮丧。在问这个问题之前,我做了很多搜索,尽管我“假设”这是我的 teamcity 命令的问题,而不是 powershell 中的错误,它是自身的。
  • 虽然建议的 write-output $_ 正在工作,但 Write-Error -ErrorRecord $_ 会产生奇特的错误输出,就像 PowerShell 本身一样
  • 是否有理由在新的一行上使用'$_'和字符串,而不是在一行上使用Write-Output "##teamcity..."?
  • 如果按照@Mike 的建议使用Write-Error,请确保您的$ErrorActionPreference 未设置为“停止”,否则将导致脚本以代码0 和exit 1 退出到达。
【解决方案2】:

无论出于何种原因,这些选项都不适用于我的 PowerShell 脚本。我花了几个小时。

对我来说,最好的选择是在 TeamCity 和 PowerShell 之间放置一个层。所以我简单地编写了一个调用 PowerShell 脚本的 C# 控制台应用程序。

我的做法是,在 TeamCity 中,我们调用一个名为:RemoteFile.ps1的脚本

使用脚本参数:%system.RemoteServerFQDN% %system.RemoteUser% %system.RemoteUserPassword% %system.RemoteScriptName% %system.RemotePropertiesFile% %system.BuildVersion% %system.RunList%

param (
    [Parameter(Mandatory=$true)]
    $Computername,
    [Parameter(Mandatory=$true)]
    $Username,
    [Parameter(Mandatory=$true)]
    $Password,
    [Parameter(Mandatory=$true)]
    $ScriptName,
    [Parameter(Mandatory=$true)]
    $Propfile,
    [Parameter(Mandatory=$true)]
    $Version,
    [Parameter(Mandatory=$true)]
    [string[]]$DeploymentTypes
)

$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
Write-Host "Readying to execute invoke-command..."
Invoke-Command -ComputerName $Computername -Credential $cred -ScriptBlock {       D:\Deployment\PowershellWrapper.exe $using:ScriptName $using:Propfile $using:Version      $using:DeploymentTypes } -ArgumentList $ScriptName,$Propfile,$Version,$DeploymentTypes

存在于指定位置的远程服务器上。

然后该文件在指定位置调用:powershellwrapper.exe(我的脚本有四个参数要传递给 PowerShell 脚本)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace PowershellWrapper
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                string argFull = @"""{0} {1} {2} {3}""";
                string arg0 = args[0];
                string arg1 = args[1];
                string arg2 = args[2];
                string arg3 = args[3];
                string argFinal = string.Format(argFull, arg0, arg1, arg2, arg3);

                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = @"powershell.exe";
                startInfo.Arguments = argFinal;
                startInfo.RedirectStandardOutput = false;
                startInfo.RedirectStandardError = false;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardInput = true;
                startInfo.CreateNoWindow = false;
                Process process = new Process();
                process.StartInfo = startInfo;
                process.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("{0} Exception caught.", e);
                Console.WriteLine("An error occurred in the deployment.", e);
                Console.WriteLine("Please contact test@test.com if error occurs.");
            }
        }
    }
}

这用四个参数调用我的脚本。脚本是第一个参数,加上三个参数。所以基本上这里发生的事情是我正在执行 PowershellWrapper.exe 而不是 PowerShell 脚本本身来捕获错误的退出代码 0,它仍然会将完整的脚本报告回 TeamCity 日志。

我希望这是有道理的。它对我们来说就像一种魅力。

【讨论】:

【解决方案3】:

我在使用 -file 运行时遇到了这个确切的问题,但由于某种原因,Kevin 提供的陷阱语法或“退出”语法在我的场景中不起作用。

我不知道为什么,但以防万一其他人遇到同样的问题,我使用了以下语法,它对我有用:

try{
    #DO SOMETHING HERE
}
catch
{
    Write-Error $_
    ##teamcity[buildStatus status='FAILURE']
    [System.Environment]::Exit(1)
}

【讨论】:

  • 我没有尝试这个陷阱,但是用一个简单的“exit -1”重放投掷为我解决了这个问题。
【解决方案4】:

在命令上使用-ErrorAction stop 默认情况下会返回退出代码 1,并且在没有添加失败条件的情况下也在 TeamCity 中显示它。现在,我们将默认使用$ErrorActionPreference = "Stop"; 为每个 PowerShell 命令实现此行为。

【讨论】:

  • 我在这个网站和其他网站上尝试了很多方法,但都没有奏效。也许是因为我的 powershell one-liner: (Get-Content -path package.json -Raw) -replace '"version": "0.0.0"','"version": "0.0.1"' | Set-Content -Path package.json 没有抛出异常?无论如何,我改为使用 just 上面的-ErrorAction,现在该步骤失败了,例如如果文件丢失。我将它添加到 Get-Content 和 Set-Content
【解决方案5】:

直到这个(大概)被关闭为 dup of my self-answer of an older question,我将在这里总结最干净的解决方案:

  • 大多数其他答案涉及从 PowerShell 位向stderr 发出一些东西。这可以通过 Format stderr output as 选项直接使用 TeamCity 完成(将其设置为 Error 而不是默认值,即 Warning

  • 但是,至关重要的是,还需要在“Failure条件”(如果任何其他答案对您有用,您可能已经开启了此功能,但 IME非常很容易忘记!)

【讨论】:

  • 这是迄今为止此页面上最干净的解决方案
  • 这将使您的构建失败,但所有后续构建步骤,即使设置为仅在成功时运行,仍将执行
  • @craigBrett 如果得到确认,这显然是一个大问题 - 我没有使用 TC atm(叹气!)所以如果这个评论和this one 可以解决其他读者的决定性问题,那将是一个对每个人都有很大的帮助
  • 这是我正在尝试解决的当前问题。所以这就是我分享我的经验的原因。但我同意这个问题可能会引起很多人的关注。
  • 请注意,这似乎会导致一些良性消息被解释为错误,例如 npm 警告和 webpack 输出(如果构建 JS 应用程序)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-19
相关资源
最近更新 更多