- 23.05.2020
- 14 Minuten zum Lesen
-
- j
- f
- s
Fehlerbehandlung ist nur ein Teil des Lebens, wenn es darum geht, Code zu schreiben. Wir können oft überprüfen und validateconditions für erwartetes Verhalten. Wenn das Unerwartete passiert, wenden wir uns der Ausnahmebehandlung zu., Sie können problemlos Ausnahmen behandeln, die durch den Code anderer Personen generiert werden, oder Sie können Ihre eigenen Ausnahmen generieren, die andere verarbeiten können.
Hinweis
Die Originalversion dieses Artikels erschien auf dem Blog von @KevinMarquette. ThePowerShell Team dankt Kevin für den Austausch dieser Inhalte mit uns. Bitte schauen Sie sich seinen Blog an atPowerShellExplained.com.
Grundlegende Terminologie
Wir müssen einige grundlegende Begriffe behandeln, bevor wir in diese einsteigen.
Ausnahme
Eine Ausnahme ist wie ein Ereignis, das erstellt wird, wenn die normale Fehlerbehandlung das Problem nicht beheben kann.,Der Versuch, eine Zahl durch Null zu teilen oder der Speicher wird knapp, sind Beispiele für etwas, das anexception erzeugt. Manchmal erstellt der Autor des von Ihnen verwendeten Codes Ausnahmen für bestimmte Probleme, wenn sie auftreten.
Werfen und Fangen
Wenn eine Ausnahme auftritt, sagen wir, dass eine Ausnahme ausgelöst wird. Um eine ausgelöste Ausnahme zu behandeln, muss youneed sie abfangen. Wenn eine Ausnahme ausgelöst wird und nicht von etwas abgefangen wird, stoppt das Skript die Ausführung.
Der Aufrufstapel
Der Aufrufstapel ist die Liste der Funktionen, die sich gegenseitig aufgerufen haben., Wenn eine Funktion aufgerufen wird, wird sie dem Stapel oder dem Anfang der Liste hinzugefügt. Wenn die Funktion beendet wird oder zurückkehrt, wird sie entferntaus dem Stapel.
Wenn eine Ausnahme ausgelöst wird, wird dieser Aufrufstapel überprüft, damit ein Ausnahmebehandler eingreift.
Fehler beenden und nicht beenden
Eine Ausnahme ist im Allgemeinen ein Abschlussfehler. Eine ausgelöste Ausnahme wird entweder abgefangen oder beendet die aktuelle Ausführung. Standardmäßig wird ein nicht abschließender Fehler von Write-Error
generiert und fügt dem Ausgabestream einen Fehler hinzu, ohne eine Ausnahme auszulösen.,
Ich weise darauf hin, weil Write-Error
und andere nicht abschließende Fehler nicht diecatch
auslösen.
Schlucken einer Ausnahme
Dies ist, wenn Sie einen Fehler abfangen, nur um ihn zu unterdrücken. Tun Sie dies mit Vorsicht, da dies Probleme beim Roubleshooting sehr schwierig machen kann.
Grundlegende Befehlssyntax
Hier ist ein kurzer Überblick über die grundlegende Syntax für die Ausnahmebehandlung in PowerShell.
Throw
Um ein eigenes Ausnahmeereignis zu erstellen, werfen wir eine Ausnahme mit dem Schlüsselwort throw
.,
function Start-Something{ throw "Bad thing happened"}
Dies erzeugt eine Laufzeitausnahme, die ein Abschlussfehler ist. Es wird von einer catch
in acalling oder beendet das Skript mit einer solchen Nachricht.
Write-Error-ErrorAction Stop
Ich habe erwähnt, dass Write-Error
standardmäßig keinen Abschlussfehler auslöst. Wenn Sie-ErrorAction Stop
angeben, generiert Write-Error
einen Abschlussfehler, der mit einercatch
behandelt werden kann.,
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Vielen Dank an Lee Dailey für die Erinnerung an die Verwendung von -ErrorAction Stop
auf diese Weise.
Cmdlet-ErrorAction Stop
Wenn Sie -ErrorAction Stop
für eine erweiterte Funktion oder ein Cmdlet angeben, werden alle Write-Error
– Anweisungen in abschließende Fehler umgewandelt, die die Ausführung stoppen oder von einer catch
behandelt werden können.,
Start-Something -ErrorAction Stop
Try/Catch
Die Art und Weise, wie die Ausnahmebehandlung in PowerShell (und vielen anderen Sprachen) funktioniert, ist, dass Sie zuerst try
als Teil des Codes und wenn es einen Fehler auslöst, können Sie catch
es. Hier ist eine kurze Probe.
Dascatch
Skript wird nur ausgeführt, wenn ein Abschlussfehler vorliegt. Wenn die try
korrekt ausgeführt wird, dannes überspringt die catch
.,
Try/Finally –
Manchmal brauchen Sie nicht, um einen Fehler zu behandeln, müssen jedoch noch einige code auszuführen, wenn eine exceptionhappens oder nicht. Einfinally
Skript macht genau das.
Schauen Sie sich dieses Beispiel an:
Wenn Sie eine Ressource öffnen oder eine Verbindung herstellen, sollten Sie sie schließen. Wenn dieExecuteNonQuery()
eine Ausnahme auslöst, wird die Verbindung nicht geschlossen. Hier ist der gleiche code in ein try/finally
block.
In diesem Beispiel wird die Verbindung bei einem Fehler geschlossen. Es ist auch geschlossen, wenn es noerror gibt., Das Skript finally
wird jedes Mal ausgeführt.
Da Sie die Ausnahme nicht abfangen, wird sie immer noch im Aufrufstapel weitergegeben.
Try / Catch / Finally
Es ist vollkommen gültig, catch
und finally
zusammen zu verwenden. Die meiste Zeit werden Sie das eine oder verwendendie andere, aber Sie können Szenarien finden, in denen Sie beide verwenden.
$PSItem
Nachdem wir die Grundlagen aus dem Weg geräumt haben, können wir etwas tiefer graben.,
Imcatch
– Block befindet sich eine automatische Variable ($PSItem
oder$_
) vom TypErrorRecord
, die die Details zur Ausnahme enthält. Hier ist ein kurzer überblick über einige der keyproperties.
Für diese Beispiele habe ich einen ungültigen Pfad in ReadAllText
, um diese Ausnahme zu generieren.
::ReadAllText( '\\test\no\filefound.log')
PSItem.toString ()
Dies gibt Ihnen die sauberste Nachricht für die Protokollierung und allgemeine Ausgabe., ToString()
wird automatisch aufgerufen, wenn $PSItem
in einem String platziert ist.
catch{ Write-Output "Ran into an issue: $($PSItem.ToString())"}catch{ Write-Output "Ran into an issue: $PSItem"}
$PSItem.InvocationInfo
Diese Eigenschaft enthält zusätzliche Informationen, die von PowerShell über die Funktion oder das Skript gesammelt wurden, in dem die Ausnahme ausgelöst wurde. Hier ist die InvocationInfo
aus der Beispielausnahme, die Icreated.
Die wichtigen Details hier zeigen die ScriptName
, die Line
des Codes und die ScriptLineNumber
wo der Aufruf gestartet wurde.
$PSItem.,ScriptStackTrace
Diese Eigenschaft zeigt die Reihenfolge der Funktionsaufrufe an, die Sie zu dem Code geführt haben, in dem die Ausnahme generiert wurde.
Ich rufe nur Funktionen im selben Skript auf, aber dies würde die Aufrufe verfolgen, wenn Multiplescripts beteiligt wären.
$PSItem.Ausnahme
Dies ist die eigentliche Ausnahme, die ausgelöst wurde.
$PSItem.Ausnahme.Nachricht
Dies ist die allgemeine Nachricht, die die Ausnahme beschreibt und ein guter Ausgangspunkt ist, wenntroubleshooting. Die meisten Ausnahmen haben eine Standardnachricht, können aber auch auf etwas Benutzerdefiniertes gesetzt werden, wenn die Ausnahme ausgelöst wird.,
PS> $PSItem.Exception.MessageException calling "ReadAllText" with "1" argument(s): "The network path was not found."
Dies ist auch die Meldung, die beim Aufruf von $PSItem.ToString()
zurückgegeben wird, wenn auf derErrorRecord
kein Satz vorhanden war.
$PSItem.Ausnahme.InnerException
Ausnahmen können innere Ausnahmen enthalten. Dies ist oft der Fall, wenn der Code, den Sie aufrufenfängt eine Ausnahme und löst eine andere Ausnahme aus. Die ursprüngliche Ausnahme wird darin platziertdie neue Ausnahme.
PS> $PSItem.Exception.InnerExceptionMessageThe network path was not found.
Ich werde dies später noch einmal wiederholen, wenn ich über das erneute Auslösen von Ausnahmen spreche.
$PSItem.Ausnahme.,StackTrace
Dies ist die StackTrace
für die Ausnahme. Ich habe oben eine ScriptStackTrace
, aber diese ist für die Aufrufe von verwaltetem Code.
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)at CallSite.Target(Closure , CallSite , Type , String )
Sie erhalten diesen Stack-Trace nur, wenn das Ereignis aus verwaltetem Code ausgelöst wird. Ich rufe ein .NETFramework funktioniert direkt, so dass wir in diesem Beispiel nur sehen können. Wenn Sie sich einen Stack-Trace ansehen, suchen Sie im Allgemeinen, wo Ihr Code stoppt und die Systemaufrufe beginnen.
Arbeiten mit Ausnahmen
Ausnahmen haben mehr als die grundlegenden Syntax-und Ausnahmeeigenschaften.,
Typisierte Ausnahmen abfangen
Sie können mit den Ausnahmen, die Sie abfangen, selektiv sein. Ausnahmen haben einen Typ und Sie können spezifizierendie Art der Ausnahme, die Sie fangen möchten.
Der Ausnahmetyp wird für jeden catch
– Block überprüft, bis einer gefunden wird, der mit Ihrem übereinstimmt exception.It es ist wichtig zu erkennen, dass Ausnahmen von anderen Ausnahmen erben können. Im obigen Beispiel erbtFileNotFoundException
von IOException
. Wenn also die IOException
zuerst war, dann würde es stattdessen aufgerufen werden., Es wird nur ein Catch-Block aufgerufen, auch wenn mehrere Übereinstimmungen vorliegen.
Wenn wir eine System.IO.PathTooLongException
hätten, würde die IOException
übereinstimmen, aber wenn wir eineInsufficientMemoryException
dann würde nichts es fangen und es würde den Stapel ausbreiten.
Fangen mehrere Typen auf einmal
Es ist möglich, mehrere catch-exception-Typen mit dem gleichen catch
– Anweisung.
Vielen Dank /u/Sheppard_Ra
, dass Sie diesen Zusatz vorgeschlagen haben.
Typisierte Ausnahmen auslösen
Sie können typisierte Ausnahmen in PowerShell auslösen., Anstatt throw
mit einer Zeichenfolge aufzurufen:
throw "Could not find: $path"
Verwenden Sie einen Ausnahmebeschleuniger wie diesen:
throw "Could not find: $path"
Sie müssen jedoch eine Nachricht angeben, wenn Sie dies auf diese Weise tun.
Sie können auch eine neue Instanz einer Ausnahme erstellen, die ausgelöst werden soll. Die Nachricht ist optional, wenn Sie dies tun, da das System Standardmeldungen für alle integrierten Ausnahmen enthält.
Wenn Sie PowerShell 5.0 oder höher nicht verwenden, müssen Sie den älteren New-Object
Ansatz verwenden.,
Wenn Sie eine typisierte Ausnahme verwenden, können Sie (oder andere) die Ausnahme nach dem Typ abfangen, wie im vorherigen Abschnitt erwähnt.
Write-Error-Exception
Wir können diese typisierten Ausnahmen zu Write-Error
hinzufügen und wir können immer noch catch
die Fehler nach exceptiontype. Verwenden Sie Write-Error
wie in diesen Beispielen:
Dann können wir es so abfangen:
catch { Write-Log $PSItem.ToString()}
Die große Liste der. NET-Ausnahmen
Ich habe eine Master-Liste mit Hilfe der Reddit/r/PowerShell-Community zusammengestellt, die enthälthundreds von.,NET Ausnahmen zu ergänzen diesen Beitrag.
- Die große Liste der. NET-Ausnahmen
Ich beginne damit, diese Liste nach Ausnahmen zu durchsuchen, die das Gefühl haben, dass sie gut zu mysituation passen. Sie sollten versuchen, Ausnahmen im Namespace base System
zu verwenden.
Ausnahmen sind Objekte
Wenn Sie viele typisierte Ausnahmen verwenden, denken Sie daran, dass es sich um Objekte handelt. Verschiedene exceptionshave verschiedene Konstruktoren und Eigenschaften., Wenn wir uns die FileNotFoundExceptiondocumentation für System.IO.FileNotFoundException
, sehen wir, dass wir eine Nachricht und einen Dateipfad übergeben können.
::new("Could not find file", $path)
Und es hat eine FileName
Eigenschaft, die diesen Dateipfad verfügbar macht.
catch { Write-Output $PSItem.Exception.FileName}
Sie sollten die.NET-Dokumentation für andere Konstruktoren und Objekteigenschaften konsultieren.
Eine Ausnahme erneut auslösen
Wenn alles, was Sie in Ihrem catch
– Block tun werden, throw
ist die gleiche Ausnahme, dann nicht catch
es., Sie sollten nur catch
eine Ausnahme, die Sie behandeln oder eine Aktion ausführen möchten, wenn es passiert.
Es gibt Zeiten, in denen Sie eine Aktion für eine Ausnahme ausführen möchten, aber die Ausnahme erneut auslösen möchten, damit Sie damit umgehen können. Wir könnten eine Nachricht schreiben oder das Problem in der Nähe der Stelle protokollieren, an der wir es entdecken, aber das Problem weiter oben im Stapel behandeln.
catch{ Write-Log $PSItem.ToString() throw $PSItem}
Interessanterweise können wir throw
innerhalb der catch
aufrufen und die currentexception erneut auslösen.,
catch{ Write-Log $PSItem.ToString() throw}
Wir möchten die Ausnahme erneut auslösen, um die ursprünglichen Ausführungsinformationen wie Quellskript und Zeilennummer beizubehalten. Wenn wir an dieser Stelle eine neue Ausnahme auslösen, wird ausgeblendet, wo die Ausnahme gestartet wurde.
Erneutes Auslösen einer neuen Ausnahme
Wenn Sie eine Ausnahme abfangen, aber eine andere auslösen möchten, sollten Sie die originalexception in die neue verschachteln. Auf diese Weise kann jemand auf dem Stapel als$PSItem.Exception.InnerException
darauf zugreifen.
catch{ throw ::new('Could not access field',$PSItem.Exception)}
$PSCmdlet.,ThrowTerminatingError ()
Eine Sache, die ich an der Verwendung von throw
für Raw-Ausnahmen nicht mag, ist, dass die Fehlermeldung bei der throw
– Anweisung angezeigt wird und angibt, dass sich in dieser Zeile das Problem befindet.
Wenn mir die Fehlermeldung mitteilt, dass mein Skript defekt ist, weil ich throw
in Zeile 31 aufgerufen habe, wird eine Fehlermeldung angezeigt, die Benutzer Ihres Skripts sehen können. Es sagt ihnen nichts Nützliches.
Dexter Dhami wies darauf hin, dass ich ThrowTerminatingError()
, um das zu korrigieren.,
Wenn wir annehmen, dass ThrowTerminatingError()
in einer Funktion namens Get-Resource
aufgerufen wurde, dannDies ist der Fehler, den wir sehen würden.
Sehen Sie, wie es auf die Funktion Get-Resource
als Ursache des Problems verweist? Das sagt dem Benutzer etwas Nützliches.
Da eine ErrorRecord
, können wir auch ThrowTerminatingError
auf diese Weise erneut werfen.,
catch{ $PSCmdlet.ThrowTerminatingError($PSItem)}
Dadurch wird die Fehlerquelle in das Cmdlet geändert und die Interna Ihrer Funktion vor den Benutzern Ihres Cmdlet ausgeblendet.
Try can create terminating errors
Kirk Munro weist darauf hin, dass einige Ausnahmen nur Fehler beenden, wenn sie in einemtry/catch
– Block ausgeführt werden. Hier ist das Beispiel, das er mir gegeben hat und das eine Laufzeitausnahme durch Null dividiert.
function Start-Something { 1/(1-1) }
Rufen Sie es dann so auf, um zu sehen, dass es den Fehler generiert und trotzdem die Nachricht ausgibt.,
&{ Start-Something; Write-Output "We did it. Send Email" }
Aber indem wir denselben Code in eine try/catch
, sehen wir etwas anderes passieren.
try{ &{ Start-Something; Write-Output "We did it. Send Email" }}catch{ Write-Output "Notify Admin to fix error and send email"}
Wir sehen, dass der Fehler zu einem Abschlussfehler wird und nicht die erste Nachricht ausgibt. Was ich an diesem nicht mag, ist, dass Sie diesen Code in einer Funktion haben können und es sich anders verhält, wenn jemand eine try/catch
.
Ich bin selbst nicht auf Probleme gestoßen, aber es ist der Fall, auf den ich achten muss.
$PSCmdlet.,ThrowTerminatingError () in try/catch
Eine Nuance von $PSCmdlet.ThrowTerminatingError()
ist, dass es einen Abschlussfehler in Ihrem Cmdlet erzeugt, aber es wird zu einem nicht abschließenden Fehler, nachdem es Ihr Cmdlet verlassen hat. Dadurch bleibt der burdenon der Aufrufer Ihrer Funktion, um zu entscheiden, wie mit dem Fehler umzugehen ist. Sie können es wieder in einen Auslöschungsfehler umwandeln, indem sie -ErrorAction Stop
oder es aus einer try{...}catch{...}
aufrufen.,
Öffentliche Funktionsvorlagen
Eine letzte Möglichkeit, die ich bei meinem Gespräch mit Kirk Munro hatte, war, dass er einetry{...}catch{...}
um jede begin
, process
und end
Block in all seinen advancedfunctions. In diesen generischen Catch-Blöcken hat er eine einzelne Zeile mit$PSCmdlet.ThrowTerminatingError($PSItem)
, um alle Ausnahmen zu behandeln, die seine Funktionen verlassen.
function Start-Something{ param() process { try { ... } catch { $PSCmdlet.ThrowTerminatingError($PSItem) } }}
Da sich innerhalb seiner Funktionen alles in einer try
– Anweisung befindet, verhält sich alles konsistent., Dies gibt dem Endbenutzer auch saubere Fehler, die den internen Code vor dem generierten Fehler verbergen.
Trap
Ich habe mich auf den try/catch
Aspekt von Ausnahmen konzentriert. Aber es gibt eine Legacy-Funktion, die ich erwähnen muss, bevor wir das abschließen.
A trap
wird in ein Skript oder eine Funktion eingefügt, um alle Ausnahmen abzufangen, die in diesem Bereich auftreten. Wenn eine Ausnahme auftritt, wird der Code in der trap
ausgeführt und dann wird der normale Code fortgesetzt. Ifmultiple Ausnahmen passieren, dann wird die Falle immer und immer aufgerufen.,
Ich persönlich habe diesen Ansatz nie übernommen, aber ich kann den Wert in Admin-oder Controller-Skripten sehen, die alle Ausnahmen enthalten, und dann weiterhin ausführen.
Schlussbemerkungen
Das Hinzufügen einer ordnungsgemäßen Ausnahmebehandlung zu Ihren Skripten macht sie nicht nur stabiler, sondern macht es Ihnen auch einfacher, diese Ausnahmen zu beheben.
Ich habe viel Zeit damit verbracht, throw
weil es ein Kernkonzept ist, wenn es um exceptionhandling geht., PowerShell gab uns auch Write-Error
, das alle Situationen behandelt, in denen Siethrow
. Denken Sie also nicht, dass Sie throw
nachdem Sie dies gelesen haben.
Nachdem ich mir nun die Zeit genommen habe, in diesem Detail über die Ausnahmebehandlung zu schreiben, verwende ich Write-Error -Stop
, um Fehler in meinem Code zu generieren. Ich werde auch den Rat von takeKirk befolgen und ThrowTerminatingError
zu meinem goto-Ausnahmehandler für jede Funktion machen.