Cmd-Nachfolger: Scripting mit der Windows PowerShell

10.07.2006 von Thomas Wölfer
Microsoft führt mit der unter dem Codenamen Monad entwickelten PowerShell eine neue Kommandozeile für Windows ein. Als objektorientierter Nachfolger von command.com und Cmd.exe setzt sie ganz auf .NET. Wir zeigen Ihnen anhand des frei erhältlichen RC1, welche neuen Möglichkeiten Sie ab sofort auf der Commandline und bei Scripts haben.

Microsofts bisherige Versuche mit Kommandozeilen-Shells waren eher unerfreulich. Die alte command.com mag für die ersten Versionen von MS DOS adäquat gewesen sein, doch den wachsenden Betriebssystemfunktionen hielt sie nicht Stand. Die mit Windows NT eingeführte Cmd.exe Shell stellte da schon deutlich mehr Möglichkeiten bereit. Im Vergleich mit gängigen Unix-Shells wie der Bash zieht Microsofts Kommandozeile aber deutlich den Kürzeren.

Das will Microsoft nun komplett ändern. Mit der PowerShell (vormals Monad Shell, MSH) möchte der Software-Konzern Administratoren auch unter Windows eine Shell an die Hand geben, mit der sie ganz nach Herzenslust Scripts schreiben und ihr System kontrollieren können. Dabei verfolgt die PowerShell ein von Grund auf anderes Konzept, als das bei textorientierten Shells wie der Bash oder auch Cmd.exe der Fall ist.

Die PowerShell können Sie sich bei Microsoft für alle gängigen Windows-Versionen unter www.microsoft.com/technet/scriptcenter/topics/msh/download.mspx herunterladen. Neben einer 32-Bit-Version ist auch eine Version für die 64-Bit-Varianten von Windows erhältlich. Beide Downloads erfordern eine kurze Registrierung und setzen das .NET Framework 2.0 voraus.

Objektorientierte Shell vs. Textlisten

Die PowerShell arbeitet vollständig objektorientiert. Anders als bei üblichen Shells handelt es sich bei den Ergebnissen jedes Kommandos der PowerShell also nicht um einen Text, sondern um ein Objekt (dazu gleich mehr). Ähnlich wie bei bisher bekannten Shells gibt es allerdings eine Pipeline, in der die Resultate der einzelnen Befehle weitergereicht und weiterverarbeitet werden können. Nur sind die Eingabewerte sowie die Resultate eben Objekte und keine Texte.

Die Objekte der PowerShell unterscheiden sich nicht von denen eines C++- oder C#-Programms. Sie sind eine Zusammenfassung aus Eigenschaften (Properties) und Methoden. Im folgenden Beispiel wollen wir dies anhand des Prozessobjekts, mit dem Windows alle laufenden Anwendungen verwaltet, näher erläutern. Das Prozessobjekt enthält dabei unter anderem eine Eigenschaft für die Prozess-ID und eine für den Namen des Prozesses. Außerdem verfügt das Objekt über verschiedene Methoden, zum Beispiel die Methode Kill(), mit der der Prozess beendet werden kann.

Das objektorientierte Konzept der PowerShell macht das von Unix-Shells bekannte dauernde Parsen von textbasierten Informationen mit all seinen Fehlermöglichkeiten überflüssig. Zur Verdeutlichung folgendes Beispiel: Angenommen, Sie hätten gerne eine Liste aller Prozesse, die mehr als 100 Handles verbrauchen. Mit einer traditionellen Linux-Shell würden Sie dazu das Kommando zur Anzeige aller Prozesse (ps -A) aufrufen. Das Kommando liefert dann eine Textliste zurück. In jeder Zeile stehen dabei Informationen über einen Prozess, durch Leerzeichen voneinander getrennt. Diese Zeilen würde man dann mit einem der gängigen Tools parsen, die Prozess-ID herausschneiden und über diese mit einem weiteren Programm die Handle-Anzahl abfragen. Das textbasierte Ergebnis dieser Abfrage würde man erneut parsen, die relevanten Zeilen herausfiltern und den relevanten Text schließlich anzeigen.

Je nachdem, wie gut das Ausschneiden und Filtern der Information aus den Textlisten gelöst ist, klappt dieser Ansatz mehr oder weniger zuverlässig. Ändert sich zum Beispiel die Breite einer Spalte der Ausgabe durch zu lange Prozessnamen, kann es schon schief gehen.

Echte Objekte bei der PowerShell

