In unserer Mini-Serie "Windows Powershell in der Praxis" zeigen wir Ihnen anhand echter Kunden Projekte, Teile der daraus gewonnenen Erkenntnisse auf. Im heutigen Artikel zeigt uns Jan Moser auf, wie dank der XML Verarbeitung mit Powershell seine Tätigkeit markant vereinfacht wurde, ohne dass er ein XML-Gurus mit XPath Kenntnissen sein musste... Meinen herzlichen Dank für diesen Beitrag geht an Herrn Jan Moser!

Jan Moser, dipl.  Inf. Ing. FH:

„eines unserer grossen Probleme in unsern MIIS Projekten ist meist, dass wir die Management Agent Konfigurationen in den verschiedenen Deployment Schritten (Development-Integration-Produktion) durch die entsprechenden Umgebungsvariablen ersetzen müssen...seit Powershell ist das Leben da für mich um einiges einfacher geworden. Da ein Management Agent Export XML nicht wirklich einfach zu lesen und zu verstehen ist habe ich den Kern der Sache extrahiert und in diesem vereinfachten Beispiel die (meiner Ansicht als Developer nach) coolen Features von Powershell herausgehoben“

XML ist heute allgegenwärtig. Sei es als Austauschformat verschiedener Programme und Plattformen, oder als strukturierte Ablage von Konfigurations-, ja zuweilen auch von Applikationsdaten. Der Umgang mit XML Daten kann in einer Scriptsprache mitunter eine etwas mühsame Angelegenheit sein. Powershell bietet jedoch einen intuitiven und einfachen Weg, mit solchen XML Strukturen umzugehen. Betrachen wir folgendes XML Dokument:

<region name=“Switzerland“>
    <country>Switzerland</country>
    <DomainPrefix>CH</DomainPrefix>
    <locations>
        <location name=“Zurich“>
            <server>
                <name>Caesar</name>
                <ip_address>192.168.1.1</ip_address>
                <domain>ZH</domain>
                <OS>Windows</OS>
            </server>
            <server>
                <name>Augustus</name>
                <ip_address>192.168.1.2</ip_address>
                <domain>ZH</domain>
                <OS>Unix</OS>
            </server>
            <server>
                <name>Nero</name>
                <ip_address>192.168.1.3</ip_address>
                <domain>ZH</domain>
                <OS>Unix</OS>
            </server>
        </location>
        <location name=“Berne“>
        <server>
                <name>London</name>
                <ip_address>192.168.10.1</ip_address>
                <domain>BE</domain>
                <OS>Windows</OS>
            </server>
            <server>
                <name>Paris</name>
                <ip_address>192.168.10.2</ip_address>
                <domain>BE</domain>
                <OS>Windows</OS>
            </server>
            <server>
                <name>Rom</name>
                <ip_address>192.168.10.3</ip_address>
                <domain>BE</domain>
                <OS>Windows</OS>
            </server>
        </location>
        <location name=“Geneva“>
        <server>
                <name>Dali</name>
                <ip_address>192.168.20.1</ip_address>
                <domain>GE</domain>
                <OS>Unix</OS>
            </server>
            <server>
                <name>Renoir</name>
                <ip_address>192.168.20.2</ip_address>
                <domain>GE</domain>
                <OS>Unix</OS>
            </server>
            <server>
                <name>Monet</name>
                <ip_address>192.168.20.3</ip_address>
                <domain>GE</domain>
                <OS>Windows</OS>
            </server>
        </location>
    </locations>
</region>

Wir können dieses ganz einfach durch folgenden Powershell Befehl einlesen:

PS C:\Users\Jan> [xml]$regiondoc = get-content c:\ps\regions.xml

Durch das Casten des Dokumentes in eine XML Struktur mit der Anweisung [xml] wird das eingelesene Objekt in ein XML Dokument umgewandelt. Powershell bietet nun eine einfache Zugriffsmöglichkeit auf die Elemente des Dokumentes. Wollen wir beispielsweise das Element Domainprefix aus dem Dokument ausgeben, können wir dies durch einfache Eingabe folgender Instruktion tun:

PS C:\Users\Jan> $regiondoc.region.domainprefix

CH


Schauen wir uns nun einmal an, was nachfolgende Befehlszeile liefert

PS C:\Users\Jan> $regiondoc.region.locations

location

--------

{Zurich, Berne, Geneva}

Das Element Locations stellt einen Container für mehrere Elemente vom Typ Location dar. Powershell stellt diese intern als Array of XMLElement dar, was uns folgende Möglichkeiten eröffnet:

Einerseits können wir in einem Loop auf die einzelnen Elemente zugreifen:

PS C:\Users\Jan> $regiondoc.region.locations.Location | %{$_.name}

Zurich

Berne

Geneva

Oder aber wir greifen direkt auf einzelne Elemente zu:

PS C:\Users\Jan> $regiondoc.region.locations.location[0].name

Zurich

PS C:\Users\Jan> $regiondoc.region.locations.location[1].name

Berne

PS C:\Users\Jan> $regiondoc.region.locations.location[2].name

Geneva

Es ist hier zu beachten, dass das Element Location das Array beinhaltet, nicht der Container Locations (dieser ist, wie die einzelnen Location-Elemente vom Typ XMLElement)

Ebenfalls wird hier deutlich dass Powershell für den User Subelemente und Attribute transparent verwaltet. Während $regiondoc.region.locations.location[2].name auf das Attribut “Name” des jeweiligen Elementes zugreift, erhält man über die Anweisung PS C:\Users\Jan> $regiondoc.region.locations.Location[0].server[0].OS Zugriff auf das Unterelement OS des ersten Server-Elementes.

