Home PowerShell Grundlagen PowerShell Try Catch Finally
formats

PowerShell Try Catch Finally

Gute Scripts und Programme zeichnen sich dadurch aus, dass sie fehlerfrei ablaufen oder das auf Fehler richtig reagiert wird. Fehler müssen also richtig behandelt werden.

PowerShell ist darauf spezialisiert, unbeaufsichtigt im Hintergrund viele Aufgaben nacheinander abzuarbeiten (Automation eben).
Wenn man z.B. auf 40 Computern etwas ausführen will und ein Script aufhört zu arbeiten, wenn der 5. Computer nicht erreichbar ist, dann wird der Fehler dadurch behandelt. Dies ist aber ärgerlich, da die restlichen 35 Computer in der Liste nicht mehr abgearbeitet werden.
Deshalb ist PowerShell so gebaut worden, dass es sehr robust läuft.
Das bedeutet, PowerShell ist so eingestellt, dass bei Fehlern (Error) die Arbeit nicht abgebrochen, sondern mit dem den nächsten Objekt / Aufgabe weitergearbeitet wird (Englisch: continue).

Ich möchte hier anhand eines kurzen Code Abschnittes demonstrieren, wie man mit Try{}Catch{}Finally{} auf einen Fehler richtig reagiert.

Code Erklärung:
Get-Content C:\Domain\Users.txt # öffnet und liest eine Textdatei
Get-ADUser $_ # liest ein User Objekt aus dem Active Directory
Set-AdUser -AccountExpirationDate ’01/01/2017′ # Setzt ein Datum für den User

Terminierende Errors und nicht terminierende Errors

Weil PowerShell sehr robust arbeitet, unterscheidet PowerShell Fehler, die unbedingt zwingend zum Abbruch der Arbeit führen müssen (Englisch: Terminating Errors) und Fehler, die nicht den Abbruch der Arbeit erzwingen (Englisch: Non-Terminating Errors).
Nach dem Auftreten eines terminierenden Fehlers werden alle folgenden Aufgaben nicht mehr bearbeitet.
Nach dem Auftreten eines NICHT terminierenden Fehlers werden alle folgenden Aufgaben trotzdem noch bearbeitet.
Alle Fehler, egal ob sie terminierend sind oder nicht, werden angezeigt und in der automatisch vorhandenen Variable $Error abgelegt (die 256 zuletzt aufgetretenen Fehler sind dort zu finden).

PowerShell Einstellungen der Terminierung bei Errors

In der PowerShell kann man einstellen, ob ein Fehler als terminierend behandelt werden soll oder nicht.
Dies macht man mit der Error Action Einstellung.

Einstellungsmöglichkeiten der Error Action in der PowerShell

  • Stop: Zeigt die Fehlermeldung an und beendet die Ausführung.
  • Inquire: Zeigt die Fehlermeldung an und fragt, ob Sie fortfahren möchten.
  • Continue: Zeigt die Fehlermeldung an und setzt die Ausführung fort (dies ist die Voreinstellung (default)).
  • SilentlyContinue: Keine Auswirkungen. Die Fehlermeldung wird nicht angezeigt und die Ausführung ohne Unterbrechung fortgesetzt.
  • Error Action Einstellung auf Globaler Session Ebene

Error Action Einstellung auf globaler Session Ebene

Es gibt in der PowerShell die globale Einstellung mit der automatisch vorhandenen Variablen $ErrorActionPreference.

Beispiel:

Diese wirkt sich auf die gesamte PowerShell Session und auf alle Cmdlets aus, die in dieser Session ablaufen.
Wenn man die $ErrorActionPreference auf Stop stellt, werden alle Fehler als terminierend behandelt.
Durch diese globale Wirkungsweise der $ErrorActionPreference ist diese Einstellung nur sehr grob; deshalb sollte diese möglichst NICHT verändert werden.
Siehe:
Get-Help about_preference_variables
oder: http://www.colorconsole.de/PS_Windows/de/about_preference_variables.htm

Error Action Einstellung auf einzelner Cmdlet Ebene

Es gibt in der PowerShell Parameter, die mit JEDEM Cmdlet verwendet werden können (common Parameter).
Einer dieser Parameter ist der  –ErrorAction Parameter.
Der –ErrorAction Parameter bestimmt, wie PowerShell auf einen Fehler des einzelnen Cmdlet reagiert.
Der –ErrorAction Parameter wirkt stärker auf das Cmdlet (den aktuellen Befehl), als die globale Variablen “$ErrorActionPreference”.
Um sehr fein bestimmen zu können, wie PowerShell auf einen Fehler reagiert, sollte der  –ErrorAction Parameter  anstatt die globale Variable $ErrorActionPreference bei einem Cmdlet genutzt werden.

Beispiel:

Siehe:
Get-Help about_CommonParameters
oder: http://www.colorconsole.de/PS_Windows/de/about_CommonParameters.htm

PowerShell Errors Exceptions und ErrorRecords