Die PowerShell verwendet einen grundlegend anderen Ansatz. Hier startet man ebenfalls das Kommando get-process, das alle laufenden Prozesse vom Betriebssystem liefert. Nur werden sie als ein Listenobjekt von Prozessobjekten geliefert. Diese Objekte kann man dann auf ihre Eigenschaften hin untersuchen – es müssen also keinerlei Textzeilen untersucht und in Spalten aufgeteilt werden. Die Eigenschaften haben dabei automatisch den passenden Datentyp: Die Prozess-ID ist also tatsächlich ein Integer und nicht etwa ein String, der nur bei der Anzeige wie ein Integer aussieht.

Auf dem Eingabe-Prompt sieht das PowerShell-Script einem herkömmlichen Script sehr ähnlich. Es enthält jedoch keine Textersetzungs- und -filtermechanismen wie die Linux-Befehle sed und grep:

MSH:> get-process | where { $_.handlecount –gt 100 }

get-process liefert dabei die Objektliste aller Prozesse. Diese wird per Pipe in den where -Ausdruck umgelenkt. Er filtert alle Objekte heraus, bei denen die Eigenschaft handlecount größer als 100 ist. Als Resultat werden die passenden Prozesse am Bildschirm angezeigt.

Zuweisung an Variablen

Die Bildschirmausgabe mag auf den ersten Blick verwundern, denn zunächst liefert die eingegebene Kommandozeile nur Objekte zurück. Sie könnten also das Ergebnis des Beispielausdrucks jederzeit in eine weitere Pipe stecken und die Objekte weiter bearbeiten. Kommt das Resultat aber am Ende einer Pipe an und handelt es sich dabei um den Bildschirm, dann (und erst dann) wandelt die PowerShell die Objekte in ihre passende Textdarstellung um und gibt sie aus. Ein anderes mögliches Ende der Pipeline könnte auch Excel sein. In diesem Fall würden die Daten stets mit dem richtigen Datentyp in den Zellen des Spreadsheets landen.

Das Ergebnis eines Ausdrucks können Sie auch jederzeit einer Objektvariablen zuweisen. Da get-process eine Objektliste zurückliefert, stellt diese Variable dann auch ein Listenobjekt dar.

MSH:> $a=get-process | where { $_.handlecount –gt 100 }

Die Variable $a enthält daraufhin das Ergebnis des Befehls und ist vom Typ her ein Listenobjekt. Mit $a.length kann man sich beispielsweise die Anzahl der Objekte in der Liste anzeigen lassen und mit $a[5] etwas auf das sechste Element der Liste zugreifen (Index startet bei 0). Liefert $a.getlenght etwa den Wert 35, so gibt es eben genau so viele Prozesse mit mehr als 100 Handles.

Gibt man den Inhalt mit der einfachen Eingabe von $a am Bildschirm aus, stellt die Shell jedoch mehr als 35 Zeilen dar, da beispielsweise über den Spalten noch ein Spaltenkopf ausgegeben wird. Hier zeigt sich erneut, dass durch den Objektansatz viele Fehler vermieden werden. Denn ermittelt man unter Linux die Anzahl der Prozesse, indem man beispielsweise mit ps –A | wc –l die Zeilen des zurückgegebenen Textes zählt, ist das Ergebnis falsch, da die Zeilen mit den Spaltenbeschriftungen und eventuelle Zusammenfassungen mitzählen.

Abgesehen von der deutlich mächtigeren objektorientierten Arbeitsweise der PowerShell beherrscht die neue Kommandozeile aber auch die textbasierte Verarbeitung, die herkömmliche Shells anzubieten haben. So ist es möglich, auch „alte“ Commandline-Programme wie zum Beispiel ipconfig von der PowerShell aus aufzurufen und dessen textbasierte Ergebnisse weiterzuverarbeiten.

Die CmdLets der PowerShell

Die PowerShell setzt voll auf das .Net Framework und die zugehörige BCL (Base Class Library) auf. Im Prinzip können Sie alle Klassen und Methoden des .Net Framework in jedem PowerShell-Script verwenden. Das bedeutet, Sie haben per Script vollen Zugang zu allen Möglichkeiten der Windows-Plattform: Sie sind also nicht auf den mitgelieferten Funktionsumfang der PowerShell beschränkt.

Die Bausteine der PowerShell sind kleine Befehle, die auf den Namen „cmdlet“ hören. Jedes CmdLet wird dabei durch einen eindeutigen Namen identifiziert. Dieser Name setzt sich aus einem Verb und einem Hauptwort zusammen, die durch einen Bindestrich voneinander getrennt werden wie etwa bei get-process.

Aus Konsistenzgründen ist das Hauptwort dabei immer im Singular gehalten. Wer mag, kann für bestehende CmdLets Aliase einrichten, um Tipparbeit zu sparen. Eines der wichtigsten CmdLets ist get-help. Dieses Kommando funktioniert ähnlich wie das man -Kommando unter Unix und liefert eine Beschreibung zu dem als Parameter übergebenen Text. Wenn Sie keinen Parameter an get-help übergeben, zeigt das Kommando eine Liste aller Elemente an, zu denen es eine Hilfe anbieten kann.

