为什么我的PowerShell退出代码始终为“ 0”?


73

我有一个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']

我相信,未捕获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,则每次都会返回正确的代码。


我什至尝试[Environment]::Exit(1)在每个之后添加throw,但仍然没有用。
Chase Florell

4
之后的任何代码throw都不会执行。
强麦Roubíček

1
如果将构建步骤的错误级别从“警告”更改为“错误”,是否有所不同?
devlord

我遇到了同样的问题。只需用一个简单的“退出-1”代替抛出。
Derek Greer

Answers:


96

这是PowerShell的已知问题。使用来执行脚本-file时,不应该返回退出代码0。

(更新:下面的链接不再起作用。请在PowerShell上查找或报告此问题:热门(1454个想法)– Windows Server

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

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

抛出异常时,以上内容应导致正确的退出代码。


2
您能否解释“为什么”该陷阱起作用?我明天回到项目时将对其进行测试。
Chase Florell

完美,行得通。有趣的是,这是怎么回事。实际上有点令人沮丧。在问这个问题之前,我做了很多搜索,尽管我“认为”这是我的teamcity命令的问题,而不是powershell本身的错误。
Chase Florell

5
虽然建议write-output $_的工作方式Write-Error -ErrorRecord $_会像PowerShell本身一样产生奇特的错误输出
Mike

是否有理由在新的一行上使用'$ _'和字符串,而不是在一行上全部使用Write-Output“ ## teamcity ...”?
罗宾(Robin)

2
如果Write-Error按照@Mike的建议使用,请确保您$ErrorActionPreference未将其设置为“ Stop”,否则将导致脚本以代码0退出并且exit 1永远不会到达。
EM0

25

使用时,我遇到了这个确切的问题-file,但是由于某种原因,凯文提供的trap语法或'exit'语法在我的场景中不起作用。

我不确定为什么,但是以防万一有人遇到相同的问题,我使用了以下语法,它对我有用:

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

我没有尝试过陷阱,但是用一个简单的“ exit -1”(重现-1)重播了这个问题,这对我来说很重要。
Derek Greer

16

我对一个较早的问题进行自我解答之前,直到(可能)关闭此方法之前,我将在此处总结最干净的解决方案:

  • 其他大多数答案都涉及stderr从PowerShell位向发出一些东西。这可以通过TeamCity通过将stderr输出格式设置选项直接完成(将其设置为Error而不是默认值Warning)。

  • 但是,至关重要的是,还必须打开“以下情况下的构建失败:... (sic)构建运行器记录了一条错误消息”,位于“失败条件”下(如果有其他答案对您有用,您会可能已经打开了此功能,但是IME容易忘记!)


2
这是到目前为止此页面上最干净的解决方案
lantrix

这是真正的解决方案。谢谢。
米歇尔·布格

1
这将使您的构建失败,但是即使设置为仅成功运行,您的所有后续构建步骤仍然会执行
Craig Brett

@craigBrett如果得到确认,那显然是个大问题-我没有使用TC atm(叹气!),因此,如果可以将此评论和这篇评论解决为对其他读者来说是个决定性的问题,对所有人都会有很大帮助
Ruben Bartelink

这是从我要解决的当前问题开始的。这就是为什么我分享我的经验。但是我同意这个问题可能会吸引很多人。
克雷格·布雷特

2

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


我在此站点和其他站点上尝试了很多方法,但没有一个起作用。也许是因为我的强而有力的一线: (Get-Content -path package.json -Raw) -replace '"version": "0.0.0"','"version": "0.0.1"' | Set-Content -Path package.json不会抛出异常吗?无论如何,我使用了上面的-ErrorAction,现在该步骤失败了,例如,如果文件丢失。我将其添加到Get-Content和Set-Content中
ColH

1

无论出于何种原因,这些选项都无法在我的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日志。

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


Sholdnt是必要的,我的回答似乎可以等效地工作,而无需在脚本和/或其他帮助程序中出现疣-不幸的是,TeamCity中没有很好地记录下来。见stackoverflow.com/questions/11647987/…–
鲁本·巴特林克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.