So arbeitet das Formatierungs-Subsystem der Powershell
27. Juli 2010Einblicke in das Formatierungs-Subsystem der Powershell hat sich Don Jones erarbeitet. Er zeigt in seinem Blog zur Powershell auf, welche Formatierungen für bestimmte Objekttypen Verwendung finden. Hier zeigt er vor allem, welche versteckten Cmdlets bei der Ausgabe zum Einsatz kommen und belegt diese Informationen mit Beispielen.
Wer hat schon einmal ein Powershell-Kommando wie das folgende ausprobiert:
Get-Process | Format-Table | Export-CSV processes.csv
Das führt zu einer wirklich schrecklichen Ausgabe. Es stellt sich aber die Frage warum? Verantwortlich dafür ist in erster Linie die Arbeitsweise des Formatierungs-Subsystems der Powershell. Denn was sich alles sozusagen unter der Haube abspielt, ist weitaus komplexer, als das was der Administrator zu sehen bekommt. Daher erweist sich ein detaillierter Blick auf die Interna als sehr nützlich.
Zuerst muss man wissen, dass die Powershell-Pipeline immer mit einem speziellen Cmdlet endet – es heißt Out-Default. Es ist immer da, selbst wenn man es nicht sehen kann. Wer den folgenden Befehl eingibt:
Get-Process
der führt eigentlich diesen Befehl aus:
Get-Process | Out-Default
Doch es wird noch komplizierter: Bei den Konsolen, die Microsoft liefert, leitet Out-Default die Ausgabe einfach weiter an Out-Host. Sprich alles was in der Pipe liegt, wird an Out-Host geleitet, das diesen Inhalt dann in eine Art der Textdarstellung überführt.
Damit sieht der Ablauf in der Realität wie folgt aus:
Get-Process | Out-Default | Out-Host
Der Trick an der Sache ist, dass die meisten Out-Cmdlets (wie Out-Host, Out-Printer oder Out-File) gar nicht wissen, was sie mit Objekten wie Prozessen, Diensten, Benutzern oder ähnlichen anfangen sollen. Diese Out-Cmdlets verstehen spezielle Formatierungsobjekte, die ihnen sagen, wie sie Tabellen oder Listen und ähnliches darstellen sollen.
Das Formatierungs-Subsystem ist dafür verantwortlich, dass zum Beispiel diese Prozessobjekte in eine Tabelle überführt werden. Diese Aufgabe wird wie folgt abgewickelt:
Zuerst bestimmt das Formatierungs-Subsystem den Objekttypus, den es anzeigen muss. Dazu kann man den folgenden Befehl ausführen:
Get-Process | Get-Member
Hier ist oben der "Type Name" zu sehen: System.Diagnostics.Process ist der Type Name für ein Prozessobjekt.
Die Powershell durchsucht dann die Formatierungsbefehle, die in den Speicher geladen wurden. Standardmäßig werden einige sätze von Befehlen geladen (sie sind unter dem Installationsverzeichnis der Powershell zu finden, dazu muss man "cd $pshome" angeben).
Hier liegen sie in Dateien mit einer Dateierweiterung namens ".format.ps1xml". Für die Prozesse kann man "System.Diagnostics.Process" in der Datei "dotnettypes.format.ps1xml" finden.
Regel 1: Wenn ein vordefiniertes Layout gefunden wird, verwendet es die Powershell.
Im Fall der Prozessobjekte gibt es ein vordefiniertes Layout (sprich Ausgabeformat). Es handelt sich dabei um eine Tabelle mit sieben Spalten. Mit dieser Information weiß die Powershell, wie sie die Tabelle anlegen soll, die man üblicherweise sieht, wenn man Get-Process ausführt. Und so legt es die Tabelle an, egal welche Prozessobjekte sich in der Pipeline befinden.
Diese Vorgehensweise funktioniert nur dann gut, wenn sich in der Pipeline nur einen Art von Objekten befindet. Dabei wird die Formatierungsansicht gewählt, die davon abhängt, welches Objekt zuerst gesehen wird.
Gibt man über eine Pipe unterschiedliche Arten von Objekten aus (das lässt sich mit einem Skriptrecht einfach anstellen), dann werden die Ergebnisse nicht mehr so schön aussehen.
Sucht der Administrator die .format.ps1xml-Dateien für "Win32_OperatingSystem" (das wird von Get-WmiObject -class Win32_OperatingSystem geliefert), dann wird man nichts Passendes finden. Denn es existiert kein vordefiniertes Layout für dieses Objekt. Daher stellt sich die Frage, was die Powershell nun macht.
Regel 2: Wenn ein DefaultDisplayPropertySet definiert ist, werden diese Properties für das Anzeigen verwendet. Anderenfalls wird versucht, alle Properties darzustellen.
Ein Blick in die standardmäßigen Extensible Type System (ETS) Befehle der Powershell (in types.ps1xml), wird Win32_OperatingSystem zeigen und einen Bereich mit der Bezeichnung "DefaultDisplayPropertySet". Er führt die sechs standardmäßig angezeigten Properties für diese Art von Objekt auf.
Regel 3: Ist klar, welche Properties angezeigt werden sollen, stellt sich die Frage wie viele. Wenn es vier sind oder weniger, wird eine Tabelle angelegt. Sind es fünf oder mehr, wird eine Liste verwendet. Die Theorie lautet: Vier Spalten werden auf den meisten Bildschirmen sauber dargestellt.
Wenn die Powershell weiß, welches Layout sie verwenden soll (entweder ein vordefiniertes Layout oder das die Regel 3 bestimmt), dann ruft es das Formatierungs-Cmdlet auf. Diese Cmdlet erzeugt die Formatierungsbefehle und gibt sie an das Out-Cmdlet zurück, das zum Einsatz kommt. Damit sieht der Befehl zu Get-Process in der Realität etwa wie folgt aus:
Get-Process | Out-Default | Out-Host | Format-Table | Out-Host
Das ist zwar nicht ganz genau die Reihenfolge, wie sie unter der Haube ausgeführt wird, doch das zeigt die Logik, die zum Einsatz kommt. Und hier kommt nun der größte Trick zum Einsatz:
Nur die Out-Cmdlets können (in den meisten Fällen) verstehen, was ein Format-Cmdlet erzeugt. Dazu der folgende Befehl:
Get-Process | Format-Table | ConvertTo-HTML | Out-File c:\test.html
Das sollte man sich in einem Webbrowser ansehen und dann wird man sehen, dass es sich um HTML handelt, was von den Formatierungs-Befehlen erzeugt wurde. Das ist nicht das, was man eigentlich wollte. Und hier kommt noch eine einfache Regel ins Spiel:
Regel 4: Das Formatieren muss zum Schluss erfolgen (also rechts in der Befehlszeile).
Daher darf ein eigener Format-Befehl immer nur zum Schluss erfolgen, außer das letzte Element auf der Befehlszeile ist Out-File, Out-Printer, oder Out-Host. Man darf die Ausgabe eines Format-Befehls nicht über die Pipe an ein anderes Cmdlet geben, es sei denn an eines der drei Out-Cmdlets.