PowerShell Debugging

Menschen neigen zu Fehlern und selbst in den kürzesten Skripten können sich Fehler verbergen, die nicht sofort auffallen

Strenge Definition mit Set-PSDebug und Set-Strictmode

Ehe ich in die Tiefen einer Funktionen und Tricks gehe, sollten Sie sich wirklich überlegen eine "Plicht zur Definition" für Variablen vorzugeben. Die meisten Fehler passieren nämlich durch Tippfehler, dass Sie eine Variable belegen und anderswo mit einem anderen Namen benutzen. Die folgenden Zeilen sorgen dafür, dass nicht initialisierte Variablen zu einem Fehler führen.

# In VBScript ist das ein
# option explicit

set-psdebug -strict

Besser so einen Fehler finden als mir zufälligen oder gar falschen Inhalten zu arbeiten. Wer schon die Variablen vorab initialisiert, sollte Sie auch gleich typisieren, d.h. einen String auch mit "[string]" voran definieren, damit Powershell auch weiß, ob ein "+" (Plus-Zeichen) nun zwei Variablen verketten oder die Inhalte addieren soll.

Fun mit set-psdebug

Allerdings hat diese Zeile auch einen Bug offenbart, den ich zufällig bemerkt habe z.B. wenn ich folgenden Zeilen nutze:

set-psdebug -strict
mkdir ".\logs" -force | out-null

Legen Sie diese Zeilen als PS1-Datei ab und starten Sie diese dann aus einer CMD-Box mit:

PowerShell.exe -noninteractive -file "c:\temp\test.ps1"

Es könnte sein, dass Sie dann folgende Fehlermeldung sehen

The variable '$_' cannot be retrieved because it has not been set.
At line:2 char:38
+         $steppablePipeline.Process($_ <<<< )
    + CategoryInfo          : InvalidOperation: (_:Token) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : VariableIsUndefined

Das Problem tritt nicht auf, wenn Sie die beiden Zeilen einfach drehen. Ich weiß nicht, wann "$_" tatsächlich von der PowerShell instanziert wird, aber anscheinend nicht früh genug für PSDEBUG

mkdir ".\logs" -force | out-null
set-psdebug -strict

Ausgaben

Zudem kann man an verschiedenen Stellen mit den drei "Write"- befehlen weitere Infos ausgeben, die einfach per Default abschaltbar sind.

Hinweis
Jeffrey Snover rät davon ab, mit "Write-Host" Debug-Ausgaben zu schreiben, da sie "nur" auf der Konsole erscheinen und daher nicht weiter verarbeitet werden können. Das widerspricht dem Ziel Code wieder zu verwenden. Write-Host ist aber durchaus in Ordnung für z.B. die Gestaltung einfacher Oberflächen u.a.
Siehe auch http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/

Ich habe es schon mehrfach erlebt, dass "Write-Host" Ausgaben nicht mit Start-Transcript erfasst werden, was die Fehlersuche ebenfalls nicht gerade macht.

write-host "Hallo ich starte"
write-warning "Ich bin eine Warnung"
write-error "Das ist ein Error"
write-debug "Das ist eine Debugmeldung 1"
$DebugPreference = "Continue" 
write-debug "Das ist eine Debugmeldung 2"
$DebugPreference = "SilentlyContinue"
write-debug "Das ist eine Debugmeldung 3"
write-host "Hallo, ich bin fertig"

So sieht die Ausgabe dann aus.

 

Die "write-error"-Meldung beendet das Skript aber nicht.

Wenn Sie bei der Ausgabe eine Zeile immer wieder überschreiben lassen wollten (z. B. als Statusanzeige), dann helfen ihnen Sonderzeichen und die Option "-nonewline"

while ($true) {
   write-host "`r " (Get-Date -Format HHMMss) -NoNewline
   Start-Sleep 1
}

Weitere Sonderzeichen sind:

`0    Null
`a    Alert
`b    Backspace
`f    Form feed
`n    New line
`r    Carriage return
`t    Horizontal tab
`v    Vertical tab

PowerShell und Breakpoints

Ich erinnere mich noch gut an das "Debugging" unter DOS. In einem Batch hat man einfach an der gewünschten Stelle ein "COMMAND:EXE" eingefügt und damit das Skript unterbrochen und konnte in der Shell bestimmte Dinge nachschauen, z.B. Variablen. Mit einem EXIT wurde dieser Child-Prozess verlassen und das eigentliche Skript konnte weiter arbeiten. PowerShell bietet diese Funktion auch aber auf einem viel  höheren Niveau.