Mit Hilfe des Kommandos Get-Command erhalten Sie eine Liste aller in der PowerShell verfügbaren Kommandos, mit Get-Command NameDesKommandos erhalten Sie Informationen über das angegebene Kommando.

Navigieren im Dateibaum

Für die Navigation im System bietet die PowerShell ein System, bei dem die verschiedensten Datenquellen sowie Laufwerke zugänglich sind: Sie können also zum Beispiel das Dir -Kommando auf ein Verzeichnis, aber auch auf einen Registry-Ast oder den Zertifikatspeicher des Systems anwenden.

Eine Auflistung aller Datenquellen liefert das CmdLet get-drive. Neben den physikalischen und Netzwerklaufwerken listet es die Registry-Äste HKEY_CURRENT_USER und HKEY_LOCAL_MACHINE auf. Interessant sind auch die Zweige Variable und Env.

Das Wechseln eines Verzeichnisses erfolgt mit Set-Location. Für diesen Befehl ist auch ein Alias auf das altbekannte cd vorhanden. Durch die Gleichstellung von Laufwerken und Registry-Ästen können Sie etwa mit cd hklm: in die Registry HKEY_LOCAL_MACHINE wechseln und darin zu den einzelnen Schlüsseln navigieren. Nach einem Wechsel mit cd variable: können Sie sich mit dir den Inhalt verschiedener Systemvariablen anzeigen lassen, ein Wechsel zu Env liefert die Environment-Variablen.

PowerShell-Scripts

Scripte liegen bei der PowerShell in ganz normalen Dateien vor, die die Shell anhand der Environment-Variable Path sucht. Als Dateiendung müssen Scripts die Erweiterung .msh aufweisen. Jedes Statement, das Sie in einem Script verwenden, können Sie auch direkt auf der Kommandozeile benutzen: Scripts sind also letztlich nichts anderes als die permanente Speicherung einer Abfolge von Kommandozeilenoperationen.

Die Sprache: Zuweisungsoperatoren

Die PowerShell verfügt über einen ganzen Satz von Zuweisungsoperatoren, die stark an die Operatoren von C# erinnern: = weist einen Wert an eine Variable zu, += addiert den Wert rechts zum Wert links, -= zieht den Wert rechts vom Wert links ab. Weitere Operatoren existieren für Multiplikationen, Divisionen und Modulo-Operationen. Mit

MSH> $a = “Ein String“

weisen Sie der Variable $a den Text „Ein String“ zu. Der Befehl

MSH> $a += “ kann wachsen“

addiert (erweitert) diesen Text um „ kann wachsen“. Um den Inhalt der Variable auszugeben, reicht die Eingabe der Variable selbst. Der Grund: Die Eingabe der Variable ist ein Ausdruck, der als Ergebnis ein Objekt liefert. Dieses Objekt kommt aber sofort am Ende der Pipeline an, und dabei handelt es sich um den Bildschirm. Dadurch wird eine textbasierte Repräsentation der Variable in der Shell angezeigt.

MSH> $a
Ein String kann wachsen

Bei der Zuweisung an Variablen können Sie aber mehr als nur Texte angeben: Integer, Arrays, Hashtables, - im Prinzip jeden Typ des .Net Frameworks. Ein Array aus vier Integern würden Sie beispielsweise wie folgt zuweisen:

MSH> $a = 1,2,4,8

Variablen können dabei auch mit Attributen versehen werden. Ein solches Attribut ist beispielsweise das ReadOnly-Attribut. Variablen, die mit diesem Attribut erzeugt werden, können im Script nicht verändert werden:

MSH> Set-Variable a 42 –Option ReadOnly

Versuchen Sie dann später, den Wert von a zu verändern, erhalten Sie eine Fehlermeldung:

$a = 9 # Fehler !

Arithmetische Ausdrücke, Conditionals, Flow

Die PowerShell kann auch rechnen, sowohl mit Variablen als auch direkt auf der Kommandozeile – und zwar mit Integern und mit Fließkommazahlen.

MSH> 2 * 2.2
4,4

Bedingungen prüfen Sie in der PowerShell mit den Ausdrücken if, elsif, else und switch. Die dadurch entstehenden Codeblöcke müssen in geschweifte Klammern gefasst werden. Für die Bedingungen benötigen Sie natürlich Vergleichsoperatoren. Davon bietet die PowerShell einen umfangreichen Satz. An Operatoren gibt es unter anderem „größer“ (-gt, greater than), kleiner (-lt , less than) und „gleich“ (-eq , equal).

