Microsoft’s Powershell (früher Codename „Monad“) erlaubt es, das gesamte System von einer Zentrale aus zu steuern. Der objektorientierte Ansatz dieser Kommandozeile ist traditionellen Shells, die mühsam Textoutput filtern müssen, um Längen überlegen. Auf der negativen Seite steht eine hölzern-sperrige interaktive Bedienung. Beachten Sie in diesem Zusammenhang auch den Beitrag Powershell für Ubuntu.
Dass Microsoft überhaupt in eine Kommandozeilen-Shell investiert, muss überraschen. Gut, bei Tüftlern, Script-Fans und Admins ist die Befehlszeile nicht totzukriegen – aber Microsoft hat eigentlich genug schlechte Erfahrungen gesammelt, um dieses Feld einfach anderen zu überlassen. Die berüchtigte und wenig genutzte „Eingabeaufforderung“ hat wenig Ruhm geerntet: Die Variante Command.COM unter Windows 98/ME war grauenhaft, die Cmd.EXE von Windows 2000/XP gerade mal alltagstauglich. Normale Windows-Anwender sehen heute kaum noch Anlass, krude Befehle auf einer solchen Kommandozeile einzugeben – sie halten sich an bequeme Mausaktionen.
Ein völlig neues Kapitel: Die Powershell wirft alle Altlasten über Bord und verwirklicht eine ehrgeizige Synthese. Monad verbindet die Fähigkeiten einer Unix-Shell mit dem objektorientierten .NET Framework. Wir erläutern, was das konkret heißt, wer das tatsächlich braucht und wo die Vorteile gegenüber den bekannten Instrumenten liegen (Cmd, Wmic, Windows Scripting Host). Die Powershell ist eindeutig ein Scripting-Instrument für Profis, bietet aber auch Einsatzmöglichkeiten für den normalen Anwender.
1. Was eine Shell (und warum Powershell eine) ist
Unter einer „Shell“ versteht man eine System-Zentrale, die einen
möglichst umfassenden Zugang zu sämtlichen Komponenten eröffnet. Eine
Shell sollte also Informationen zur gesamten Hard- und Software liefern,
alle externen Programme und Scripts starten, ferner Benutzerdateien
selbst bearbeiten oder an Anwendungsprogramme weitergeben. Die grafische
Windows-Shell, der Explorer, bietet jedoch keinen umfassenden Zugang –
an die Registry, die Prozesse oder die Systemdienste etwa kommen Sie nur
über eigene Programme heran – in diesen Fällen Regedit, Task-Manager
und MMC (Management-Konsole). Viele weitere Windows-Komponenten sind
zwar irgendwo integriert. Wo sie jeweils zu finden sind, lässt sich aber
nicht intuitiv erschließen. Eine gute Shell zeichnet sich vor allem
dadurch aus, dass sie alle Programme, Inhalte und Methoden an einem
Punkt integriert. Die Powershell startet praktisch alles, womit man sie
füttert: EXE, BAT, MSC, VBS … Benutzerdateien wie DOC, JPG, XLS, PDF
lädt die Shell direkt in die passende Anwendung. Übrigens: Steht der
Dateiname in Anführungszeichen, muss ein „&“ vorangestellt werden,
um den Befehlszeilenmodus zu erzwingen. Meister aller Klassen: Die
Powershell holt via WMI (Windows Management Instrumentation)
Informationen aus allen verfügbaren Windows-Klassen und steuert
COM-Anwendungen wie den Internet Explorer oder Office-Programme. Ein
Beispiel:
$dlg = new-object -ComObject "shell.application"
$tmp = $dlg.BrowseForFolder(0,"",1,17)
Die beiden Zeilen starten den Windows-Dialog „Ordner suchen“. Der gewählte Ordner landet dann in der Variablen $tmp. Das ist nur ein kleines Beispiel für die Möglichkeiten. Dreh- und Angelpunkt sind die derzeit 153 vordefinierten Befehle, welche die Powershell nach „get-command“ auflistet, Ein erstes „Cmdlet“ („Commandlet“ – so der Name dieser Kommandos) deutet an, dass die Shell Objekte integriert, die bislang nicht unter einen Hut zu bringen waren:
get-psdrive
listet nicht nur die Laufwerke auf, sondern auch die Registry („hklm:“ und „hkcu:“), Variablen und Funktionen. Da diese Objekte als Laufwerke gemountet sind, lassen sie sich auch genauso ansprechen:
cd hklm:\software\classes\exefile
dir
„cd“ und „dir“ sind DOS-freundliche Aliases für die eigentlichen Cmdlets „set-location“ und „get-childitem“. Das Beispiel zeigt, dass die Shell in der Registry, in Funktionen und Variablen ebenso wie in Datei-Ordnern spazierengeht. Ändern, Löschen, Eintragen ist natürlich mit den passenden Cmdlets ebenso möglich. Nur noch zwei Beispiele:
get-itemproperty "hkcu:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
stop-process -processname notepad*
Der erste Befehl zeigt die Windows-Standardordner des aktuellen Benutzers, der zweite beeendet Notepad oder Notepad2.
2. Der Prompt und die Datei Profile.PS1
Von der Bedienung gibt es starke Ähnlichkeiten mit der gewohnten Kommandozeile Cmd.EXE: Das Editieren der Zeilen geschieht auf dem Prompt auf vergleichbare Weise. Das gilt für die automatische Pfad- und Datei-Namenserweiterung mit der-Taste, ebenso wie für die History-Funktion, bei der Sie mitoderdie letzten Befehle zurückholen. Zahlreiche Aliases für die umständlichen Cmdlets orientieren sich an alten Cmd-Kommandos („dir“, „del“, „cls“, „ren“, „md“, „type“ …). Eine eigenes Alias ist mit
new-alias n notepad.exe
schnell angelegt (um mit „n“ den Editor zu starten). Der Wert der Aliases ist aber begrenzt, da sie nur als Kommandoabkürzung taugen und keinerlei Argumente zulassen. Weit leistungsfähiger sind Functions:
function n {notepad.exe $args}
Jetzt kann auf dem Prompt der Dateiname gleich mitgegeben werden („n“). $Args ist eine Standardvariable, die sämtliche oder einzelne ($Args[]) Argumente weitergibt. Da der Powershell-Prompt eine Eingabe wie „3.14 * 6.34“ oder „0x2A1“ (Hexzahl) direkt als Rechenaufgabe interpretiert und löst, können Sie mit etwas Code auch komplexere Aufgaben umsetzen:
function Tage {((get-date("$args"))-(get-date)).days}
Die Eingabe „tage 22.10.2009“ gibt dann die Anzahl der Tage bis zum eingegebenen Datum zurück. Die Powershell geizt mit vorgefertigten Funktionen, aber es ist nur eine Frage der Zeit, bis Anwender selbstgestrickte im Web anbieten werden. Der in einer Funktion abgelegte Code gilt so lange, bis die Shell geschlossen wird. Aber es ist natürlich nicht zumutbar, komplexere Funktionen oder Befehle jedes Mal neu per Hand einzugeben. Hier kommt das Script Profile.ps1 ins Spiel, das die Shell beim Start automatisch einliest. Es kann alle möglichen Variablen, selbstgestrickte Funktionen und Aliases enthalten. Der Standardpfad der aktuellen Version 3.0 ist
%windir%\System32\WindowsPowerShell\v1.0
Eine hier abgelegte Profile.ps1 liest die Shell beim Start in jedem Fall neu ein. PS1-Scripts werden aber erst ausgeführt, wenn die Policy entsprechend eingestellt ist:
Set-ExecutionPolicy unrestricted
3. Der Kern der Powershell: Objekte in der Pipeline
Bis hierher habe ich die Powershell wie eine Unix-ähnliche Shell beschrieben. Damit sind wir aber noch nicht beim Kern der Sache. Der heißt nämlich: Objektorientierung im Verbund mit Pipelines. Pipes wie „Sort“ oder „Find“ unter Cmd filtern einfach Text, Pipes in der Powershell hingegen das gesamte Objekt inklusive aller Eigenschaften und Methoden. Dazu ein Beispiel:
$a=get-process
Das Kommando schreibt die aktuelle Prozessliste in die Variable $a (alle Variablen, gleich welchen Typs, beginnen mit „$“). Einfaches
$a
zeigt dann die Prozessliste mit acht Eigenschaften an. Diese Repräsentation ist aber nur freundliche Formathilfe der Shell. Die Variable enthält in Wahrheit wesentlich mehr:
$a | where {$_.name -like "*explorer*"}|format-list *
Hoppla: Jetzt liefert Variable zu dem einen Prozess „Explorer“ gut die doppelte Menge an Daten, die sie vorher über sämtliche Prozesse zurückgab – eine Unzahl weiterer Eigenschaften, welche die Shell ohne explizite Formatangabe automatisch wegfiltert. Erst „format-list *“ (Kurzform: „fl *“) liefert das Ganze. Um herauszufinden, welche Eigenschaften und Methoden für ein Datenobjekt insgesamt vorliegen, gibt es einen systematischeren Weg:
"Hallo" | get-member
„Hallo“ ist ein String-Objekt, daher erhalten wir eine Liste der .NET-Methoden zur Stringverarbeitung, so etwa „ToUpper“ – und folgerichtig gibt dann
"Hallo".toupper()
„HALLO“ zurück. Über „get-member“ ermittelte Pipes helfen dann, Objekt-Variablen genau auf das inhaltlich Gewünschte einzugrenzen:
$aktuell=dir c:,d:,e: -recurse -include *.doc,*.xls | where {$_.lastaccesstime -gt "1.10.2008"} | select name,lastaccesstime
Schon der „Dir“-Befehl grenzt ein, der „Where“-Filter lässt auch davon nur die jüngst genutzten Dateien übrig, und „Select“ reduziert dann die Objekteigenschaften auf zwei. Das Objekt enthält jetzt nur noch diese zwei Eigenschaften der zuletzt genutzten DOC- und XLS-Dateien auf den Laufwerken C:, D: und E:. Beachten Sie, dass nun für diese Objektvariable scheinbar selbstverständliche Datei-Eigenschaften tatsächlich fehlen: Eine Sortierung nach der Extension („sort extension“) wäre etwa nicht mehr möglich. Aus dem gleichen Grund kann
dir -recurse|select name,length|where {$_.CreationTime -gt "1.10.2008"}
nie ein Ergebnis befördern, weil die Eigenschaft des Erstellungsdatums (CreationTime) durch den Select-Befehl ausgefiltert wurde und nicht mehr existiert. Die zwei Pipes sind also syntaktisch umzustellen. Ein letztes praktisches Beispiel für kombinierte Pipes:
$doppel=dir c: -recurse *.mp3 | group length|where {$_.count -gt 1}|select -expand group| ft name,length
Diese eine Zeile beantwortet eine komplexe Frage: Wenn es auf Laufwerk C: MP3-Dateien gleicher Bytezahl gibt, werden diese untereinander angezeigt. Lautet dann auch noch der Name ähnlich, handelt es sich offenbar um überflüssige Doppel.
4. Powershell mit umfassenden Script-Fähigkeiten
Die Beispielzeilen in diesem Beitrag sind – der Lesbarkeit halber – bewusst knapp gehalten. Komplexe Objekt-Pipes per Hand einzugeben ist weder vergnüglich noch notwendig. Der Platz für globale Funktionen und Variablen ist die schon angesprochene Profile.PS1 , weiterer Code kann in beliebigen Textdateien mit Endung PS1 abgelegt und in der Shell durch Eingabe der Code-Datei abgerufen werden. Die umfangreichen Script-Möglichkeiten, um Objektvariablen weiter zu bearbeiten, können wir hier kaum andeuten. Sie entsprechen in vielen Belangen der Syntax von Unix-Shells, sind aber auch für jeden WSH-oder Batch-Erfahrenen durchaus zugänglich – an „If“, „For“, „Foreach“, „While“ oder „Switch“ in der einen oder anderen Form kommt keine Sprache vorbei. Die Fehlermeldungen des Interpreters sind informativ und zielführend, das Hilfesystem verdient hingegen bislang seinen Namen nicht. Den simpelsten Zugang zum Scripten bieten sicherlich Filter, die den Datenstrom der Pipeline entweder weiterfließen lassen oder nicht. Die einzelne Instanz wird in jedem Filter (auch in „Where“ oder „Foreach“) mit der globalen Variable „$_“ bezeichnet. Darf das Ding oder eine Eigenschaft desselben also durch, schreibt man einfach „$_“ oder „$_.“. Ganz einfaches Beispiel:
Filter Dirs {if ($_.parent) {$_}}
dir -recurse|Dirs|select name
Um mit Kontrollstrukturen wie „If“ oder „While“ Entscheidungen zu treffen, helfen die üblichen Vergleichsperatoren:
-eq gleich (equal)
-ne ungleich (not equal)
-lt kleiner (lower than)
-le kleiner oder gleich (lower or equal)
-gt größer (greater than)
-ge größer oder gleich (greater or equal)
Also etwa:
if ($a -lt 1000) {write-host "Kleiner"}
5. Super-Shell? Nicht jetzt und nicht für jeden …
Objektorientierung und Scriptsprache der Shell sind konzeptionell
beeindruckend. Selbst die sperrigen Cmdlets verlieren zusehends ihren
Schrecken, wenn man sich eingearbeitet hat und ihre konsistente
Grammatik kennt: „get-content“ („gc“) funktioniert einfach immer, wenn
ein „Content“ vorliegt, also bei der Datei Boot.INI ebenso wie bei der
Funktion „Prompt“. Für Fehlerbehandlung („Trap“), Testläufe („-Whatif“
bei Methoden), automatische Erkennung von Variablentypen und saubere
Gültigkeitsbereiche der Variablen („Scopes“) ist ebenso gesorgt wie für
die Erweiterbarkeit: Das .NET-Framework mit dem C#-Compiler enthält
alles, um neue DLLs und darin enthaltene Cmdlets zu erstellen. Die Shell
kann wesentlich mehr als Cmd und der Scripting Host zusammen und macht
nebenbei eine ganze Reihe von Windows-Applets arbeitslos.
Den Geschmack der Admins und Tüftler dürfte die Shell treffen. Den
normalen Anwender wird sie kaum auf die Kommandozeile locken: Das
Editieren der Zeilen ist so spartanisch wie bei Cmd, vor allem aber
mangelt es noch an interessanten, fertigen Funktionen, die sofort
Produktivität versprechen. Die derzeit verfügbare Beta kommt
buchstäblich nackt zum Anwender: Er muss sie erst selbst Funktion um
Funktion zu einem tauglichen Werkzeug machen. Es ist wohl nur eine Frage
der Zeit – aber im Moment sind im Web überzeugende Scripts und
Funktionen noch Mangelware.
Nicht ganz nebensächlich ist ferner der Ressourcenverbrauch: Schon die
nackte Shell ist mit ihren .NET-Bibliotheken kein Leichtgewicht. Einige
größere Objektvariablen mit Dateiobjekten eingelesen, und die Powershell
wird im Handumdrehen zum RAM- und CPU-Konsumenten Nummer 1.
6. Einige Kommandozeilen-Beispiele
Zur Ausgabe auf dem Prompt bietet die Shell verschiedene vordefinierte Farbcodes.
write-host "Starte Vorgang..."
write-warning "Achtung..."
write-error "Fehler..."
So definieren Sie das Erscheinungsbild (hier blaue Schrift auf weißem Hintergrund) und Titel.
$host.UI.RawUI.ForegroundColor = "DarkBlue"
$host.UI.RawUI.BackgroundColor = "White"
$host.ui.rawui.WindowTitle = "Powershell on ${env:userdomain}${env:username}" + " " + (get-date -uformat "%A, %d.%m.%Y [%T]")
Folgende Pipe gibt die 50 größten Dateien auf Laufwerk D: zurück.
Get-ChildItem -recurse d:\ | Select name,length | Sort-Object length -descending | Select-Object -first 50
Ähnlich der folgende Befehl, der Dateigrößen und den kompletten Pfadnamen der 50 größten Dateien zurückgibt:
Get-ChildItem d:\ -recurse | Sort length -descending | Select -first 50 | fl length,fullname
Das folgende Kommando filtert gezielt alle Dateien, die größer sind als 50 MB (Out-Gridview bietet eine grafische Tabelle außerhalb der Powershell):
Get-ChildItem d:\ -recurse | Where {$_.Length -gt 50MB} | select fullname,length | out-gridview
Die nächste Pipe gibt die 10 größten Dateien auf Laufwerk D: zurück und zeigt an, wie lange die Aktion dauert.
measure-command {Get-ChildItem -recurse d: | Select name,length | Sort-Object length -descending | Select-Object -first 10 }
So lesen Sie die Shellfolder aus der Registry in eine Variable und nutzen diese zur Navigation:
$UserFolder=get-itemproperty "HKCU:SoftwareMicrosoftWindowsCurrentVersionExplorerShell Folders"
push-location $UserFolder.Desktop
Folgender Befehl erzwingt eine Pause von 5 Sekunden:
start-sleep -s 5
Die folgenden Befehle erstellen ein COM-Objekt und nutzen es dann zur Fensterdarstellung:
$dlg = new-object -ComObject "shell.application"
$dlg.CascadeWindows()
$dlg.MinimizeAll()
$dlg.UndoMinimizeAll()
Das nachfolgende Kommando (eine Zeile!) listet die Windows-Dienste mit
ihren beschreibenden Namen auf und kennzeichnet alle laufenden Dienste
rot, die beendeten grün:
get-service | foreach {if ($_.status -eq "stopped") {write-host
-f green $_.displayname} else {write-host -f red $_.displayname}}
Das nächste Kommando beendet den Explorer:
stop-process -processname explorer
Die kleine Funktion „n“ verkürzt den Aufruf von Notepad auf die Eingabe
„n“. Die Standardvariable $Args sorgt dafür, dass eine übergebene Datei
in Notepad geöffnet wird:
Function n{Notepad.EXE $args}
n c:\boot.ini
Folgende Funktion liefert eine Tabelle aller verfügbaren Laufwerke mit Typ, Größe und freiem Speicher:
Function Disks {
$Disks = Get-WMIObject Win32_LogicalDisk -computer "."
""
write-host " Kennung Typ GB GB frei" -BackGroundColor DarkRED -ForeGroundColor White
""
ForEach ($d in $Disks) {
$typ=$d.drivetype
Switch ($typ) {
2 {$typ="FDD"}
3 {$typ="HDD"}
4 {$typ="Net"}
5 {$typ="CD "}
}
" {0} {1} {2,7:n} {3,10:n}" -f $D.DeviceID, $typ, $($d.Size/1024/1024/1024), $($d.freespace/1024/1024/1024)
}
""
}
Das nächste Beispiel (eine Zeile!) liefert eine Statistik zu den Dateierweiterungen im aktuellen Ordner (inkl. Unterordner):
dir -recurse|group-object -property extension|sort
count|Format-table
@{expression="name";width=8},@{expression="group";width=65},@{expression="count";width=5}
Wenn Sie mit „format-table“ (oder Kurzform „ft“) die Ausgabe als Tabelle anfordern, lässt die Powershell zwischen den Spalten oft große, störende Zwischenräume. Das lässt sich mit „format-table -autosize“ oder kürzer „ft -auto“ unterbinden:
dir -recurse | sort length | select length,fullname | ft -auto
Folgender Ablauf könnte in einem PS1-Script erfolgen: Aufruf eines Tasks – Warten auf dessen Abschluss – Fortsetzen des Scripts:
notepad.exe
$a=get-process notepad
$a.WaitForExit()
write-host "Weiter..."
Die nachfolgenden Zeilen greifen eine Webseite ab und legen den Inhalt in einer Textdatei ab:
$wc = new-object System.Net.WebClient
$wc.DownloadString('http://www.beispiel.de/index.html') | out-file s.txt
get-content s.txt
Das nächste Beispiel zeigt eine einfache Textsuche mit dem Cmdlet Select-String:
Select-String -path *.txt -SimpleMatch "Monitor"
Um dieselbe Suche auf alle Unterverzeichnisse auszuweiten, hilft folgende Kombination mit Get-Childitem (oder gci) und dem Parameter -recurse:
gci -include *.txt -recurse | Select-String -SimpleMatch "Monitor"
Eine seit Version 2.0 eingeführte attraktive Out-Variante ist „Gridview“. Die Objektdaten werden grafisch dargestellt und lassen sich filtern und sortieren:
get-service | select status, name, displayname, servicesdependedon | out-gridview