Windows PowerShellZeilenweise Skripterstellung

Don Jones

In früheren Beiträgen habe ich die Tatsache betont, dass Windows PowerShell eine Shell ist. Windows PowerShell ist für die interaktive Verwendung gedacht – im Gegensatz zur Shell cmd.exe (bzw. Eingabeaufforderung), die Sie wahrscheinlich schon kennen. Windows PowerShell unterstützt jedoch trotzdem eine Skriptsprache, und zwar eine Skriptsprache, die robuster ist als die Stapelsprache von cmd.exe,

und ebenso leistungsfähig wie andere Sprachen (z. B. VBScript), wenn nicht sogar leistungsfähiger. Die Tatsache, dass es sich bei Windows PowerShell™ um eine interaktive Shell handelt, macht das Erlernen der Skripterstellung ausgesprochen einfach. Sie können Skripts sogar interaktiv innerhalb der Shell entwickeln und zeilenweise schreiben, sodass Sie die Ergebnisse Ihrer Arbeit sofort vor sich haben.

Diese iterative Skripterstellungstechnik erleichtert außerdem das Debuggen. Da Sie die Ergebnisse Ihrer Skripterstellung sofort sehen, können Sie das Skript schnell überarbeiten, wenn das Ergebnis nicht wie erwartet ausfällt.

In diesem Beitrag wird die interaktive Skripterstellung in Windows PowerShell anhand eines Beispiels vorgeführt. Es wird ein Skript erstellt, das Dienstnamen aus einer Textdatei liest und den Startmodus der jeweiligen Dienste so festlegt, dass sie deaktiviert sind.

Dieser Beitrag soll Ihnen das Konzept näher bringen, Windows PowerShell-Skripts in kleinen Schritten zu erstellen, statt ein ganzes Skript in einem Stück anzugehen. Sie können sich eine beliebige Verwaltungsaufgabe vornehmen, sie in ihre Bestandteile zerlegen und anschließend ausarbeiten, wie Sie die Bestandteile unabhängig voneinander funktionsfähig machen. Windows PowerShell stellt ausgezeichnete Möglichkeiten bereit, um die Teile zu verbinden. Dies wird im Weiteren erörtert. Dabei wird auch deutlich, dass Sie das endgültige Skript leichter schreiben können, wenn Sie stückweise vorgehen.

Lesen von Namen aus einer Datei

Herauszufinden, wie Textdateien in Windows PowerShell gelesen werden, kann eine Geduldsprobe darstellen. Wenn ich „Help *file*“ ausführe, wird nur die Hilfe für das Cmdlet „Out-File“ angezeigt, das einer Datei Text sendet, nicht jedoch Text liest. Das ist natürlich keine große Hilfe. Cmdlet-Namen in Windows PowerShell haben jedoch eine Art von Logik, die ich zu meinem Vorteil nutzen kann. Wenn Windows PowerShell ein Element abruft, beginnt der Cmdlet-Name gewöhnlich mit „Get“ (Abrufen). Daher führe ich „Help Get*“ aus, um diese Cmdlets anzuzeigen. Anschließend führe ich einen Bildlauf durch die Liste aus und finde Get-Content. Dies bringt mich meinem Ziel schon näher. Ich führe nun „Help Get-Content“ aus, um weitere Informationen anzuzeigen (siehe Abbildung 1), und erhalte das gewünschte Ergebnis.

Abbildung 1 Ausführen von „Help Get-Content“, um weitere Informationen anzuzeigen

Abbildung 1** Ausführen von „Help Get-Content“, um weitere Informationen anzuzeigen **(Klicken Sie zum Vergrößern auf das Bild)

Windows PowerShell behandelt nahezu alles wie ein Objekt, und Textdateien stellen dabei keine Ausnahme dar. Eine Textdatei ist grundsätzlich eine Sammlung von Zeilen, wobei jede Zeile in der Datei eine Art unabhängiges Objekt darstellt. Nachdem eine Textdatei namens „C:\services.txt“ erstellt und mit Dienstnamen versehen wurde (jeder Name erhält seine eigene Zeile innerhalb der Datei), kann Windows PowerShell mithilfe des Cmdlets „Get-Content“ die Namen einzeln lesen. Da die vorliegende Anleitung zeigen soll, wie Skripts interaktiv entwickelt werden, wird als Erstes „Get-Content“ ausgeführt, wobei der Name der Beispieltextdatei verwendet wird, um zu sehen, was passiert:

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

Wie erwartet liest Windows PowerShell die Datei und zeigt den Namen an. Die Anzeige der Namen ist natürlich nicht das eigentliche Ziel. Jetzt ist jedoch klar, dass Get-Content erwartungsgemäß funktioniert.

