Comment arrêter un script PowerShell sur la première erreur?


250

Je veux que mon script PowerShell s'arrête lorsqu'une des commandes que j'exécute échoue (comme set -edans bash). J'utilise à la fois les commandes Powershell ( New-Object System.Net.WebClient) et les programmes ( .\setup.exe).

Réponses:


303

$ErrorActionPreference = "Stop" vous permettra d'obtenir une partie du chemin (c'est-à-dire que cela fonctionne très bien pour les applets de commande).

Cependant, pour les EXE, vous devrez vous vérifier $LastExitCodeaprès chaque appel exe et déterminer si cela a échoué ou non. Malheureusement, je ne pense pas que PowerShell puisse aider ici car sous Windows, les EXE ne sont pas très cohérents sur ce qui constitue un code de sortie "succès" ou "échec". La plupart suivent la norme UNIX de 0 indiquant la réussite, mais pas tous. Découvrez la fonction CheckLastExitCode dans cet article de blog . Vous pourriez trouver cela utile.


3
Fonctionne-t-il $ErrorActionPreference = "Stop"pour des programmes bien comportés (qui retournent 0 en cas de succès)?
Andres Riofrio

14
Non, cela ne fonctionne pas du tout pour les EXE. Cela ne fonctionne que pour les applets de commande PowerShell qui s'exécutent en cours. C'est une sorte de douleur mais vous devez vérifier $ LastExitCode après chaque appel EXE, vérifier cela par rapport au code de sortie attendu et si ce test indique un échec, vous devez lancer pour terminer l'exécution du script, par exemple throw "$exe failed with exit code $LastExitCode"où $ exe n'est que le chemin vers l'EXE.
Keith Hill

2
Accepté car il contient des informations sur la façon de le faire fonctionner avec des programmes externes.
Andres Riofrio

4
J'ai mis une demande de fonctionnalité à ce sujet ici: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant

5
notez que psake a un commandlet appelé "exec" que vous pouvez utiliser pour encapsuler les appels vers des programmes externes avec une vérification de LastExitCode et afficher une erreur (et arrêter, si vous le souhaitez)
enorl76

68

Vous devriez pouvoir accomplir ceci en utilisant l'instruction $ErrorActionPreference = "Stop"au début de vos scripts.

Le paramètre par défaut de $ErrorActionPreferenceest Continue, c'est pourquoi vous voyez vos scripts continuer après que des erreurs se soient produites.


21
Cela n'affecte pas les programmes, uniquement les applets de commande.
Joey

18

Malheureusement, en raison d'applets de commande boguées comme New-RegKey et Clear-Disk , aucune de ces réponses ne suffit. Je me suis actuellement installé sur les lignes suivantes en haut de tout script PowerShell pour maintenir ma santé mentale.

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'

puis tout appel natif reçoit ce traitement:

native_call.exe
$native_call_success = $?
if (-not $native_call_success)
{
    throw 'error making native call'
}

Ce modèle d'appel natif devient lentement assez courant pour moi que je devrais probablement chercher des options pour le rendre plus concis. Je suis toujours un débutant PowerShell, donc les suggestions sont les bienvenues.


14

Vous avez besoin d'une gestion des erreurs légèrement différente pour les fonctions PowerShell et pour appeler des exes, et vous devez être sûr de dire à l'appelant de votre script qu'il a échoué. En s'appuyant sur Execla bibliothèque Psake, un script qui a la structure ci-dessous s'arrêtera sur toutes les erreurs, et est utilisable comme modèle de base pour la plupart des scripts.

Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"


# Taken from psake https://github.com/psake/psake
<#
.SYNOPSIS
  This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
  to see if an error occcured. If an error is detected then an exception is thrown.
  This function allows you to run command-line programs without having to
  explicitly check the $lastexitcode variable.
.EXAMPLE
  exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
#>
function Exec
{
    [CmdletBinding()]
    param(
        [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
        [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd)
    )
    & $cmd
    if ($lastexitcode -ne 0) {
        throw ("Exec: " + $errorMessage)
    }
}

Try {

    # Put all your stuff inside here!

    # powershell functions called as normal and try..catch reports errors 
    New-Object System.Net.WebClient

    # call exe's and check their exit code using Exec
    Exec { setup.exe }

} Catch {
    # tell the caller it has all gone wrong
    $host.SetShouldExit(-1)
    throw
}

Invoquer par exemple:, Exec { sqlite3.exe -bail some.db "$SQL" }le -bailprovoque une erreur car il essaie de l'interpréter comme un paramètre d'applet de commande? Envelopper les choses entre guillemets ne semble pas fonctionner. Des idées?
rcoup

Existe-t-il un moyen de placer ce code quelque part au centre de sorte que vous puissiez faire une sorte de #include lorsque vous souhaitez utiliser la méthode Exec?
NickG

10

Une légère modification de la réponse de @alastairtree:

function Invoke-Call {
    param (
        [scriptblock]$ScriptBlock,
        [string]$ErrorAction = $ErrorActionPreference
    )
    & @ScriptBlock
    if (($lastexitcode -ne 0) -and $ErrorAction -eq "Stop") {
        exit $lastexitcode
    }
}

Invoke-Call -ScriptBlock { dotnet build . } -ErrorAction Stop

Les principales différences sont les suivantes:

  1. il utilise le verbe-nom (imitation Invoke-Command)
  2. implique qu'il utilise l' opérateur d'appel sous les couvertures
  3. imite le -ErrorAction comportement des applets de commande intégrées
  4. se termine avec le même code de sortie plutôt que de lancer une exception avec un nouveau message

1
Comment passez-vous les paramètres / variables? par exempleInvoke-Call { dotnet build $something }
Michael Blake

1
@MichaelBlake, la question que vous posez est si juste, autoriser un passage des paramètres rendrait cette approche dorée. J'inspectais adamtheautomator.com/pass-hashtables-invoke-command-argument pour régler la Invoke-Appel pour soutenir pass-through params. Si je réussis, je le posterai comme une autre réponse ici.
Maxim V. Pavlov

Pourquoi utilisez-vous l'opérateur d'éclaboussure pendant l'appel? Qu'est-ce que ça vous apporte? & @ScriptBlocket & $ScriptBlocksemblent faire la même chose. N'ont pas été en mesure de google quelle est la différence dans ce cas
pinkfloydx33

6

Je suis nouveau sur PowerShell, mais cela semble être le plus efficace:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}

2

Je suis venu ici à la recherche de la même chose. $ ErrorActionPreference = "Stop" tue mon shell immédiatement quand je préfère voir le message d'erreur (pause) avant qu'il ne se termine. Se replier sur mes sensibilités batch:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

J'ai trouvé que cela fonctionne à peu près de la même manière pour mon script PS1 particulier:

Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}

0

La redirection stderrvers stdoutsemble également faire l'affaire sans aucune autre commande / encapsuleur scriptblock bien que je ne trouve pas d'explication pourquoi cela fonctionne de cette façon ..

# test.ps1

$ErrorActionPreference = "Stop"

aws s3 ls s3://xxx
echo "==> pass"

aws s3 ls s3://xxx 2>&1
echo "shouldn't be here"

Cela produira ce qui suit comme prévu (la commande aws s3 ...retourne $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.