Warum sind meine PowerShell-Exit-Codes immer "0"?


73

Ich habe ein PowerShell-Skript wie folgt

##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']

Soweit ich glaube, führt ein UngefangenerThrow zu einem Exit-Code von 1, aber leider sagt TeamCity etwas anderes.

[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

Es kann auch wichtig sein zu beachten, dass my auf eingestellt Execution Modeist Execute .ps1 script with "-File" argument.

Ich habe versucht, es zu ändern Put script into PowerShell stdin with "-Command -" arguments, aber dann ist es mit einem Exit-Code von 1sogar beim Bestehen von Tests fehlgeschlagen . Ich bin mir sicher, dass es -Fileder richtige Weg sein wird , es so zu betreiben .

Wenn ich das Skript in öffne C:\BuildAgent\work\e903de7564e599c8\build.ps1und es manuell in CMD ausführe, funktioniert es genauso ... Das heißt, die fehlgeschlagenen Tests schlagen fehl und das %errorlevel%ist immer noch so 0.

Wenn ich es jedoch in PowerShell ausführe und aufrufe $LASTEXITCODE, wird jedes Mal der richtige Code zurückgegeben.


Ich habe sogar versucht, [Environment]::Exit(1)gleich nach jedem hinzuzufügen throw, aber es hat immer noch nicht funktioniert.
Chase Florell

4
Code danach throwwird nicht ausgeführt.
Aleš Roubíček

1
Wenn Sie die Fehlerstufe für den Erstellungsschritt von "Warnung" in "Fehler" ändern, macht dies einen Unterschied?
Devlord

Ich bin auf dasselbe Problem gestoßen. Ersetzen Sie einfach Ihren Wurf durch einen einfachen "Ausgang -1".
Derek Greer

Antworten:


96

Dies ist ein bekanntes Problem mit PowerShell. Das Ausführen eines Skripts mit -filegibt den Exit-Code 0 zurück, wenn dies nicht der Fall sein sollte.

(Update: Die folgenden Links funktionieren nicht mehr. Bitte suchen oder melden Sie dieses Problem in PowerShell: Hot (1454 Ideen) - Windows Server. )

Da die Verwendung -commandbei Ihnen nicht funktioniert hat, können Sie versuchen, oben im Skript eine Falle hinzuzufügen:

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

Das Obige sollte zu einem korrekten Exit-Code führen, wenn eine Ausnahme ausgelöst wird.


2
Können Sie erklären, warum diese Falle funktionieren würde? Ich werde es morgen testen, wenn ich wieder im Projekt bin.
Chase Florell

perfekt, das hat funktioniert. Es ist interessant, dass dies der Fall ist. Eigentlich irgendwie frustrierend. Ich habe ziemlich viel gesucht, bevor ich diese Frage gestellt habe, obwohl ich "angenommen" habe, dass es ein Problem mit meinem Teamcity-Befehl ist und kein Fehler in Powershell selbst.
Chase Florell

5
Während vorgeschlagen write-output $_funktioniert, Write-Error -ErrorRecord $_erzeugt ausgefallene Fehlerausgabe genau wie PowerShell selbst
Mike

Gibt es einen Grund, '$ _' und die Zeichenfolge in einer neuen Zeile zu verwenden, anstatt "## teamcity ..." in einer Zeile zu schreiben?
Robin

2
Wenn Sie Write-Errorwie von @Mike vorgeschlagen verwenden, stellen Sie sicher, dass Ihr $ErrorActionPreferencenicht auf "Stop" eingestellt ist. Andernfalls wird das Skript mit Code 0 beendet und exit 1nie erreicht.
EM0

25

Ich hatte genau dieses Problem beim Ausführen von -file, aber aus irgendeinem Grund funktionierte die Trap-Syntax oder die von Kevin bereitgestellte Exit-Syntax in meinem Szenario nicht.

Ich bin mir nicht sicher warum, aber nur für den Fall, dass jemand anderes das gleiche Problem hat, habe ich die folgende Syntax verwendet und es hat bei mir funktioniert:

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

Ich habe die Falle nicht ausprobiert, aber das Wiederholen des Wurfs mit einem einfachen "exit -1" hat das Problem für mich behoben.
Derek Greer

16

Bis dies (vermutlich) als Dup meiner Selbstantwort auf eine ältere Frage geschlossen wird, fasse ich hier die sauberste Lösung zusammen:

  • Bei den meisten anderen Antworten wird etwas stderraus dem PowerShell-Bit ausgegeben. Dies kann direkt mit TeamCity über die Option Format stderr output als Option erreicht werden (setzen Sie sie auf Error anstelle der Standardeinstellung Warning ).

  • Entscheidend ist jedoch auch, dass unter " Fehlerbedingungen " die Option "Build fehlschlagen, wenn: ... eine Fehlermeldung vom (sic) Build Runner protokolliert wird " aktiviert wird (wenn eine der anderen Antworten für Sie funktioniert) wahrscheinlich haben Sie dies bereits eingeschaltet, aber IME ist es sehr leicht zu vergessen!)