Hier sind es "Breakpoints"

# Hilfe zum PS Debugger anzeigen
get-help about_debuggers

# Verfügbaren Commandlets anzeigen
get-help *psbreak*

Name                 Category Synopsis
----                 -------- --------
Set-PSBreakpoint     Cmdlet   Legt einen Haltepunkt für eine Z...
Get-PSBreakpoint     Cmdlet   Ruft die Haltepunkte ab, die in ...
Remove-PSBreakpoint  Cmdlet   Löscht Haltepunkte aus der aktue...
Enable-PSBreakpoint  Cmdlet   Aktiviert die Haltepunkte in der...
Disable-PSBreakpoint Cmdlet   Deaktiviert die Haltepunkte in d...

# PSDEbug aktivieren
set-psdebug

Allerdings dürften die meisten PowerShell und Exchange Einsteiger mit diesen Funktionen anfangs überfordert sein. Vielleicht ist die ISE (Integrated Script Environment) mit dem dort eingebauten Debugger oder ein Programm wie PowerGUI für den Anfang sogar besser geeignet, um die ersten Gehversuche mit PowerShell bei der Fehlersuche zu unterstützen.

PowerShell und "Debug"-Schnittstelle

Windows hat intern eine "Debug-Schnittstelle", in welche Programme ihre Ausgaben einstellen können und andere Programme können sich für den Empfang dieser Nachrichten registrieren. Wenn kein Programm lauscht, dann verwirft Windows die Meldungen und es kostet kaum Ressourcen. Ein gutes Programm zum Anzeigen ist z.B. DebugView von Sysinternals. Und es ist aus PowerShell denkbar einfach, eine Meldung an diese Schnittstelle zu überstellen. 

[System.Diagnostics.Debug]::WriteLine("Nachricht","Katgorie")

Diese Schnittstelle bietet sich also für viele Detailausgaben, die im Fehlerfall oder zur Analyse herangezogen werden können aber ansonsten nicht besonders beachtet werden müssen. Das ist insbesondere interessant, wenn mehrere Prozesse im Hintergrund laufen.

Eine weitere Option ist die Aktivierung eines Trace mit.

Set-PSDebug -trace 0

Spielen Sie einfach mal damit herum, welches Hilfsmitteln ihnen besser hilft. Ich habe mir mittlerweile eine eigene Funktion für das Debugging gebaut, der ich die Meldung und einen Schweregrad übergebe, die dann optional einen Bildschirmausgabe erzeugt, eine Eventlogeintrag schreibt oder eine Mail versendet.

Zuletzt können Sie natürlich auch alles protokollieren lassen, was auf der Konsole passiert.

Start-Transcript -Path e2k7rUS-$starttime.log -Append

Stop-Transcript

Allerdings werden das dann eventuell schon etwas umfangreichere Textdateien. Aber wenn ein Skript viel Ausgaben produziert und der Bildschirmbuffer überläuft, ist es oft die einfachste Möglichkeit, etwas nach zu vollziehen.

PowerShell Debugger

Es gibt von Microsoft keine richtige "EntwicklungsUmgebung" und schon gar keinen "Debugger" für PowerShell. Allerdings wurde in der Version 2. eine Debug-Funktion eingebaut, mit der Sie ein Skript bei Fehlern oder über Vorgaben unterbrechen können. Die sich dann öffnende Box ist eine "Untershell", so dass Sie alle Variablen zum aktuellen Stand einsehen und auch verändern können. Mit einem EXIT geht es dann einfach weiter und mit einem "{break}" können Sie das Skript abbrechen. Das ist natürlich kein Vergleich zu den Debug-Funktionen von PowerGUI und anderen Programmen. Weitere Details finden Sie auf folgendem TechNet Artikel.

Testen statt Debuggen

Anstatt immer "hinterher" nach Fehlern zu suchen gibt es interessante Frameworks, mit denen Sie beschreiben, was ein Skript, eine Funktion oder ein Modul leisten soll. Damit lassen sich dann Tests automatisieren. Zugegeben, bislang habe ich damit noch nicht gearbeitet:

Weitere Links