Unter Windows gibt es viele Möglichkeiten, um regelmäßige Aufgaben durch ein Skript erledigen zu lassen. Die simpelste davon ist die Kommandozeile, denn der Interpreter CMD.EXE bietet das, was schon unter DOS mit den Batch-Dateien möglich war. Was Sie mit Vistas Kommandozeile alles erreichen können, zeigt Ihnen dieser Beitrag.
VBScript – der alte Platzhirsch
Der kleinste gemeinsame Nenner aller Microsoft-Skriptsprachen ist der Basic-Dialekt VBScript. Es fand bei den dynamischen ASP-Webseiten ebenso Verwendung wie als Sprache zum Programmieren der Office-Anwendungen. Auch als Allzwecksprache für Problemlösungen unter Vista dient VBScript. Sie müssen lediglich mit einem Editor eine Datei mit der Endung .VBS anlegen und können damit auch administrative Jobs vereinfachen.
Allerdings kommt man mit VBScript nicht eben schnell zum Ziel, weil die Sprache zwar sehr universell, aber wenig mächtig ist. Fast immer muss man entweder eine Windows-Library einbinden und deren Funktionen aufrufen. Die Suche nach diesen Informationen erfordert in der Regel einiges an Zeit und Herumprobieren.
Ein neuer Ansatz
Es fehlt also ein universelles Werkzeug, das Administratoren bei ihrer täglichen Arbeit unterstützt. Ein Weg aus dieser Problematik wäre es etwa gewesen, das Problem ähnlich zu lösen wie unter Unix, wo es viele kleine Tools gibt, die in der richtigen Zusammenschaltung und Parametrisierung unglaublich flexibel sind. Dabei dient jeweils der Text-Output des einen Tools als Eingabe des per Pipe verbundenen nächsten Hilfsprogramms.
Die Microsoft-Entwickler haben jedoch mit der Powershell (früherer Arbeitsname „Monad“) einen vollkommen anderen Ansatz verfolgt. Das Kernkonzept dabei ist eine Art Objektorientierung, ähnlich wie bei modernen Programmiersprachen. Die Powershell von Vista können Sie als besondere Kommandozeile starten, interaktiv verwenden oder vorbereitete Skripts ablaufen lassen.
Die ersten Schritte auf der Shell
Holen Sie sich die Powershell und installieren sie. Nach dem Start erscheint das blaue Powershell-Eingabefenster, wo Sie gleich Ihr erstes Kommando eingeben:
Get-ChildItem c:users
Als Antwort erscheinen die Dateien des angegebenen Verzeichnisses. Alle Befehle können Sie übrigens auch durchgängig klein schreiben. In diesem Beitrag finden Sie sie allerdings in der gemischten Schreibweise, weil die anschaulicher ist. Wenn Sie Get-ChildItem ohne Parameter eintippen, wird Ihnen der Inhalt des aktuellen Verzeichnisses zurückgegeben. Das ist nach dem Start der Powershell Ihr persönlicher Ordner in C:Benutzer. Um den aktuellen Pfad anzuzeigen, verwenden Sie das Kommando Get-Location. Ändern können Sie das aktuelle Verzeichnis so:
Set-Location c:tmp
Logisch aufgebaut
Alle Kommandos – in der Powershell-Terminologie werden sie „Cmdlets“ genannt – sind immer nach dem Prinzip Verb-Substantiv aufgebaut.
Das macht es leichter, einen Befehl mit einer bestimmten Fähigkeit zu suchen. Hierbei hilft das Cmdlet Get-Command, das alle Befehle in Kurzform auflistet. Ohne Parameter verwendet, gibt Get-Command alle Befehle zurück. Sie können aber beispielsweise auch folgendes in die Komandozeile schreiben:
Get-Command Get*
Damit erhalten Sie eine Liste aller Befehle, die Objekte liefern. Wie Sie sehen, gibt es Get-Cmdlets für alle möglichen Arten von Informationen, etwa für die Abfrage laufender Dienste (Get-Service), laufender Prozesse („Get-Process“), Ereignisse (Get-Eventlog) oder einfach des aktuellen Datums plus Uhrzeit (Get-Date)
Das Wissen um den schematischen Aufbau des Namens können Sie nutzen, um Befehle zu erhalten, die ein ganz bestimmtes Aufgabengebiet haben. Lassen Sie sich zum Beispiel alle Cmdlets aufzählen, bei denen es sich um das Erkennen oder Steuern von Diensten dreht:
Get-Command *service
Es gibt Cmdlets, die Daten liefern, als Filter wirken, Ausgaben erzeugen oder Sortierungen vornehmen. Die meisten Cmdlets haben Parameter, wie etwa Get-ChildItem als Parameter ein bestimmtes Verzeichnis als Datenquelle akzeptiert. Es gibt auch Standard-Parameter, die bei fast allen Cmdlets gleich sind, beispielsweise -whatif, das jede Befehlsaktion unterdrückt. Stattdessen erscheint nur eine Meldung, die ausssagt, was der Befehl tun würde. Das ist in der Erprobungsphase von Programmen recht praktisch.
Fehlt bei der Eingabe ein zwingend erforderlicher Parameter, erfragt ihn die Powershell von Ihnen. Geben Sie etwa einmal diese Zeile ein:
Get-EventLog
An dieser Stelle weiß die Powershell nicht, welche der drei Ereignisarten (System, Application, Security) sie verwenden soll und bittet darum mit der Rückfrage „LogName: “ um eine Konkretisierung. Geben Sie zum Beispiel „System“ ein, erfolgt die Anzeige des kompletten System-Ereignislogs. Per [Strg]+[C] brechen Sie die Auflistung ab.
Aliase – Nützliche Abkürzungen
Dass die Namen der Befehle so schön logisch konstruiert sind, ist sehr löblich. Doch damit entstehen recht lange Befehlszeilen. Darum können Sie in der Powershell einen Befehl durch einen Alias ansprechen und dabei eine große Anzahl vordefinierter Aliase nutzen. So hat das zu Beginn verwendete „Get-ChildItem“ zwei Aliase, um es mit den üblichen Shell-Kommandos der Unix- und Windows-Welt ansprechen zu können: „dir“ und „ls“.
Um sich alle Aliase anzeigen zu lassen, dient – Sie haben es vielleicht schon erraten – das Kommando „Get-Alias“. Mit „Set-Alias“ definieren Sie einen eigenen, neuen Alias. Sind Sie etwa mit „ls“ und „dir“ unzufrieden und möchten statt dessen zur Anzeige von Dateien „list“ verwenden, dann definieren Sie diesen Alias so:
Set-Alias list Get-ChildItem
Beispiel-Skript: Dateisicherung
Wenn Sie etwa aus einem Quellverzeichnis alle Dateien, die von einem bestimmten Tag stammen, in ein anderes Verzeichnis kopieren wollen, werden Sie schnell feststellen, dass so ein Job mit reinen Batch-Mitteln nicht „mal eben so“ zu erledigen ist.
Tasten Sie sich schrittweise an die Lösung heran. Eine sinnvolle Grundlage ist die Auflistung der Dateiobjekte des Quellverzeichnisses. Das jagen Sie durch einen Filter, der nur die Dateien des gewünschten Datums durchlässt:
Get-ChildItem c:tmp | Where-Object {$_.lastwritetime -lt "2007/08/01"}
Die Zeile schaut schlimmer aus, als sie ist. Das Pipe-Symbol bewirkt, dass die Dateiobjekte des ersten Cmdlets dem „Where-Object“ übergeben werden. Die gewünschte Filterbedingung steht zwischen den geschweiften Klammern. Darin wirkt „$_“ als Stellvertreter für jedes übergebene Dateiobjekt. Die haben als Eigenschaft unter anderem lastwritetime – eben das Datum der letzten Änderung. Das soll kleiner als (englisch „less than“) das angegebene Datum sein. Für jedes Dateiobjekt wird das geprüft und die Datei unterdrückt, sofern die Bedingung nicht zutrifft.
Weil nach dem „Where-Object“ keine weitere Anweisung steht, gibt die Powershell eine Liste der zutreffenden Dateien auf dem Bildschirm aus. Diese Liste könnten Sie jetzt als Datei schreiben lassen und dies als Vorlage für die Kopieraktion verwenden. Es geht aber auch kompakter und ohne, dass Sie die Gefilde der Powershell verlassen müssen. Dazu fügen Sie als weitere Aktion noch hinten an
| Copy-Item -destination x: backup
Es gibt aber nicht nur ein Cmdlet zum Kopieren, sondern auch eines zum Verschieben. Das nennt sich Move-Item. Zum Verschieben aller Dateien, die vor 2007 angelegt wurden, schreiben Sie
Get-ChildItem | Where-Object {$_ .lastwritetime –lt "2007/1/1"} | MoveItem x:backup
Variablen einsetzen
Befehle in der Powershell können auch Variablen enthalten. Die lassen sich etwa dazu nutzen, lange Verkettungen mit Pipe-Symbol übersichtlicher aufzusplitten. Mit dem folgenden Befehl weisen Sie der Variablen „$a“ die Liste der Dateiobjekte von „c:tmp“ zu
$a = Get-ChildItem c:tmp
Wenn Sie nun einfach „$a“ in eine Zeile schreiben, erhalten Sie die Liste der Dateien angezeigt. Denn die Wiedergabe des Inhalts ist für Variablen die Standard-Aktion. Schreiben Sie nun folgender Zeile hinterher
$b = $a | Where-Object {$_ .length -gt 1024*1024}
Nun enthält „$b“ alle Dateien aus „$a“, die größer als ein MByte sind. Sie können also eine Variable genauso als Quelle innerhalb einer Pipe-Verkettung benutzen wie eines der Cmdlets, die mit „Get…“ beginnen.
Das Beispiel können Sie gleich zu etwas Sinnvollem erweitern: Lassen Sie sich alle Dateien auf C: anzeigen, die größer als ein halbes GByte sind. Die Ausgabe soll absteigend sortiert erfolgen, damit die größten Platzfresser zuerst erscheinen.
$allfiles = dir c: -recursive
$bigfiles = $allfiles | Where-Object {$_.length -gt 500M}
$bigfiles = $bigfiles | Sort-Object length –descending
Der Parameter recurse bewirkt, dass alle Unterverzeichnisse von C: rekursiv nach Dateien durchsucht werden. Powershell kennt typische Abkürzungen für große Byte-Einheiten, wie hier 500M für das halbe Gigabyte. Das „Sort“-Cmdlet verwendet die Eigenschaft „Size“ der Dateiobjekte als Sortierfeld und sortiert wegen -descending die Auflistung in absteigender Reihenfolge.
Noch hat das Programm zum Ermitteln großer Dateien einen Schönheitsfehler. Denn die Ausgabe erfolgt nicht als einfache Liste, sondern mit jeweils einem neuen Vorspann für jedes Verzeichnis. Das lässt sich jedoch durch das Cmdlet Select-Object lösen. Damit können Sie nur bestimmte Eigenschaften von Objekten durchlassen. In diesem Fall genügen Fullname, was dem Dateinamen inklusive Pfadnamen entspricht, sowie Length, also die Dateigröße. Erweitern Sie das obige Skript um die Zeile
$bigfiles | select-object fullname,length
Nun erscheint die Anzeige im kompakten Listenformat.
Ausgaben erzeugen
Das Ergebnis des Platzfresser-Skripts erscheint in der aktuellen Form erst einmal nur auf dem Bildschirm. Vielleicht möchten Sie es aber lieber in einer Textdatei ausgeben, um es weiterverarbeiten zu können. Dazu dient das Ausgabe-Cmdlet „Out-File“. Geben Sie als zusätzliche Zeile noch ein
$bigfiles | select-object fullname,length | Out-File c:tmpbigfiles.txt -Width 255
Damit erfolgt die Ausgabe statt am Bildschirm in die im Skript genannte Datei. Der width-Parameter sorgt dabei dafür, dass die Optimierung der Seitenbreite statt auf die 80 Zeichen der Powershell-Konsole auf 255 Zeichen erfolgt. Damit werden lange Pfadnamen nicht abgeschnitten. Falls doch, erweitern Sie die width-Angabe einfach.
Die Powershell kann aber nicht nur normale Textdateien erzeugen, sondern etwa auch formatierte CSV-Dateien. Schreiben Sie zum Beispiel
$bigfiles | Export-Csv c:tmpbigfiles.csv
Dann können Sie die Datei „bigfiles.csv“ zum Beispiel direkt in Excel importieren, um Sie weiterzuverarbeiten oder besser formatiert auszugeben.
Skripts speichern und starten
Zum Ausprobieren ist die interaktive Verwendung der Powershell recht praktisch. Haben Sie aber das gewünschte Ziel erreicht, möchten Sie das Skript speichern und beim nächsten Mal direkt starten können. Dazu legen Sie einfach per Notepad oder einem anderen Editor eine Textdatei mit den gewünschten Skriptzeilen an und speichern Sie als Datei mit der Endung „.ps1“.
Sie können das Skript auf verschiedene Arten starten. In der Powershell selbst genügt es, den Namen ohne die Endung einzugeben. Lautet der vollständige Name des Skripts etwa „tmptest.ps1“, dann rufen Sie es so auf
tmptest
Ist das per „Set-Location“ gewählte aktuelle Verzeichnis „tmp“, genügt es sogar, „test“ einzutippen.
Von einer normalen Kommandozeile aus starten Sie Ihr Skript so
powershell c:tmptest.ps1
Allerdings wird mit den Grundeinstellungen keine der Varianten funktionieren, weil die Powershell eine sehr restriktive Sicherheitsstufe aktiviert hat und generell nur signierte Skripts zulässt. Diese Einstellung ändern Sie so ab, dass nur Skripts von fremden Servern signiert sein müssen.
Weil die dazu verwendete Einstellung sicherheitskritisch ist, können Sie sie nur als Administrator vornehmen. Starten Sie dazu eine Powershell per Rechtsklick auf die verknüpfung mittels der Option Als Administrator ausführen.
Hilfestellung erhalten
Eine angenehme Eigenschaft der Powershell ist ihre Auskunftsbereitschaft. Jeder Befehl bringt eine Erläuterung mit, die sich über ein vorangestelltes „Get-Help“ anzeigen lässt.
Noch ausführlichere Informationen inklusive beispielhafter Aufrufe bekommen Sie, wenn Sie dem Konstrukt ein -detailed anhängen. Um etwa alles über den Befehl „Get-Process“ zu erfahren, tippen Sie ein
Get-Help Get-Process –detailed
Nun möchten Sie vielleicht ein Skript schreiben, das unter bestimmten Bedingungen alle Instanzen des Programms „evilproc.exe“ beendet. Denn dieser böse Prozess läuft manchmal Amok und beansprucht dann plötzlich mehrere hundert MByte Arbeitsspeicher.
Da ist „Get-Process“ sicher ein guter Ausgangspunkt, listet es doch alle laufenden Programme auf. Beim Lesen der Beschreibung haben Sie gesehen, dass Get-Process praktischerweise den Namen des Prozesses als optionalen Parameter erlaubt.
Mit der folgenden Eingabe erhalten Sie also eine Sammlung von Prozess-Objekten, die den gewünschten Namen tragen
Get-Process evilproc
Es soll aber nur dann ein solcher Task beendet werden, wenn er übermäßig speicherhungrig ist. Sie müssen also einen entsprechenden Filter anbringen. Aber welche Eigenschaft eines Prozess-Objekts sagt etwas über die RAM-Nutzung aus?. Dazu lassen Sie sich mit „Get-Member“ alle Methoden und Eigenschaften anzeigen, die so ein Objekt hat:
Get-Process | Get-Member
Als Ergebnis erhalten Sie eine ziemlich lange Liste, die zu durchforsten einige Zeit dauert. Grenzen Sie darum die Anzeige auf „Properties“ ein, also die Eigenschaften des Objekts, die etwas über den Status aussagen:
Get-Process | Get-Member -MemberType property
Von den angebotenen Möglichkeiten sieht PrivateMemorySize recht zielführend aus. Folgende Konstruktion liefert Ihnen alle Prozesse mit dem gewünschten Namen, die mehr als 100 MByte RAM beanspruchen:
$evil = Get-Process evilproc
$bigandevil = $evil | ?{$_.PrivateMemorySize -gt 100M}
Wie in Unix heißt in der Powershell die Aktion zum Beenden eines Prozesses „kill“. Sie können also das Vorhaben mit folgender Zeile abschließen:
$bigandevil | kill –verbose
Der „verbose“-Parameter dient dabei dazu, Ihnen anzuzeigen, welche Prozesse „kill“ beendet. Um das Skript zunächst zu testen, wäre es eine gute Idee, bei „kill“ den whatif-Parameter einzusetzen. Dann könnten Sie im Taskmanager unter „Leistung | Ressourcenmonitor“ prüfen, ob die genannten Prozessnummern (PIDs) tatsächlich den von Ihnen vorgegebenen maximalen RAM-Hunger überschreiten.
Schleifen & Co.? Meist gar nicht nötig
In den Beispielen dieses Artikels haben Sie vielleicht einige Elemente vermisst, die Sie von anderen Skriptsprachen kennen, etwa Schleifen, Bedingungen oder selbstdefinierte Funktionen. Ja, auch Powershell verfügt über all diese klassischen Methoden. Nur braucht man sie oft gar nicht, um zum gewünschten Ergebnis zu gelangen.