Skript-Debugging für die PowerShell (1)
In PowerShell-Skripte für die Automatisierung der IT-Aufgaben schleichen sich unweigerlich Fehler ein. Das macht den IT-Verantwortlichen nicht gleich zu einem schlechten Skripter, er muss jedoch Bugs identifizieren und ausmerzen, bevor sie in der Produktionsumgebung für Chaos sorgen. Im ersten Teil des Workshops geht es um die Besonderheiten des PowerShell-Debugging und verbreitete Fehlerquellen.
Kein Softwarecode ist von Anfang an perfekt, so sehr die Entwickler sich auch anstrengen mögen. Dies gilt insbesondere für Skriptcode, der oft nicht von professionellen Programmierern erstellt wird, sondern von Administratoren zwischen all den anderen Aufgaben, die sie täglich zu erledigen haben. Manchmal sind die Fehler im Skript leicht festzustellen – aufgerufene Cmdlets oder Funktionen liefern Fehlermeldungen oder das Skript macht offensichtlich nicht das, was es soll. In anderen Fällen treten Fehler nur unter bestimmten Umständen auf, beispielsweise wenn dem ausführenden Account notwendige Berechtigungen fehlen oder gewisse Features des Betriebssystems nicht installiert sind. In jeder Situation müssen Sie den Fehler lokalisieren und beheben können, damit Ihr Skript sicher und zuverlässig seinen Dienst verrichten kann.
Für die meisten verbreiteten Programmiersprachen existieren spezielle Debugger, die sich in den fertig kompilierten Prozess einklinken und das Geschehen während der Ausführung oder zumindest bei Fehlern in Echtzeit untersuchen. Bei Skriptsprachen muss bei Anpassungen am Code nicht erst aufwendig kompiliert und gelinkt werden, daher verläuft die Fehlersuche oft weniger strukturiert, nach dem Prinzip von Versuch und Irrtum. Da werden in einzelne Codezweige diagnostische Ausgaben eingebaut, um festzustellen, welcher Zweig abgearbeitet wird und welche Werte einzelne relevante Variablen in diesem Zweig jeweils haben. Diese Ausgaben müssen nach erledigter Entstörung wieder entfernt werden.
Besonderheiten des PowerShell-Debugging
Falls Sie ein solches Debugging in Ihren Skripten erwägen, sollten Sie sich dennoch nicht auf die Ausgabemethoden wie Write-Host oder Write-Output verlassen, deren Ausgaben Sie nach erfolgtem Troubleshooting irgendwie unterdrücken müssen. Vielmehr sollten Sie gleich auf die Cmdlets zurückgreifen, die dafür vorgesehen sind: Write-Verbose und Write-Debug.
Standardmäßig erfolgt aus diesen Befehlen keine Konsolenausgabe, Sie können dies jedoch bei Bedarf aktivieren. Setzen Sie dafür entweder im Skript oder in der PowerShell-Sitzung vor Aufruf des Skripts die Systemvariable "$VerbosePreference" beziehungsweise "$DebugPreference" auf den Wert "Continue" und die Befehle schicken den ihnen übergebenen String jeweils an den Ausgabe-Stream 4 (Verbose) beziehungsweise 5 (Debug). So können Sie die Diagnostikausgaben im Skriptcode belassen und die Ausgaben nur bei der Fehlersuche einschalten. Falls das Setzen der Umgebungsvariablen unerwünscht oder unmöglich ist, können Sie das Verhalten auch mit Parametern steuern. Beginnen Sie dafür Ihr Skript oder Ihre Funktion mit
[CmdletBinding()] Param()
und Sie können auf die Standardparameter "-Verbose" und "-Debug" zurückgreifen, die die jeweilige Ausgabe aktivieren. Hier tritt ein Unterschied im Verhalten zwischen Write-Verbose und Write-Debug zu Tage: Der Switch "-Debug" stellt die Präferenz nicht auf "Continue", sondern auf "Inquire". Das führt dazu, dass Sie nach jeder diagnostischen Ausgabe aufgefordert werden, das Skript fortzusetzen oder anzuhalten. Wählen Sie hier die Option "H", bekommen Sie bereits den ersten Vorgeschmack auf den PowerShell-Debugger, denn nun ist die Ausführung mitten im Skript angehalten und alle Variablen haben die Werte, die Ihnen im Laufe des Skripts zugewiesen worden sind. Sie können die Werte durch Zuweisungen korrigieren und anschließend mit dem exit-Befehl die Frage nach dem Fortsetzen des Skripts wiederholt aufrufen, um sie diesmal mit "J" zu beantworten und die Ausführung fortzusetzen, oder mit der Antwort "B" das Skript zu beenden.
Heute stehen nicht nur Profi-Programmierern, sondern auch Gelegenheitsskriptern deutlich mächtigere Debugging-Werkzeuge zur Verfügung. Bevor wir uns diesen Möglichkeiten zuwenden, schauen wir uns zunächst einmal einige typische Skriptfehler an.
Verbreitete Fehlerquellen
Fehler in PowerShell-Skripten fallen üblicherweise in verschiedene Kategorien. Die erste davon beschreibt Logikfehler. So führt "-lt" statt "-gt" im Zusammenspiel mit "if" oder "Where-Object" dazu, dass der falsche Zweig abgearbeitet wird oder die falschen Objekte aus der Pipeline ausgewählt werden. Derselbe Fehler in einer Schleifen-Ausgangsbedingung (for, while oder do/until) kann dazu führen, dass die Schleife unendlich oft durchläuft. Logikfehler schleichen sich häufig dadurch ein, dass der Skripter im Laufe der Entwicklung die Vergleichswerte ins Gegenteil umdreht, die Vergleichsbedingung jedoch unverändert lässt. Um einen solchen Fehler zu erkennen, hilft nur scharfes Nachdenken, denn nur Sie als Autor des Skripts können wissen, was beabsichtigt war.
Als Nächstes sind Typfehler zu nennen: Die PowerShell verwendet zwar innerhalb der Sprache nur schwache Typen, die sie jedoch für die tatsächliche Verarbeitung in starke .NET-Typen konvertiert. Und hier gilt die Regel, dass bei allen Operationen, einschließlich der Vergleichsoperatoren, der erste Operand den Ergebnistyp bestimmt, sodass alle nachfolgenden Werte in diesen Typ umgewandelt werden. Das führt bisweilen zu Phänomenen, die sich auch aus der Diagnostik-Ausgabe nach Kernighan nicht gleich erklären lassen, ein Beispiel: Wir arbeiten mit den Variablen $a = 10, $b = "10" und $c = 9. Nun ergibt $a -gt $c "True", $b -gt $c "False", $a + $b "20" und $b + $a ergibt 1010 oder genauer "1010". Auf der Konsole sehen die Werte von $a und $b identisch aus, die Klarheit schafft erst die Anwendung der Methode ".GetType()". In diese Kategorie gehören beispielsweise auch die Fehler, die daraus entstehen, dass manche Cmdlets immer Arrays zurückgeben, während andere bei exakt einem Ergebnis ein Skalar des entsprechenden Typs liefern, bei mehreren Ergebnissen jedoch ein Array.
Base-0-Fehler treten bei der Arbeit mit Arrays (dazu gehören auch Strings, wenn Sie einzelne Zeichen oder Unterstrings ansprechen) in der PowerShell auf. Hierbei beginnt die Indizierung der Elemente stets bei 0. Die Skripter übersehen dies oft und versuchen, bei einem Array der Länge N auf das N-te Element zuzugreifen, obwohl das letzte Element in Wirklichkeit den Index N-1 trägt. Die String-Funktionen quittieren diese Versuche korrekterweise mit einem Fehler, beim Zugriff auf Arrays hingegen liefert die PowerShell bei einem nicht existierenden Element einfach stillschweigend "$null" zurück.
Im zweiten Teil der Workshopserie schauen wir uns an, wie Sie falsch eingesetzte Variablen vermeiden und warum Sie bei der Skriptentwicklung immer die vorhandene Infrastruktur berücksichtigen müssen. Im dritten Teil vergleichen wir konkret das Debuggen in der Shell und das Debuggen im Editor.
Über den Autor: Evgenij Smirnov ist Senior Solutions Architect bei Semperis und Microsoft-MVP für Cloud & Datacenter Management sowie VMware Certified Implementation Expert für Datacenter-Virtualisierung.