Bei der Ausführung eines Befehls oder eines Scripts/Programms erwarten wir, dass der Befehl ohne einen Fehler (Englisch: Error)  zu verursachen abläuft. Fehler sind also die Ausnahme in einem Befehlsablauf (Englisch: Exception).
PowerShell wurde mit dem .NET Framework entwickelt und nutzt auch die .NET Exception Klassen (Objekte).
Es ist eine .NET Framework Konvention, dass die Exception Klassen immer einen Namen haben, der mit “Exception” endet.
PowerShell nutzt die .NET Exception um ErrorRecord Objekte herzustellen.
Die ErrorRecord Objekte enthalten den Fehler in Form einer .NET Exception und noch zusätzliche Informationen.
Errors (Fehler) in der PowerShell sind also ErrorRecord Objekte, die eine .NET Exception enthalten.

Welche Exceptions einen Befehl verursachen können, sieht man meistens an den Objekten und deren Methoden, die dieser Befehl produziert.
Die Exceptions sind im Microsoft Developer Network (MSDN) in der .NET Framework Dokumentation zu einer Klasse (Objekt) aufgelistet.
Leider sind die Cmdlets von PowerShell nicht so gut dokumentiert, so dass man hier erst Fehler provozieren muss, um heraus zu bekommen, welche Fehlerarten von einem Cmdlet generiert werden.
Bei unserem Beispiel wird mit dem Cmdlet Get-Content eine Datei geöffnet und Zeilenweise eingelesen.
Dabei können vielfältige Fehler entstehen (die Exception ist hier gelb markiert):

# Fehler provozieren, die Datei gibt es nicht
Get-Content C:\GibtEsNicht.txt