Powershell erlaubt diese Syntax nicht nur beim Lesen von Daten, sondern auch beim Schreiben. Wenn wir also beispielsweise das Element OS aller Server einer Location verändern wollen können wir das wiefolgt tun:

Vorher:

PS C:\Users\Jan> $regiondoc.region.locations.location[0].server

Caesar 192.168.1.1 ZH Windows

Augustus 192.168.1.2 ZH Unix

Nero 192.168.1.3 ZH Unix

Mit nachfolgender Codesequence werden alle Server mit OS = Windows auf Windows 2003 R2 und alle Unix Server auf Solaris verändert ( jedenfalls auf dem Papier)

PS C:\Users\Jan> $regiondoc.region.locations.location[0].server |
% { if ($_.OS -
like "Windows")
   {$_.OS = "Windows 2003 R2"}
elseif ($_.OS -like "Unix") { $_.OS
= "Solaris"}}

Nachher

PS C:\Users\Jan> $regiondoc.region.locations.location[0].server

Caesar 192.168.1.1 ZH Windows 2003 R2

Augustus 192.168.1.2 ZH Solaris

Nero 192.168.1.3 ZH Solaris

Ebenso wie man bestehende Elemente, bzw Attribute verändern kann, kann man diese hinzufügen, bzw. wieder entfernen. Dazu gibt es mehrere Methoden.

1.) Wir „klonen“ ein bereits bestehendes Objekt und ändern dessen Eigenschaften. Dies bietet sich vor allem dann an, wenn ein Objekt viele Subobjekte besitzt, da ein Klonen sämtliche Subelemente und Attribute des Originals ebenfalls kopiert. Durch das Klonen erhalten wir eine identische Kopie des Objektes. Dies kann anhand eines einfachen Beispiels dargestellt werden:

PS C:\Users\Jan> $regiondoc.region.locations.location[2].server[0]

name ip_address domain OS

---- ---------- ------ --

Dali 192.168.20.1 GE Unix

Wir klonen nun den Eintrag durch folgenden Befehl:

PS C:\Users\Jan> $newserver = $regiondoc.region.locations.location[0].server[0].Clone()

Dadurch erstellen wir eine genaue Kopie des Servers “Dali”, die wir nun nach Belieben verändern können, ohne das Original ebenfalls zu modifizieren.

Wenn wir also dessen Namen ändern sieht $newserver dann wiefolgt aus:

PS C:\Users\Jan> $newserver.name = "Duerer"

PS C:\Users\Jan> $newserver

name ip_address domain OS

Duerer 192.168.20.1 GE Unix

Wohingegen das Original unberührt bleibt:

PS C:\Users\Jan> $regiondoc.region.locations.location[2].server[0]

name ip_address domain OS

Dali 192.168.20.1 GE Unix

Wenn wir also jetzt den neu erstellten Server namens Dürer der Location Geneva anfügen wollen, können wir dies über folgenden Befehl tun ( ich habe nebst dem Namen noch die IP Adresse und das OS geändert)

PS C:\Users\Jan> $regiondoc.region.locations.location[2].AppendChild($newserver)

Eine Auflistung der Server der Location zeigt uns dass der Append erfolgreich war:

PS C:\Users\Jan> $regiondoc.region.locations.location[2].Server

name ip_address domain OS

Dali 192.168.20.1 GE Unix

Renoir 192.168.20.2 GE Unix

Monet 192.168.20.3 GE Windows

Duerer 192.168.20.100 GE Windows

Meiner Ansicht nach ist dies, wenn schon Daten gleicher Struktur vorhanden sind, sicher die einfachste und sicherste Methode einen neuen Eintrag hinzuzufügen. Alternativ dazu kann man natürlich auch einen neuen Eintrag von Null auf erstellen. Dies würde wiefolgt aussehen:

PS C:\Users\Jan> $newelement = $regiondoc.CreateElement("Server")

Die einzelnen Subelemente müssten ebenso erstellt werden und als ChildElemente an $newelement angehängt werden.

PS C:\Users\Jan> $newOSelement = $regiondoc.CreateElement("OS")

PS C:\Users\Jan> $newOSelement.set_InnerText(“Unix”)

PS C:\Users\Jan> $newelement.AppendChild($newOSelement)

Attribute werden wiefolgt angefügt:

PS C:\Users\Jan> $newattribute = $regiondoc.CreateAttribute(“Version”)

PS C:\Users\Jan> $newattribute.set_value(“2003 R2”)

PS C:\Users\Jan> $newelement.setAttributeNode($newattribute)

Mit RemoveElement() bzw. RemoveAttribute() können diese ebenso wieder entfernt werden.

Um ein verändertes Dokument wieder physikalisch abzuspeichern wird die Save Methode des Dokumentes mit einer Pfadangabe aufgerufen

PS C:\Users\Jan> $regiondoc.save(“c:\ps\regions_modified.xml”)

Wie man sehen kann, ermöglicht Powershell durch seine intuitive Darstellung von XML als Objekt eine äusserst effiziente Methode um strukturierte Daten auszulesen, zu modifizieren oder auch komplett neu zu erstellen. Es ist möglich ohne (für den ungeübten Anwender of kryptische) XPATH Abfragen sich schnell durch das Dokument zu bewegen und Zugriff auf entsprechende Werte zu erhalten.