Wie stoppe ich ein PowerShell-Skript beim ersten Fehler?

250

Ich möchte, dass mein PowerShell-Skript beendet wird, wenn einer der von mir ausgeführten Befehle fehlschlägt (wie set -ein Bash). Ich verwende sowohl Powershell-Befehle ( New-Object System.Net.WebClient) als auch Programme ( .\setup.exe).

Andres Riofrio
quelle

Antworten:

303

$ErrorActionPreference = "Stop" Sie erhalten einen Teil des Weges dorthin (dh dies funktioniert hervorragend für Cmdlets).

Für EXEs müssen Sie $LastExitCodesich jedoch nach jedem Exe-Aufruf selbst überprüfen und feststellen, ob dies fehlgeschlagen ist oder nicht. Leider glaube ich nicht, dass PowerShell hier helfen kann, da EXEs unter Windows nicht besonders konsistent sind, was einen Exit-Code für "Erfolg" oder "Fehler" ausmacht. Die meisten folgen dem UNIX-Standard 0, was auf Erfolg hinweist, aber nicht alle. Überprüfen Sie die CheckLastExitCode-Funktion in diesem Blog-Beitrag . Vielleicht finden Sie es nützlich.

Keith Hill
quelle
3
Funktioniert es $ErrorActionPreference = "Stop"für gut erzogene Programme (die bei Erfolg 0 zurückgeben)?
Andres Riofrio
14
Nein, es funktioniert überhaupt nicht für EXEs. Dies funktioniert nur für PowerShell-Cmdlets, die in Bearbeitung ausgeführt werden. Es ist eine Art Schmerz, aber Sie müssen $ LastExitCode nach jedem EXE-Aufruf überprüfen, dies mit dem erwarteten Exit-Code vergleichen und wenn dieser Test einen Fehler anzeigt, müssen Sie werfen, um die Ausführung des Skripts zu beenden, z. B. throw "$exe failed with exit code $LastExitCode"wo $ exe nur der Pfad zu ist die EXE.
Keith Hill
2
Akzeptiert, da es Informationen darüber enthält, wie es mit externen Programmen funktioniert.
Andres Riofrio
4
Ich habe hier eine Feature-Anfrage dazu gestellt: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant
5
Beachten Sie, dass psake ein Commandlet namens "exec" hat, mit dem Sie Aufrufe an externe Programme mit einer Überprüfung auf LastExitCode umbrechen und einen Fehler anzeigen (und gegebenenfalls stoppen) können
enorl76
68

Sie sollten dies erreichen können, indem Sie die Anweisung $ErrorActionPreference = "Stop"am Anfang Ihrer Skripte verwenden.

Die Standardeinstellung $ErrorActionPreferenceist Continue, weshalb Ihre Skripte nach Auftreten von Fehlern weiterhin ausgeführt werden.

goric
quelle
21
Dies betrifft keine Programme, sondern nur Cmdlets.
Joey
18

Leider reicht aufgrund fehlerhafter Cmdlets wie New-RegKey und Clear-Disk keine dieser Antworten aus. Ich habe mich derzeit in den folgenden Zeilen oben in einem Powershell-Skript niedergelassen, um meine geistige Gesundheit zu erhalten.

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

und dann bekommt jeder native Anruf diese Behandlung:

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

Dieses native Anrufmuster wird für mich langsam so häufig, dass ich wahrscheinlich nach Optionen suchen sollte, um es prägnanter zu gestalten. Ich bin immer noch ein Powershell-Neuling, daher sind Vorschläge willkommen.

aggieNick02
quelle
14

Sie benötigen eine etwas andere Fehlerbehandlung für Powershell-Funktionen und zum Aufrufen von Exes, und Sie müssen dem Aufrufer Ihres Skripts unbedingt mitteilen, dass es fehlgeschlagen ist. Aufbauend auf Execder Bibliothek Psake stoppt ein Skript mit der folgenden Struktur alle Fehler und kann als Basisvorlage für die meisten Skripte verwendet werden.

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
quelle
Unter Berufung auf zB Exec { sqlite3.exe -bail some.db "$SQL" }, die -baileinen Fehler verursacht , da er es als Cmdlets Parameter zu interpretieren versucht? Dinge in Anführungszeichen zu setzen scheint nicht zu funktionieren. Irgendwelche Ideen?
Gruppe
Gibt es eine Möglichkeit, diesen Code an einer zentralen Stelle zu platzieren, damit Sie eine Art #include ausführen können, wenn Sie die Exec-Methode verwenden möchten?
NickG
10

Eine geringfügige Änderung der Antwort von @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

Die Hauptunterschiede hier sind:

  1. es verwendet das Verb-Nomen (Nachahmung Invoke-Command)
  2. impliziert, dass es die verwendet Anrufbetreiber unter der Decke verwendet wird
  3. ahmt nach -ErrorAction Verhalten von eingebauten Cmdlets nach
  4. Beendet mit demselben Exit-Code, anstatt eine Ausnahme mit einer neuen Nachricht auszulösen
Lucas
quelle
1
Wie übergeben Sie Parameter / Variablen? zBInvoke-Call { dotnet build $something }
Michael Blake
1
@MichaelBlake Ihre Anfrage ist so richtig, dass das Durchlassen eines Params diesen Ansatz zu Gold machen würde. Ich inspiziere adamtheautomator.com/pass-hashtables-invoke-command-argument , um den Invoke-Call so anzupassen, dass der Parameter-Pass-Through unterstützt wird. Wenn es mir gelingt, werde ich es hier als weitere Antwort veröffentlichen.
Maxim V. Pavlov
Warum verwenden Sie den Splatting-Operator während des Aufrufs? Was bringt dir das? & @ScriptBlockund & $ScriptBlockscheinen das gleiche zu tun. Ich konnte nicht googeln, was der Unterschied in diesem Fall ist
pinkfloydx33
6

Ich bin neu in Powershell, aber das scheint am effektivsten zu sein:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
Peter L.
quelle
2

Ich bin hierher gekommen, um dasselbe zu suchen. $ ErrorActionPreference = "Stop" beendet meine Shell sofort, wenn ich die Fehlermeldung (Pause) lieber sehen möchte, bevor sie beendet wird. Auf meine Batch-Sensibilität zurückgreifen:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Ich fand, dass dies für mein spezielles ps1-Skript ziemlich gleich funktioniert:

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

Das Umleiten stderrnach stdoutscheint den Trick auch ohne andere Befehle / Scriptblock-Wrapper zu tun, obwohl ich keine Erklärung dafür finden kann, warum es so funktioniert.

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Dies gibt wie erwartet Folgendes aus (der Befehl gibt aws s3 ...zurück $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
ubi
quelle