Windows Powershell in der Praxis: XML Verarbeitung (von Jan Moser)
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.