Ändern eines Diensts

Als Nächstes soll der Startmodus eines Diensts geändert werden. Der erste Schritt ist wiederum das Finden des richtigen Cmdlets. Ich führe daher „Help *Service*“ aus. Daraufhin wird eine Auswahlliste zurückgegeben, und nur das Cmdlet „Set-Service“ scheint in diesem Falle nützlich zu sein. Ich will dieses Cmdlet testen, um sicherzustellen, dass ich verstehe, wie es funktioniert, bevor ich versuche, es in ein Skript einzufügen. Durch Ausführen von „Help Set-Service“ wird angezeigt, wie das Cmdlet funktioniert, und ein schneller Test bestätigt dies:

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

Kombinieren der Teile

Jetzt muss ich die Möglichkeit, Dienstnamen aus einer Datei zu lesen, mit dem Cmdlet „Set-Service“ kombinieren. An dieser Stelle kommen die leistungsfähigen Pipelinefunktionen von Windows PowerShell zum Einsatz. Mit der Pipeline kann eine Cmdlet-Ausgabe als Eingabe an ein zweites Cmdlet weitergeleitet werden. Die Pipeline leitet ganze Objekte weiter. Falls eine Sammlung von Objekten in die Pipeline gestellt wird, wird jedes Objekt einzeln durch die Pipeline geleitet. Das bedeutet, dass die Ausgabe von Get-Content, die eine Sammlung von Objekten ist, an Set-Service geleitet werden kann. Da Get-Content eine Sammlung weiterleitet, wird jedes Objekt bzw. jede Textzeile in der Sammlung einzeln an Set-Service geleitet. Das Ergebnis ist, dass Set-Service einmal für jede Zeile in meiner Textdatei ausgeführt wird. Der Befehl sieht folgendermaßen aus:

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

Es geschieht Folgendes:

  1. Das Cmdlet „Get-Content“ wird ausgeführt und liest die gesamte Datei. Jede Zeile in der Datei wird als ein eindeutiges Objekt behandelt, und zusammen stellen die Zeilen eine Sammlung von Objekten dar.
  2. Die Sammlung von Objekten wird an Set-Service geleitet.
  3. Die Pipeline führt das Cmdlet „Set-Service“ einmal für jedes Eingabeobjekt aus. Für jede Ausführung wird das Eingabeobjekt als der erste Parameter des Cmdlets, bei dem es sich um den Dienstnamen handelt, an Set-Service geleitet.
  4. Set-Service wird ausgeführt, wobei das Eingabeobjekt für seinen ersten Parameter bzw. alle anderen angegebenen Parameter verwendet wird, in diesem Fall für den Parameter „-startuptype“.

Interessanterweise ist die Aufgabe an diesem Punkt erledigt, und dabei ist das Skript noch nicht einmal geschrieben. Es wäre schwierig, dieselbe Aktion in der Cmd.exe-Shell auszuführen, und in VBScript wäre hierfür ein Dutzend Zeilen von Code erforderlich. Windows PowerShell wickelt all dies jedoch in einer einzigen Zeile ab. Die Aufgabe ist jedoch noch nicht ganz abgeschlossen. Wie Sie sehen können, stellt der Befehl nicht viel Statusausgabe oder Feedback bereit. Abgesehen davon, dass keine Fehler angezeigt wurden, ist es schwer festzustellen, ob tatsächlich ein Aktion ausgeführt wurde. Jetzt, da ich die Funktionen im Griff habe, um die Aufgabe abzuschließen, kann ich am Aussehen arbeiten, indem ich mit der eigentlichen Skripterstellung beginne.

„Windows PowerShell Prompt Here“ von Michael Murgolo

Zu den beliebtesten Microsoft® PowerToys für Windows® (und zu meinen Favoriten) gehört das Tool „Eingabeaufforderung hier öffnen“. Das Tool „Eingabeaufforderung hier öffnen“ ist als Teil von Microsoft PowerToys für Windows XP bzw. der Windows Server® 2003 Resource Kit Tools verfügbar. Mit diesem Tool können Sie durch Rechtsklicken auf einen Ordner oder ein Laufwerk in Windows-Explorer ein Befehlsfenster öffnen, das auf den ausgewählten Ordner zeigt.