2
Dies ist bei weitem die sauberste Lösung auf dieser Seite
Lantrix

Dies ist die wahre Lösung. Vielen Dank.
Michiel Bugher

1
Dies wird Ihren Build fehlschlagen, aber alle Ihre nachfolgenden Build-Schritte, auch wenn sie nur auf Erfolg ausgeführt werden, werden weiterhin ausgeführt
Craig Brett

@craigBrett , die offensichtlich ein großes Problem ist , wenn bestätigt - ich bin mit TC nicht atm (seufz!) also , wenn dieser Kommentar und dies kann für andere Leser etwas schlüssig gelöst werden , die eine große Hilfe für alle sein würden
Ruben Bartelink

Dies geht von dem aktuellen Problem aus, das ich zu lösen versuche. Deshalb habe ich meine Erfahrungen geteilt. Aber ich stimme zu, dass dieses Problem wahrscheinlich ziemlich viele Menschen betrifft.
Craig Brett

2

Die Verwendung -ErrorAction stopeines Befehls gibt standardmäßig einen Exit-Code 1 zurück und zeigt ihn auch in TeamCity an, ohne eine Fehlerbedingung hinzuzufügen. Dieses Verhalten wird jetzt standardmäßig für jeden verwendeten PowerShell-Befehl implementiert $ErrorActionPreference = "Stop";.


Ich habe viele der Methoden auf dieser und anderen Seiten ausprobiert, aber keine hat funktioniert. Vielleicht, weil mein Powershell-Einzeiler (Get-Content -path package.json -Raw) -replace '"version": "0.0.0"','"version": "0.0.1"' | Set-Content -Path package.jsonkeine Ausnahme macht? Wie auch immer, ich habe stattdessen nur die obige -ErrorAction verwendet, und jetzt schlägt der Schritt fehl, z. B. wenn die Datei fehlt. Ich habe es sowohl zum Get-Content als auch zum Set-Content
hinzugefügt

1

Keine dieser Optionen hat in meinem PowerShell-Skript aus irgendeinem Grund funktioniert. Ich habe Stunden damit verbracht.

Für mich war die beste Option, eine Ebene zwischen TeamCity und PowerShell zu legen. Also habe ich einfach eine C # -Konsolenanwendung geschrieben, die das PowerShell-Skript aufruft.

So wie ich es mache, nennen wir in TeamCity ein Skript mit dem Namen: RemoteFile.ps1

Mit Skriptargumenten:% 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

Welches auf dem Remote-Server am angegebenen Speicherort vorhanden ist.

Diese Datei ruft dann Folgendes auf: Powershellwrapper.exe auch am angegebenen Speicherort (mein Skript verfügt über vier Parameter, die an das PowerShell-Skript übergeben werden müssen).

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.");
            }
        }
    }
}

Und das ruft mein Skript mit vier Parametern auf. Das Skript ist der erste Parameter plus drei Argumente. Im Wesentlichen wird hier also die Datei PowershellWrapper.exe anstelle des PowerShell-Skripts selbst ausgeführt, um die fehlerhaften Exit-Code-Nullen zu erfassen, und das vollständige Skript wird weiterhin im TeamCity-Protokoll angezeigt.

Ich hoffe das ergibt Sinn. Es funktioniert wie ein Zauber für uns.


Sollte nicht notwendig sein , scheint meine Antwort gleichwertig zu funktionieren, ohne dass Warzen im Skript und / oder in anderen Helfern erforderlich sind - dies ist in TeamCity leider nicht sehr gut dokumentiert. Siehe stackoverflow.com/questions/11647987/…
Ruben Bartelink
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.