Get-Content : Der Pfad “C:\GibtEsNicht.txt” kann nicht gefunden werden, da er nicht vorhanden ist.
Bei Zeile:1 Zeichen:12
+ Get-Content <<<<  C:\GibtEsNicht.txt
+ CategoryInfo          : ObjectNotFound: (C:\GibtEsNicht.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

# Fehler provozieren, Zugriff verweigert
Get-Content -LiteralPath “C:\windows\System32\%APPDATA%”

Get-Content : Der Zugriff auf den Pfad “C:\windows\System32\%APPDATA%” wurde verweigert.
Bei Zeile:1 Zeichen:12
+ Get-Content <<<<  -LiteralPath “C:\windows\System32\%APPDATA%”
+ CategoryInfo          : PermissionDenied: (C:\windows\System32\%APPDATA%:String) [Get-Content], UnauthorizedAccessException
+ FullyQualifiedErrorId : GetContentReaderUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetContentCommand

# Fehler provozieren, Pfad ist leer
Get-Content -LiteralPath “”

Get-Content : Das Argument kann nicht an den Parameter “LiteralPath” gebunden werden, da es sich um eine leere Zeichenfolge handelt.
Bei Zeile:1 Zeichen:25
+ Get-Content -LiteralPath <<<<  “”
+ CategoryInfo          : InvalidData: (:) [Get-Content], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.GetContentCommand

PowerShell Speichert die 256 zuletzt aufgetretenen Fehler als ErrorRecord Objekte in der automatisch vorhanden Variable $Error.
Den zuletzt aufgetretenen Fehler kann man mit dem Index 0 auslesen $Error[0].

Die Exception in einem ErrorRecord liest man aus dem Property Exception aus

Beispiel:

Hier kann man mit Get-Member oder der Methode GetType feststellen, welcher .NET Typ hinter der Exception steckt.

Beispiele:

Durch diese Analyse der Exception sieht man, dass die  ItemNotFoundException Exception von der .NET Klasse System.Management.Automation.ItemNotFoundException erzeugt worden ist.
Nun kann man sich im MSDN die Dokumentation zu diesem Objekt in der Klassendefinition ansehen.

Try{} Catch{}  Finaly{}

Mit Try-, Catch- und Finally-Blöcken kann man in der PowerShell auf Fehler mit Abbruch reagieren oder diese Fehler behandeln (versuch der Fehler Korrektur oder Änderung des Programmablaufes).

Die Trap-Anweisung kann ebenfalls verwendet werden, um Fehler mit Abbruch in Skripts zu behandeln.
Diese arbeitet aber immer global wie eine On Error Goto Anweisung.
Die globale Wirkungsweise von Trap macht es schwer, auf Fehler besser individuell zu reagieren.
Jedoch kann man Trap sehr gut dazu verwenden, um z.b. ein ganzes Script einzurahmen (was ich nicht empfehle).
Siehe: Get-Help about_Trap.

Try{}

Wie das Wort schon sagt, wird innerhalb der geschweiften Klammern des Try{} Blocks versucht, ein oder mehrere Befehle auszuführen.
Hier werden Befehle ausgeführt, bei denen man weiß, dass Fehler auftreten können.

Das Try{} Konstrukt fängt nur Fehler ab, die terminierend sind!
Deshalb muss in PowerShell die Error Action auf STOP gestellt werden, damit ein Fehler immer als terminierend behandelt wird.

Wie auch schon bei der Error Action, sollte man auch mit einem Try{} Block so fein granular wie möglich arbeiten.
In eine Try{} Block sollte möglichst immer nur ein Befehl eingerahmt werden.
Wenn man in einem Try{} Block mehrere Befehle einnahmt, weiß man nicht, welcher Befehl den Error verursacht hat. Dies macht es erst möglich, Fehler richtig zu einem Befehl zuzuordnen und die Fehlerquelle zu finden.

Hier sollte man seine Code so umstellen, dass möglichst nur ein Befehl in einem Try{} Block steht; das macht die Situation klarer.

Auch in den anderen Befehlen können Fehler auftreten; diese können auch einzeln behandelt werden.

Catch{}

Einem Try{} block MUSS IMMER mindestens ein Catch{} Block folgen.
Es können mehrere Catch{} Blöcke, die sich auf den eine Try{} Block beziehen, angegeben werden.

Wenn innerhalb des Try{} Blocks noch mehr folgende Befehle stehen, werden diese nicht mehr ausgeführt (wie bei einem Sprung Befehl.)
Nach dem Auftreten eines terminierenden Fehlers innerhalb des Try{} Blockes wird sofort in die Abarbeitung der Catch{} Blöcke gesprungen.

Wie ich oben gezeigt habe, kann der Grund für einen Fehler sehr vielfältig sein.
Ein nacktes Catch ohne die Angabe eines speziellen Fehler Typs fängt ALLE Fehler ab – egal welche Ursache sie haben!
Es würde also bei JEDEM Fehler, egal welcher Art, immer auf die gleiche Weise reagiert.

Auch hier sollte man sehr fein granular feststellen, von welcher Art der Fehler ist, damit man auf einen Fehler in der richten Weise reagieren kann und aussagekräftige Fehlermeldungen generiert.

Man sollte möglichst immer eine spezielle Exception in einem Catch abfangen!
Um auf verschiedene Ausnahmen spezifisch reagieren zu können, werden mehrere Catch-Anweisungsblöcke angegeben, von denen jeder auf einen bestimmten Ausnahmetyp reagiert.

Beispiel:

Beim Auftreten einer Ausnahme werden die Catch-Zweige so lange der Reihe nach angesteuert, bis der Typ gefunden wird, der die ausgelöste Ausnahme beschreibt.
Man sollte in jeder Ausnahmebehandlung im letzten (oder vielleicht auch einzigen) Catch-Zweig keinen Exception Typ angeben. Verzichtet man auf den letzten Catch-Zweig, könnte trotz aller Catch-Zweige immer noch eine unerwartete Ausnahme auftreten.

Die Abarbeitung der Catch-Zweige folgt dem »Ist-eine«-Prinzip der Vererbung in den .NET Exception Klassen. Deshalb muss eine bestimmte Reihenfolge bei der Angabe der Catch-Zweige eingehalten werden.
Ausgehend vom ersten bis hin zum letzten catch-Zweig sollten die angegebenen Exceptions immer allgemeiner werden.
Die Vererbungshierarchie kann man in der MSDN Dokumentation zu einer Exception sehen.

Beispiel der Vererbungshierarchie von der ItemNotFoundException Exception:

System.Object
..System.Exception
….System.SystemException
……System.Management.Automation.RuntimeException
……..System.Management.Automation.SessionStateException
……….System.Management.Automation.ItemNotFoundException

Jede .NET Exception ist von der allgemeinen Klasse System.Exception abgeleitet (vererbt).
Deshalb hat jede Exception die Eigenschaften (Member) dieser Ur-Klasse.

Sehen sie sich die Eigenschaften der System.Exception in der MSDN Dokumentation an!
http://msdn.microsoft.com/de-de/library/vstudio/system.exception.aspx

Finally{}

Der Finally{} Block ist optional und muss unmittelbar dem letzten Catch-Block folgen, falls er angegeben wird.
Der Finally{} Block  wird zum Freigeben von Ressourcen und “aufräumarbeiten” genutzt.
In einem Finally{} Block werden typischerweise geöffnete Datenbank-, Netzwerk-, Datei- Verbindungen wieder geschlossen.
Wenn ein  Finally{} Block vorhanden ist, wird dieser IMMER ausgeführt, egal ob ein Fehler aufgetreten ist oder gar kein Fehler auftrat.
Der Finally{} Block  wird sogar noch ausgeführt, wenn Sie das Skript mit STRG+C oder Exit beenden.

Weitere Verweise:

LESEN !:
Get-Help about_try_catch_finally
oder: http://www.colorconsole.de/PS_Windows/de/about_try_catch_finally.htm

Try Catch Finally and error handling in PowerShell
http://www.vexasoft.com/blogs/powershell/7255220-powershell-tutorial-try-catch-finally-and-error-handling-in-powershell

How Can I Use Try/Catch/Finally in Windows PowerShell?
http://blogs.technet.com/b/heyscriptingguy/archive/2010/03/11/hey-scripting-guy-march-11-2010.aspx

 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
Kommentare deaktiviert  comments