Andere Operatoren für Addition, Subtraktion, Negation, für die Ersetzung von Text oder das Verodern von Werten stehen ebenfalls zur Verfügung. Ein Ausdruck, bei dem Sie den Inhalt einer Variablen mit dem Wert 4 vergleichen, sieht also folgendermaßen aus:

MSH> $a = 3
MSH> if ( $a –gt 2) { Write-Host $a ist größer als 2 }
3 ist größer als 2

Das Write-Host CmdLet entspricht dabei in etwa dem bislang bekannten „echo“: Es zeigt seinen Parameter am Bildschirm an.

Die PowerShell verfügt auch über die Schleifen-Konstrukte: Es gibt ein while, ein foreach und ein foreach-object. While läuft so lange, bis sein Parameter false wird, mit foreach iterieren Sie über eine Sammlung, und mit foreach-object rufen Sie für jedes Objekt aus einer Sammlung direkt einen Codeblock auf.

Objekte und Eigenschaften

Nachdem Sie bei der PowerShell immer mit Objekten arbeiten, ist es natürlich wichtig zu wissen, wie diese Objekte aussehen. So können Sie beispielsweise mit Get-Process eine Liste aller Prozesse anzeigen – woher erfahren Sie nun aber, welche Eigenschaften und Methoden ein solches Prozessobjekt hat?

Auf diese Frage gibt es zwei Antworten: Entweder Sie blicken in die MSDN-Dokumentation zum Prozessobjekt im .NET Framework – oder Sie verwenden ein CmdLet, um sich diese Information ausgeben zu lassen. Angenommen, Sie ermitteln sich das Prozessobjekt zum Prozess lsass, dem lokalen Sicherheitsdienst von Windows:

MSH>Get-Process lsass

In diesem Fall gibt Ihnen die PowerShell Informationen zu diesem Prozess aus. Dabei zeigt sie aber nur eine Untermenge der am häufigsten benötigten Eigenschaften eines Prozesses an. Wie kommen Sie nun also an die anderen? Ganz einfach: Dafür gibt es Get-Member:

MSH> Get-Process lsass | Get-Member –MemberType Property

Dieser Befehl liefert Ihnen eine Liste aller Eigenschaften von Prozessen. Anhand der Liste können Sie beispielsweise sehen, dass Prozesse als Eigenschaft unter anderem auch eine Prozess-ID und einen Namen haben.

Zugriff auf Methoden und eigene Funktionen

Wenn Sie etwa nur diese beiden Eigenschaften, Prozess-ID und den Namen des Prozessobjekts anzeigen wollen, können Sie den folgenden Ausdruck verwenden:

MSH> foreach ( $var in Get-Process ) { Write-Host $var.Name - $var.Id }

Analog dazu können Sie auch auf die Methoden der Objekte zugreifen.

Statische Methoden rufen Sie mit einem doppelten Doppelpunkt auf. Die aktuelle Zeit erhalten Sie beispielsweise über die statische Methode Now() des DateTime-Typs der Base Class Library:

MSH> [DateTime]::Now

Abgesehen von Scripts können Sie zum Vereinfachen der Arbeit auch eigene Funktionen definieren. Eine Funktion zum Addieren zweier Zahlen könnte beispielsweise wie folgt programmiert und aufgerufen werden.

MSH> function add { [int]$args[0] + [int]$args[1]}

MSH> add 4 5

9

Für die Ausgabe von Ergebnissen als Text am Bildschirm bietet die PowerShell einige CmdLets mit dem Prefix format. Eine Ausgabe der aktuellen Prozesse als Liste erhalten Sie beispielsweise so:

MSH> Get-Process | Format-List

Fazit

In diesem Beitrag haben Sie die Grundlagen des Scripting mit Microsofts neuer PowerShell erlernt. Wir können Sie nur ermutigen, sich die aktuelle RC1-Version herunterzuladen und damit zu experimentieren. Wer schon etwas Erfahrung mit objektorientierten Sprachen besitzt oder schon einmal das .NET-Framework genutzt hat, wird sich auf der neuen Shell sofort wohl fühlen. Scripts gelingen mit der PowerShell um Größenordnungen schneller als mit cmd.exe – nicht nur weil die Programmierung viel einfacher ist, sondern auch weil die Fehlerrate, und damit die Zeit für das Debugging, drastisch sinken.

Neben einfachen Scripts bietet die PowerShell eine ganze Reihe weiterer Features – nicht zuletzt haben Sie auch die Möglichkeit, die Shell durch eigene CmdLets zu erweitern. Mit diesen tiefer gehenden Themen wird sich ein zukünftiger Beitrag beschäftigen. (ala)