ForEach und die Pipeline-Variable richtig einsetzen

19. November 2014

Nichts geht mehr ohne die Powershell – viele Administratoren werden diese Aussage unterschreiben. Nicht nur von Microsoft, auch Dritthersteller wie VMware liefern für die Konfiguration ihrer Software gleich entsprechende Powershell-Module mit.

Welche Möglichkeiten das Zusammenspiel des Cmdlets ForEach mit der „Pipeline-Variablen“ bietet, zeigt Mark MInasi in diesem Beitrag.

Wer viele Verwaltungsaktionen in einem Schwung ausführen muss, dem hilft die Powershell sehr gut weiter. Hier kommt zum einen das Cmdlet ForEach ins Spiel und das lässt sich bestens mit der „Pipeline-Variablen“ $_ kombinieren. In dieser Konstellation entstehen sehr mächtige „Instrumente“, die oftmals nur aus einer einzigen „Skriptzeile“ bestehen.

Nun mag diese Ausführung sich zunächst recht theoretisch anhören – doch ein kurzes recht einfach gehaltenes Beispiel verdeutlicht diese Aussage. Angenommen das Unternehmen „bigfirm.com“ besitzt eine Organisationseinheit (OU, Organizational Unit) namens „Machinists“. In dieser OU soll nun das „Description“-Attribut eines jeden Benutzerkontos (in dieser OU) den Wert „Machinist“ zugewiesen bekommen.

Diese Aktion kann der Administrator mit dem folgenden „Powershell-Einzeiler“ (wegen der Lesbarkeit läuft der Befehl hier dann allerdings doch über mehrere Zeilen) ausführen

get-aduser -filter * -searchbase "ou=machinists,dc=bigfirm,dc=com" |
set-aduser -description "Machinist"

Dieser Befehl lässt sich durch den Einsatz von ForEach und der Variablen $_ wie folgt umbauen:

get-aduser -filter * -searchbase "ou=machinists,dc=bigfirm,dc=com" |
foreach {set-aduser $_ -description "Machinist"}

Dieser zweite Ansatz führt exakt dieselben Aktionen aus wie das erste Beispiel. Doch die Konstruktion im zweiten Code-Beispiel ist doch komplizierter – daher stellt sich die Frage: Warum soll man diese zweite Version generell in Betracht ziehen. Die Antwort darauf lautet simpel: Diese zweite Version erweist sich als weitaus flexibler.

Dazu erneut ein Beispiel: Angenommen es soll im Attribut „description“ nicht der Begriff „Machinist“ eingetragen werden, sondern etwas persönlicheres, wie zum Beispiel der Vorname des Benutzers gefolgt von „the Machinist“. Das würde dann zum Beispiel lauten: „Dan the Machinist“. Diese Aufgabe kann der Administrator nicht ohne eine Foreach-Konstruktion lösen.

Will man diese Aktion ausführen, sollten folgende Überlegungen einfließen: Im Active Directory gibt es das Attribut „givenname“, das den Vornamen des Benutzers enthält. Dieses Attribut kann mit Hilfe des Cmdlets Get-ADUser ermittelt werden, wie es im folgenden Code-Beispiel zu sehen ist:

$someuser = (get-aduser jdorn)
$someuser.givenname

Hier wird auf das Attribut „givenname“ des Benutzerobjekts „jdorn“ zugegriffen. Die Elemente in der Pipeline liegen bereits in einer Variablen (der eingebauten Variable $_) und nach dem Cmdlet Get-ADUser ist ein Benutzerobjekt in der Pipeline, so dass der Ausdruck „$_.givenname“ den Vornamen des Benutzers enthält.
Um nun zwei Text-.Strings aneinanderzufügen, muss man das Pluszeichen (+) heranziehen. Wenn man also an den jeweiligen Benutzervornamen den Text „the Machinist“ anhängen möchte, dann wird der zugehörige Code in der Powershell wie folgt aussehen:

$_.givenname + "the Machinist"

Damit sollte dann der komplette Code wie folgt aussehen:

get-aduser -filter * -searchbase
"ou=machinists,dc=bigfirm,dc=com" |
set-aduser -description ($_.givenname + "the Machinist")

Doch leider funktioniert das in der Powershell nicht. Somit stellt sich die Frage – Warum? Die mit der Powershell aufgebauten einzeiligen Befehle sind zwar recht mächtig – oftmals ersetzt eine Powershell-Zeile einen VBScript-basierten Code, der sich über 50 und mehr Zeilen erstreckt. Denn bei genauer Betrachtung maskiert die Powershell die Komplexität, die bei einzelnen Entscheidungen und bei Schleifendurchläufen ins Spiel kommt. Doch das lässt sich in komplexeren Fällen – etwa wenn man die Variable $_ in der rechten Seite der Pipeline verwendet (wie in dem hier gezeigten Beispiel) – nicht realisieren. Hier lautet die etwas vereinfachte Regel: Immer wenn man $_ in der rechten Seite einer Pipeline verwendet, muss man die ForEach-Konstruktion zusammen mit einem „Scriptblock“ verwenden. Diese Vereinfachung gilt zumindest für alle „AD-Cmdlets“.

Daher muss man das vorherige Cmdlet etwas umarbeiten. Und einen etwas komplexeren ForEach-Block einbauen. Das sieht im Kontext des bereits angegebenen Code-Beispiels wie folgt aus:

get-aduser -filter * -searchbase "ou=machinists,dc=bigfirm,dc=com" |
foreach {set-aduser $_ -description ($_.givenname + "the Machinist")}

In diesem Fall ist der Einsatz des ForEach-Blocks keine schwierige Angelegenheit. Man muss sich nur immer vor Augen halten, dass die meisten Einzeiler in der Powershell nach dem Prinzip „Filter – dann die betreffende Aktion“ funktionieren. Ein einfaches Beispiel dazu lautet:

get-aduser | unlock-adaccount

Mit get-aduser bekommt man die gewünschten Objekte (ohne sie weiter einzuschränken) und mit unlock-adaccount erfolgt die eigentliche Arbeit.
Um nun die gewünschte Konstruktion passend für den Einsatz mit ForEach zu machen, muss der Administrator die folgenden Schritte ausführen:

Der „Filter“ – also der Teil links von der Pipeline bleibt unverändert. Die Pipeline selbst bleibt ebenfalls stehen, doch danach wird mit ForEach und der geöffneten geschweiften Klammer der Skriptblock begonnen. Das Cmdlet, das die eigentliche Arbeit – auf der rechten Seite des Pipeline-Symbols ausführt, muss den Namen des Benutzerkontos wissen, das geändert werden soll. Doch in der ForEach-Umgebung sieht es nicht dieses einzelne Objekt so wie es bei einer „normalen“ Einzeiler-Konstruktion anzutreffen ist. Daher muss man den Namen des Benutzerkontos in $_ sozusagen übergeben. Dann wird aus dem vorherigen

unlock-adaccount

ein

unlock-adaccount $_

Danach muss man nur noch den gewünschten Rest des Kommandos angeben und schon kann man mit der Konstruktion alle beliebigen Properties des Benutzerkontos in der Pipeline bearbeiten – wie etwa das Erweitern von „givenname“ durch „the Machinist“. Dabei sollte man aber beachten, dass das Cmdlet Get-ADUser standardmäßig nur etwa 10 der Properties zurückgibt – wer alle Properties benötigt, der muss noch den Parameter -properties mit angeben. Und zu guter Letzt muss der Administrator dann auch noch den Code-Block mit einer geschlossenen geschweiften Klammer abschließen.

Mark Minasi/rhh

Lesen Sie auch