web-dev-qa-db-fra.com

Pourquoi mes codes de sortie PowerShell sont-ils toujours "0"?

J'ai un script PowerShell comme suit

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

D'après ce que je suis amené à croire, une Throw non capturée donnera un code de sortie de 1, mais malheureusement, TeamCity dit le contraire.

[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

Il peut également être important de noter que mon Execution Mode est défini sur Execute .ps1 script with "-File" argument.

J'ai essayé de le changer en Put script into PowerShell stdin with "-Command -" arguments, mais cela a échoué avec un code de sortie de 1, même après des tests réussis. Je suis sûr que l'exécuter en tant que -File sera le bon moyen.

Si j'ouvre le script situé à C:\BuildAgent\work\e903de7564e599c8\build.ps1 et que je l'exécute manuellement dans CMD, il fait la même chose ... c'est-à-dire que les tests échouant échouent et que %errorlevel% est toujours 0.

Pourtant, si je l'exécute dans PowerShell et que j'appelle $LASTEXITCODE, il renvoie le bon code à chaque fois.

68
Chase Florell

Il s'agit d'un problème connu avec PowerShell. L'exécution d'un script avec -file renvoie un code de sortie de 0 alors que ce n'est pas le cas.

(Mise à jour: les liens ci-dessous ne fonctionnent plus. Recherchez ou signalez ce problème sur PowerShell: Hot (1454 idées) - Windows Server)

Étant donné que l'utilisation de -command ne fonctionnait pas pour vous, vous pouvez essayer d'ajouter une interruption en haut du script:

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

Ce qui précède devrait donner un code de sortie approprié lorsqu'une exception est levée.

90
Kevin Richardson

J'avais exactement ce problème lorsque je travaillais avec le -file, mais pour une raison quelconque, la syntaxe d'interruption ou la syntaxe de sortie fournie par Kevin ne fonctionnait pas dans mon scénario.

Je ne sais pas pourquoi, mais juste au cas où quelqu'un d'autre aurait le même problème, j'ai utilisé la syntaxe ci-dessous et cela a fonctionné pour moi:

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

Jusqu'à ce que (vraisemblablement) ce soit fermé comme dup de ma réponse personnelle à une question plus ancienne , je résumerai ici la solution la plus propre:

  • La plupart des autres réponses impliquent d'émettre quelque chose dans la variable stderr à partir du bit PowerShell. Cela peut être accompli directement avec TeamCity via l'option Format stderr en tant que (définissez-la sur Error au lieu de la valeur par défaut, qui est Warning).

  • Cependant, il est également nécessaire d’activer le message "Echec de la construction si: ... Un message d’erreur est consigné par le (sic) constructeur de la construction" sous "Conditions de défaillance" (si l’un des autres les réponses fonctionnent pour vous, cela sera probablement déjà activé, mais IME, il est très facile à oublier!)

13
Ruben Bartelink

Aucune de ces options ne fonctionnait pour moi dans mon script PowerShell pour une raison quelconque. J'ai passé des heures dessus.

Pour moi, la meilleure option consistait à placer une couche entre TeamCity et PowerShell. J'ai donc simplement écrit une application console C # qui appelle le script PowerShell.

Comme je le fais, dans TeamCity, nous appelons un script nommé: RemoteFile.ps1

Avec les arguments de script:% 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

Qui existe sur le serveur distant à l'emplacement spécifié.

Ensuite, ce fichier appelle ceci: powershellwrapper.exe également à l'emplacement spécifié (mon script a quatre paramètres à transmettre au script 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 [email protected] if error occurs.");
            }
        }
    }
}

Et cela appelle mon script avec quatre paramètres. Le script étant le premier paramètre, plus trois arguments. Donc, essentiellement, ce qui se passe ici, c'est que j'exécute PowershellWrapper.exe au lieu du script PowerShell lui-même pour capturer les codes de sortie erronés 0 et qu'il continue de signaler le script complet en cours d'exécution dans le journal TeamCity.

J'espère que cela à du sens. Cela fonctionne comme un charme pour nous.

1
mumbles

L'utilisation de -ErrorAction stop sur une commande renvoie un code de sortie 1 par défaut et l'affiche également dans TeamCity sans ajouter de condition d'échec. Nous allons maintenant implémenter ce comportement par défaut pour chaque commande PowerShell utilisant $ErrorActionPreference = "Stop";.

0
Bart VdA