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).

Andres Riofrio
la source

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.

Keith Hill
la source
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.

goric
la source
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.

aggieNick02
la source
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
}
alastairtree
la source
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
Lucas
la source
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"}
Peter L
la source
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}
harvey263
la source
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
ubi
la source