Wann sollte ich Write-Error vs. Throw verwenden? Beendende oder nicht endende Fehler

145

Als ich mir ein Get-WebFile-Skript auf PoshCode ( http://poshcode.org/3226) ansah, bemerkte ich diese für mich seltsame Erfindung :

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

Was ist der Grund dafür im Gegensatz zu den folgenden?

$URL_Format_Error = [string]"..."
Throw $URL_Format_Error

Oder noch besser:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

Soweit ich weiß, sollten Sie Write-Error für nicht terminierende Fehler und Throw für terminierende Fehler verwenden. Daher sollten Sie meines Erachtens keinen Write-Error gefolgt von Return verwenden. Ist da ein Unterschied?

Bill Barry
quelle
4
Was meinst du? Wenn Write_error zulässt, dass das Skript fortgesetzt wird, ist es sehr verständlich, nach Write-Error eine return-Anweisung zu haben. Der Fehler wurde ausgeschrieben und Sie kehren zu dem Code zurück, der die Funktion zuerst aufgerufen hat. Da Throw zum Beenden von Fehlern dient, wird es automatisch beendet, so dass eine return-Anweisung in einer throw-Deklaration unbrauchbar ist
Gisli
1
@Gisli: Es ist wichtig zu beachten , dass returnsie nicht in dem an den Aufrufer zurückgeben processBlock einer (Fortgeschrittene) Funktion; Stattdessen wird mit dem nächsten Eingabeobjekt in der Pipeline fortgefahren. Dies ist in der Tat das typische Szenario für die Erzeugung nicht beendender Fehler: Wenn die Verarbeitung weiterer Eingabeobjekte noch möglich ist.
mklement0
1
Beachten Sie, dass Throwein Skript-Abschlussfehler generiert wird, der nicht mit den Anweisungs-Abschlussfehlern identisch ist, die beispielsweise durch Get-Item -NoSuchParameteroder ausgelöst werden 1 / 0.
mklement0

Antworten:

188

Write-Errorsollte verwendet werden, wenn Sie den Benutzer über einen unkritischen Fehler informieren möchten. Standardmäßig wird lediglich eine Fehlermeldung in roter Schrift auf der Konsole gedruckt. Es verhindert nicht, dass eine Pipeline oder eine Schleife fortgesetzt wird. Throwerzeugt andererseits einen sogenannten Abbruchfehler. Wenn Sie throw verwenden, werden die Pipeline und / oder die Stromschleife beendet. Tatsächlich wird die gesamte Ausführung beendet, es sei denn, Sie verwenden eine trapoder eine try/catchStruktur, um den Beendigungsfehler zu behandeln.

Es ist eine Sache zu beachten, wenn Sie festgelegt $ErrorActionPreferencezu"Stop" verwenden , Write-Errorwird es einen Abschlussfehler erzeugen .

In dem Skript, mit dem Sie verlinkt haben, finden wir Folgendes:

if ($url.Contains("http")) {
       $request = [System.Net.HttpWebRequest]::Create($url)
}
else {
       $URL_Format_Error = [string]"Connection protocol not specified. Recommended action: Try again using protocol (for example 'http://" + $url + "') instead. Function aborting..."
       Write-Error $URL_Format_Error
    return
   }

Es sieht so aus, als wollte der Autor dieser Funktion die Ausführung dieser Funktion stoppen und eine Fehlermeldung auf dem Bildschirm anzeigen, wollte aber nicht, dass das gesamte Skript nicht mehr ausgeführt wird. Der Skriptautor hätte verwenden können, throwdies würde jedoch bedeuten, dass Sie try/catchbeim Aufrufen der Funktion a verwenden müssten .

returnbeendet den aktuellen Bereich, der eine Funktion, ein Skript oder ein Skriptblock sein kann. Dies lässt sich am besten mit Code veranschaulichen:

# A foreach loop.
foreach ( $i in  (1..10) ) { Write-Host $i ; if ($i -eq 5) { return } }

# A for loop.
for ($i = 1; $i -le 10; $i++) { Write-Host $i ; if ($i -eq 5) { return } }

Ausgabe für beide:

1
2
3
4
5

Ein Gotcha hier verwendet returnmit ForEach-Object. Die Verarbeitung wird nicht wie erwartet unterbrochen.

Mehr Informationen:

Andy Arismendi
quelle
Ok, also Throw stoppt alles, Write-Error + Return stoppt nur die aktuelle Funktion.
Bill Barry
@ BillBarry Ich habe meine Antwort ein wenig mit einer Erklärung von aktualisiert return.
Andy Arismendi
Was ist mit Write-Error, gefolgt von exit (1), um sicherzustellen, dass der entsprechende Fehlercode an das Betriebssystem zurückgegeben wird? Ist das jemals angemessen?
Pabrams
19

Der Hauptunterschied zwischen dem Write-Error cmdlet und dem throw Schlüsselwort in Power ist , dass erstere einfach druckt einen Text auf den Standardfehlerstrom (Stderr) , während die letzteren tatsächlich beenden die Verarbeitung des laufenden Befehls oder eine Funktion, welche dann abgewickelt von PowerShell durch Senden von Informationen über den Fehler an die Konsole.

Sie können das unterschiedliche Verhalten der beiden in den von Ihnen bereitgestellten Beispielen beobachten:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

In diesem Beispiel wurde das returnSchlüsselwort hinzugefügt, um die Ausführung des Skripts explizit zu stoppen, nachdem die Fehlermeldung an die Konsole gesendet wurde. Im zweiten Beispiel ist das returnSchlüsselwort hingegen nicht erforderlich, da die Beendigung implizit erfolgt durch throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Enrico Campidoglio
quelle
2
Wenn Sie $ ErrorActionPreference = "Stop" haben, beendet Write-Error auch den Prozess.
Michael Freidgeim
4
Gute Informationen, aber während der PowerShell-Fehlerstrom dem textbasierten stderr-Stream in anderen Shells analog ist , enthält er wie alle PowerShell-Streams Objekte , nämlich Instanzen, die standardmäßig in der automatischen Sammlung erfasst werden ( enthält den neuesten Fehler). Selbst wenn Sie nur eine Zeichenfolge verwenden , wird diese Zeichenfolge in eine Instanz eingeschlossen. [System.Management.Automation.ErrorRecord]$Error$Error[0]Write-Error[System.Management.Automation.ErrorRecord]
mklement0
16

Wichtig : Es gibt 2 Arten von Beendigungsfehlern , die in den aktuellen Hilfethemen leider zusammengeführt werden :

  • Anweisung -terminating Fehler, wie durch cmdlets in bestimmten Situationen nicht behebbaren gemeldet und durch Ausdrücke in dem eine Ausnahme .NET / a PS Laufzeitfehler auftritt; nur der Anweisung wird beendet und die Skriptausführung wird standardmäßig fortgesetzt .

  • Skript- terminierende Fehler (genauer: Runspace abbreche ), entweder getriggert durch Throwoder durch eine der anderen Fehlertypen über fehler Aktion Präferenz-Variable / Parameterwert eskaliert Stop.
    Sofern sie nicht abgefangen werden, beenden sie den aktuellen Runspace (Thread). Das heißt, sie beenden nicht nur das aktuelle Skript, sondern gegebenenfalls auch alle Aufrufer.

Eine umfassende Übersicht über die Fehlerbehandlung von PowerShell finden Sie in dieser GitHub-Dokumentation .

Der Rest dieses Beitrags konzentriert sich auf das Nichtbeenden und nicht auf Anweisungen endende Fehler.


Um die vorhandenen hilfreichen Antworten mit einem Fokus auf den Kern der Frage zu ergänzen: Wie wählen Sie aus , ob eine Aussage beendet werden soll? oder ein nichtbeendender Fehler werden soll ?

Cmdlet-Fehlerberichterstattung enthält hilfreiche Richtlinien. Lassen Sie mich eine pragmatische Zusammenfassung versuchen :

Die allgemeine Idee hinter nicht-Abschlussfehler ist „fehlertolerant“ Verarbeitung von großen Eingangssätzen zu ermöglichen : Ausfall eines verarbeiten Teilmenge der Eingangsobjekte sollten nicht (standardmäßig) Abbruch der - möglicherweise mit langer Laufzeit - Prozess als Ganzes , So können Sie die Fehler überprüfen und nur die fehlgeschlagenen erneut verarbeiten später Objekte erneut verarbeiten - wie über die in der automatischen Variablen gesammelten gemeldet $Error.

  • Bericht a NON-TERMINATING- Fehler, wenn Ihr Cmdlet / Ihre erweiterte Funktion:

    • akzeptiert MEHRERE Eingabeobjekte über die Pipeline-Eingabe und / oder Array-Wert-Parameter AND
    • Bei SPEZIFISCHEN Eingabeobjekten treten Fehler auf UND auf
    • Diese Fehler verhindern NICHT DIE VERARBEITUNG VON WEITEREN Eingabeobjekten IM PRINZIP ( situativ sind möglicherweise keine Eingabeobjekte mehr vorhanden und / oder vorherige Eingabeobjekte wurden möglicherweise bereits erfolgreich verarbeitet).
      • Verwenden Sie $PSCmdlet.WriteError()in erweiterten Funktionen, um einen nicht terminierenden Fehler zu melden (dies Write-Errorführt leider nicht dazu $?, dass er $Falseim Bereich des Aufrufers festgelegt wird - siehe dieses GitHub-Problem ).
      • Behandlung eines nicht terminierenden Fehlers: $?Gibt an, ob der letzte Befehl mindestens einen nicht terminierenden Fehler gemeldet hat .
        • Somit $?ist $Falsekann entweder bedeuten , daß eine beliebige (nicht leer) Subset von Eingabeobjekten wurden nicht korrekt verarbeitet, möglicherweise das gesamte Set.
        • Die $ErrorActionPreferenceVoreinstellungsvariable und / oder der allgemeine Cmdlet-Parameter -ErrorActionkönnen das Verhalten von nicht terminierenden Fehlern (nur) in Bezug auf das Fehlerausgabeverhalten und die Frage ändern, ob nicht terminierende Fehler zu Skript -terminierenden Fehlern eskaliert werden sollen.
  • In allen anderen Fällen einen STATEMENT-TERMINATING- Fehler melden .

    • Insbesondere wenn ein Fehler in einem Cmdlet / einer erweiterten Funktion auftritt, die nur ein EINZEL- oder NEIN-Eingabeobjekt akzeptiert und NO- oder EINZEL-Ausgabeobjekt ausgibt oder nur Parametereingaben verwendet und die angegebenen Parameterwerte eine sinnvolle Operation verhindern.
      • In erweiterten Funktionen müssen Sie verwenden $PSCmdlet.ThrowTerminatingError() , um einen Anweisungsbeendigungsfehler zu generieren.
      • Beachten Sie, dass das ThrowSchlüsselwort im Gegensatz dazu einen Skript-Abschlussfehler generiert , der das gesamte Skript abbricht (technisch: der aktuelle Thread ).
      • Behandeln eines Anweisungsbeendigungsfehlers: Es kann ein try/catchHandler oder eine trapAnweisung verwendet werden (die nicht mit nicht beendenden Fehlern verwendet werden kann). Beachten Sie jedoch, dass selbst Anweisungsbeendigungsfehler standardmäßig nicht die Ausführung des restlichen Skripts verhindern. Wie bei nicht terminierenden Fehlern wird $?angezeigt, $Falseob die vorherige Anweisung einen Anweisungsbeendigungsfehler ausgelöst hat.

Leider spielen nicht alle PowerShell-eigenen Cmdlets nach diesen Regeln :

  • Obwohl es unwahrscheinlich ist, dass ein Fehler New-TemporaryFileauftritt , meldet (PSv5 +) einen nicht terminierenden Fehler, wenn er fehlschlägt, obwohl keine Pipeline-Eingabe akzeptiert und nur ein Ausgabeobjekt erstellt wird. Dies wurde jedoch ab mindestens PowerShell [Core] 7.0 korrigiert: siehe GitHub Problem .

  • Resume-JobIn der Hilfe wird behauptet, dass das Übergeben eines nicht unterstützten Jobtyps (z. B. eines mit erstellten Jobs Start-Job, der nicht unterstützt wird, da er Resume-Jobnur für Workflow- Jobs gilt) einen Beendigungsfehler verursacht. Dies gilt jedoch nicht für PSv5.1.

mklement0
quelle
8

Write-Errorermöglicht dem Verbraucher der Funktion, die Fehlermeldung mit -ErrorAction SilentlyContinue(alternativ -ea 0) zu unterdrücken . Während throwerfordert eintry{...} catch {..}

Um einen Versuch zu verwenden ... fangen Sie mit Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
quelle
Warum müssen Sie Write-Error überhaupt aufrufen, wenn Sie die Fehlermeldung unterdrücken möchten?
Michael Freidgeim
8

Ergänzung zu Andy Arismendis Antwort :

Ob Write-Error den Prozess beendet oder nicht, hängt von der$ErrorActionPreference Einstellung ab.

Für nicht triviale Skripte $ErrorActionPreference = "Stop"wird eine Einstellung empfohlen , die schnell fehlschlägt.

"Das Standardverhalten von PowerShell in Bezug auf Fehler, das darin besteht, bei Fehlern fortzufahren ... fühlt sich sehr VB6" On Error Resume Next "-ish an."

(von http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/ )

Allerdings macht es Write-ErrorAnrufe zu beenden.

Um Write-Error unabhängig von anderen Umgebungseinstellungen als nicht abschließenden Befehl zu verwenden, können Sie einen gemeinsamen Parameter -ErrorAction mit dem folgenden Wert verwenden Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Michael Freidgeim
quelle
0

Wenn Sie den Code richtig gelesen haben, sind Sie richtig. Das Beenden von Fehlern sollte verwendet throwwerden. Wenn Sie mit .NET-Typen arbeiten, ist es hilfreich, auch die .NET-Ausnahmekonventionen zu befolgen.

Yzorg
quelle