Beim Erlernen der Verwendung von Windows PowerShell wurde mir bewusst, dass ich mir die gleiche Funktionalität wünschte. Aus diesem Grund änderte ich die setup.inf-Datei von „Eingabeaufforderung hier öffnen“ (cmdhere.inf) aus den Windows Server 2003 Resource Kit Tools, um ein Kontextmenü „Windows PowerShell Prompt Here“ (Windows PowerShell Eingabeaufforderung hier) zu erstellen. Diese .inf-Datei ist im ursprünglichen Blogbeitrag enthalten, auf dem diese Randleiste beruht (verfügbar unter leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx). Um das Tool zu installieren, klicken Sie einfach mit der rechten Maustaste auf die .inf-Datei, und wählen Sie „Install“ (Installieren) aus.

Beim Erstellen der Windows PowerShell-Version dieses Tools habe ich im Original einen Fehler entdeckt: Beim Deinstallieren von „Eingabeaufforderung hier öffnen“ blieb ein deaktivierter Kontextmenüeintrag übrig. Deshalb habe ich eine aktualisierte Version von cmdhere.inf bereitgestellt. Sie finden sie ebenfalls im ursprünglichen Blogbeitrag.

Diese beiden PowerToys nutzen die Tatsache aus, dass diese Kontextmenüeinträge in der Registrierung unter Schlüsseln konfiguriert sind, die mit den Verzeichnis- und Laufwerksobjekttypen (Directory und Drive) verknüpft sind. Dies geschieht in derselben Weise, in der Kontextmenüaktionen mit Dateitypen verknüpft werden. Wenn Sie zum Beispiel mit der rechten Maustaste auf eine .txt-Datei in Windows-Explorer klicken, werden mehrere Aktionen am Anfang der Liste angezeigt (z. B. Öffnen, Drucken und Bearbeiten). Um zu verstehen, wie diese Elemente konfiguriert sind, wird als Nächstes die Registrierungsstruktur HKEY_CLASSES_ROOT untersucht.

Wenn Sie den Registrierungs-Editor öffnen und den Zweig HKEY_CLASSES_ROOT erweitern, sehen Sie, dass Schlüssel für bestimmte Dateitypen (wie .doc, .txt und Ähnliches) benannt sind. Wenn Sie auf den .txt-Schlüssel klicken, sehen Sie, dass der Wert (standardmäßig) „txtfile“ lautet (siehe Abbildung A). Dies ist der Objekttyp, der mit einer .txt-Datei verknüpft wird. Wenn Sie einen Bildlauf nach unten durchführen, den txtfile-Schlüssel erweitern und anschließend den Shellschlüssel darunter erweitern, sehen Sie die Schlüssel, die für manche der Kontextmenüeinträge für eine .txt-Datei benannt wurden. (Sie werden nicht alle sehen, weil es andere Methoden zum Erstellen von Kontextmenüs gibt.) Unter jedem dieser Schlüssel befindet sich ein Befehlsschlüssel. Der (standardmäßige) Wert unter dem Befehlsschlüssel ist die Befehlszeile, die Windows ausführt, wenn Sie das entsprechende Kontextmenüelement auswählen.

Die Tools für die cmd-Eingabeaufforderung und die Windows PowerShell-Eingabeaufforderung verwenden dieses Verfahren, um die Kontextmenüeinträge von „Eingabeaufforderung hier öffnen“ und „Windows PowerShell Prompt Here“ zu konfigurieren. Es gibt keine Dateitypen, die mit Laufwerken und Verzeichnissen verknüpft sind. Es gibt jedoch Laufwerks- und Verzeichnisschlüssel unter HKEY_CLASSES_ROOT, die mit diesen Objekten verknüpft sind.

Abbildung A Kontextmenüeinträge werden in der Registrierung konfiguriert

Abbildung A** Kontextmenüeinträge werden in der Registrierung konfiguriert **(Klicken Sie zum Vergrößern auf das Bild)

Michael Murgolo ist ein leitender Infrastrukturberater für Microsoft Consulting Services. Er ist auf die Bereiche Betriebssysteme, Bereitstellung, Netzwerkdienste, Active Directory, Systemverwaltung, Automatisierung und Patchverwaltung spezialisiert.

Interaktive Skripterstellung

Eine meiner Aufgabenstellungen besteht darin, für jeden Dienst, der in C:\services.txt aufgelistet ist, mehrere Cmdlets auszuführen. So kann ich den gewünschten Dienstnamen und andere Informationen ausgeben, damit der Fortschritt des Skripts nachverfolgt werden kann.

Zuvor habe ich die Pipeline verwendet, um Objekte von einem Cmdlet an ein anderes weiterzuleiten. Diesmal werde ich jedoch ein Verfahren anwenden, das mehr einem Skript ähnelt und als Foreach-Konstrukt bezeichnet wird (dies wurde im letzten Monat in einem Beitrag vorgestellt). Das Foreach-Konstrukt kann eine Sammlung von Objekten annehmen und führt für jedes Objekt der Sammlung mehrere Cmdlets aus. Ich bestimme eine Variable, die das aktuelle Objekt in jedem Durchlauf darstellt. Das Konstrukt könnte zum Beispiel folgendermaßen beginnen:

foreach ($service in get-content c:\services.txt)

Ich führe nach wie vor das Cmdlet „Get-Content“ aus, um den Inhalt der Textdatei abzurufen. Diesmal soll das Foreach-Konstrukt die Sammlung der Objekte durchlaufen, die von Get-Content zurückgegeben werden. Die Schleife wird für jedes Objekt einmal ausgeführt, und das aktuelle Objekt wird in die Variable „$service“ gestellt. Jetzt muss ich nur den Code angeben, der innerhalb der Schleife ausgeführt werden soll. Ich dupliziere zunächst meinen ursprünglichen einzeiligen Befehl (dadurch wird die Komplexität minimiert und sichergestellt, dass die Funktionalität nicht beeinträchtigt wird):

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

Dies ist ein höchst interessanter Vorgang. Beachten Sie, dass ich die erste Zeile des Foreach-Konstrukts mit einer öffnenden geschweiften Klammer abgeschlossen habe. Alles innerhalb der öffnenden und schließenden geschweiften Klammern befindet sich in der Foreach-Schleife und wird einmal für jedes Objekt in der Eingabesammlung ausgeführt. Nach Eingabe von { und Drücken der Eingabetaste wurde die Eingabeaufforderung von Windows PowerShell in >> geändert (siehe Abbildung 2). Dies zeigt an, dass Windows PowerShell erkennt, dass ein Konstrukt begonnen wurde, und auf das Abschließen des Konstrukts wartet. Ich habe anschließend mein Cmdlet „Set-Service“ eingegeben. Diesmal wurde $service als erster Parameter verwendet, da $service den aktuellen aus meiner Textdatei gelesenen Dienstnamen darstellt. In der nächsten Zeile beende ich das Konstrukt mit einer schließenden geschweiften Klammer. Nach zweimaligen Drücken der Eingabetaste führt Windows PowerShell meinen Code sofort aus.

Abbildung 2 Windows Powershell erkennt, dass ein Konstrukt begonnen wurde

Abbildung 2** Windows Powershell erkennt, dass ein Konstrukt begonnen wurde **(Klicken Sie zum Vergrößern auf das Bild)

Meine Eingabe ähnelt zwar einem Skript, Windows PowerShell führt die Eingabe jedoch direkt aus, und sie wird in keiner Textdatei gespeichert. Jetzt werde ich alles noch einmal eingeben und dabei eine Codezeile hinzufügen, die den aktuellen Dienstnamen ausgibt:

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

Beachten Sie, dass ich eine Anzeigeaufforderung eingeschlossen und diese Aufforderung in doppelte Anführungszeichen gesetzt habe. Die Anführungszeichen teilen Windows PowerShell mit, dass dies eine Textzeichenfolge und kein weiterer Befehl ist. Wenn Sie jedoch doppelte Anführungszeichen statt einfacher verwenden, durchsucht Windows PowerShell die Textzeichenfolge nach Variablen. Wenn eine Variable gefunden wird, wird der eigentliche Wert der Variablen durch den Variablennamen ersetzt. Folglich können Sie sehen, dass der aktuelle Dienstname angezeigt wird, wenn dieser Code ausgeführt wird.

Dies ist jedoch noch kein Skript.

Bis zu diesem Zeitpunkt habe ich Windows PowerShell interaktiv verwendet, was eine hervorragende Möglichkeit darstellt, um die Ergebnisse meiner Arbeit unmittelbar anzuzeigen. Doch nach einer gewissen Zeit wird das wiederholte Schreiben dieser Codezeilen zu einer aufwändigen Angelegenheit. Aus diesem Grund kann Windows PowerShell auch Skripts ausführen. Bei Windows PowerShell-Skripts wird der Begriff „Skript“ wörtlich genommen: Windows PowerShell liest die Skripttextdatei und gibt jede gelesene Zeile genau so ein, als ob Sie die Zeilen manuell schreiben würden. Das heißt, alles, was ich zuvor ausgeführt habe, kann in eine Skriptdatei eingefügt werden. Ich verwende daher Editor, um eine Datei mit dem Namen „disableservices.ps1“ zu erstellen, und füge Folgendes ein:

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

Diese Datei wurde in einem Ordner mit dem Namen „C:\Test“ gespeichert. Jetzt versuche ich, die Datei von Windows PowerShell aus auszuführen:

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

Der Versuch schlägt fehl. Was ist schief gegangen? Windows PowerShell ist auf den Ordner „C:\Test“ ausgerichtet, hat das Skript jedoch nicht gefunden. Warum? Aufgrund von Sicherheitseinschränkungen wurde Windows PowerShell so konzipiert, dass kein Skript vom aktuellen Ordner ausgeführt werden kann. Dadurch wird verhindert, dass über ein Skript ein unberechtigter Zugriff auf einen Betriebssystembefehl erfolgt. Es ist beispielsweise nicht möglich, ein Skript mit dem Namen „dir.ps1“ zu erstellen, um damit den normalen dir-Befehl außer Kraft zu setzen. Wenn ein Skript vom aktuellen Ordner ausgeführt werden soll, muss ein relativer Pfad angegeben werden:

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

Leider funktioniert dies immer noch nicht. Ich habe den richtigen Pfad eingegeben, erhalte jedoch eine Meldung von Windows PowerShell, dass das Ausführen von Skripts nicht möglich ist. Die Ursache hierfür ist, dass Windows PowerShell Skripts standardmäßig nicht ausführen kann. Auch dies ist eine Sicherheitsvorkehrung, die dazu dient, die Probleme zu vermeiden, die bei der Verwendung von VBScript auftraten. Schädliche Skripts können unter Windows PowerShell standardmäßig nicht ausgeführt werden, da das Ausführen von Skripts standardmäßig nicht möglich ist. Um mein Skript auszuführen, muss ich die Ausführungsrichtlinie explizit ändern:

PS C:\test> set-executionpolicy remotesigned

Die Ausführungsrichtlinie „RemoteSigned“ ermöglicht das Ausführen unsignierter Skripts vom lokalen Computer. Heruntergeladene Skripts müssen zunächst signiert werden, damit sie ausgeführt werden können. Grundsätzlich stellt die Verwendung der Richtlinie „AllSigned“ die bessere Wahl dar. Mit dieser Richtlinie werden nur Skripts ausgeführt, die mit einem Zertifikat von einem vertrauenswürdigen Herausgeber digital signiert wurden. Da ich jedoch kein Zertifikat zur Hand habe, kann ich meine Skripts nicht signieren. In diesem Falle bietet sich daher die Verwendung der Richtlinie „RemoteSigned“ an. Jetzt versuche ich erneut, mein Skript auszuführen:

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

Beachten Sie, dass die hier verwendete Ausführungsrichtlinie „RemoteSigned“ keine optimale, sondern lediglich eine angemessene Lösung darstellt. Für eine sicherere Lösung empfiehlt sich Folgendes: Rufen Sie ein Code-signiertes Zertifikat ab, verwenden Sie das Windows PowerShell-Cmdlet „Set-AuthenticodeSignature“ zum Signieren der Skripts, und legen Sie als Ausführungsrichtlinie die wesentlich sicherere Ausführungsrichtlinie „AllSigned“ fest.

Darüber hinaus gibt es eine andere Richtlinie, die Richtlinie „Unrestricted“, deren Verwendung Sie auf jeden Fall vermeiden sollten. Mit dieser Richtlinie wird zugelassen, dass alle Skripts, sogar schädliche Skripts von Remotestandorten, ungehindert auf Ihrem Computer ausgeführt werden, und das kann Sie in eine gefährliche Lage bringen. Aus diesem Grund wird von der Verwendung der Richtlinie „Unrestricted“ kategorisch abgeraten.

Unmittelbare Ergebnisse

Mit den interaktiven Skriptfunktionen in Windows PowerShell können Sie schnell ein Musterskript oder kleine Skriptteile erstellen. Da das Ergebnis unmittelbar angezeigt wird, können Sie Ihre Skripts leicht anpassen, um die gewünschten Ergebnisse zu erzielen. Abschließend können Sie Ihren Code in eine .ps1-Datei verschieben. Auf diese Weise ist er stets leicht zugänglich. Denken Sie daran: Im Idealfall sollten Sie diese .ps1-Dateien digital signieren, damit Sie die Windows PowerShell-Ausführungsrichtlinie „AllSigned“, die sicherste Richtlinie zur Skriptausführung, nicht ändern müssen.

Don Jones ist der führende Experte für die Skripterstellung bei SAPIEN Technologies und Mitverfasser von Windows PowerShell: TFM (siehe www.SAPIENPress.com). Sie erreichen Don Jones unter don@sapien.com.

© 2008 Microsoft Corporation und CMP Media, LLC. Alle Rechte vorbehalten. Die nicht genehmigte teilweise oder vollständige Vervielfältigung ist nicht zulässig.