XML in PowerShell: XPath-Abfragen und Namespaces

    XML Path Language (XPath)Um Informationen aus XML-Dokumenten zu extrahieren gibt es mit XPath eine vom W3C standar­disierte Abfrage­sprache. Damit kann man wesent­lich kom­plexere Opera­tionen aus­führen als mit der Punkt-Notation von PowerShell. Umständlich wird XPath im Zusammen­spiel mit Namespaces.

    XML Path Language (XPath) erlaubt etwa, Elemente abhängig von Attribut­werten zu selektieren, Geschwister­knoten über Positions­parameter anzu­sprechen oder die Navigation über eine verkürzte Syntax sowie Wildcards (siehe dazu die XPath-Beispiele bei W3C).

    XPath in Methoden von XMLDocument

    PowerShell sieht die Verwendung von XPath-Ausdrücken ebenfalls vor, und zwar über die Methoden SelectNodes() und SelectSingleNode(). Sie stehen in Objekten des Typs System.Xml.XMLDocument und System.Xml.XMLElement zur Verfügung. Die erste der beiden liefert alle Knoten, auf welche die Abfrage zutrifft, die zweite nur das erste Vorkommen.

    Get-Member zeigt die beiden Methoden von System.Xml.XMLDocument und System.Xml.XMLElement, die XPath-Ausdrücke akzeptieren.

    Für die folgenden Ausführungen bedienen wir uns eines Fragments aus einer .ovf-Datei, die dem Austausch von VMs über Plattformen hinweg dient:

    Nachdem man die Datei mit

    [xml]$ovf = Get-Content -Path '.\MyVM.ovf'

    eingelesen hat, könnte man alle Item-Elemente so adressieren:

    $ovf.DocumentElement.SelectNodes("//Item")

    In der Praxis enthalten heute fast alle XML-Dokumente zumindest eine Namespace-Deklaration, das hier als Beispiel verwendete OVF-Format gleich mehrere:

    In diesem Fall würde jeder Aufruf von SelectSingleNode() oder SelectNodes() nach obigem Muster kommentarlos zu einem leeren Ergebnis führen.

    Wenn in einem XML-Dokument Namespaces definiert sind, dann ergibt eine einfache XPath-Abfrage kein Ergebnis.

    Abhilfe schafft hier das Erzeugen eines XmlNamespaceManager-Objekts, das man im Aufruf der zwei Funktionen übergeben muss:

    $ns = New-Object System.Xml.XmlNamespaceManager($ovf.NameTable)
    $ns.AddNamespace("ns","http://schemas.dmtf.org/ovf/envelope/1")

    Der erste Befehl erstellt das Objekt und der zweite fügt den Default Namespace hinzu. Interessant dabei ist, dass Letzterer ja kein Präfix definiert, aber wir für den XmlNamespaceManager ein solches benötigen. Dieses Beispiel verwendet "ns", das wir dann brauchen, um uns auf Elemente oder Attribute zu beziehen, die kein Präfix haben:

    $ovf.SelectNodes("//ns:Item",$ns)

    Wie man hier sieht, nutzt der XPath-Ausdruck nun das Präfix für den Default Namespace und als zweites Argument übergeben wir den oben definierten XmlNamespaceManager. Für eine vollständige Verfügbarkeit aller Elemente müsste man natürlich alle Namespaces hinzufügen.

    Wenn man sich die dafür nötige Tipparbeit sparen möchte, dann kann man dieses Script von Github herunterladen. Es liest alle Namespace-Definitionen aus und erzeugt daraus einen neuen XmlNamespaceManager.

    XPath-Abfragen mit Select-Xml

    Für XPath-Abfragen bietet PowerShell mit Select-Xml ein eigenes Cmdlet, welches einen vereinfachten Zugriff auf XML-Dateien erlaubt. Diese müssen dabei nicht explizit in ein XML-Objekt eingelesen werden, vielmehr übergibt man ihm den Pfad zur Datei einfach über den Parameter Path:

    Select-Xml -Path '.\MyVM.ovf' -XPath "//Item"

    Nachdem die OVF-Datei mehrere Namespace-Deklarationen enthält, liefert auch dieser Aufruf ganz lapidar kein Ergebnis. Die Lösung besteht hier jedoch nicht in der Definition eines XmlNamespaceManager, sondern man erstellt eine Hash-Tabelle nach dem Muster

    $namespace = @{Präfix=URI; Präfix=URI; …}

    und übergibt diese dem Parameter Namespace:

    Select-Xml -Path '.\MyVM.ovf' -XPath "//ns:Item" -Namespace $namespace

    Wie man hier sieht, bezieht sich das Präfix "ns" wieder auf den Default Namespace. Man kann dafür im Hash-Table auch ein beliebiges anderes Präfix wählen, das man aber dann natürlich auch in der XPath-Abfrage verwenden muss.

    XPath-Abfrage mit Select-Xml unter Angabe von Namespaces

    Eine Besonderheit von Select-Xml besteht darin, dass es ein Array zurückgibt, das aus Nodes, dem Pfad und dem Xpath-Ausdruck besteht. Um den Inhalt der Knoten aufzuschließen, setzt man Select-Object ein:

    Select-Xml -Path '.\MyVM' -XPath "//ns:Item" -Namespace $namespace |
    select -ExpandProperty Node

    Alternativ greift man über den Punkt-Operator auf die Node-Eigenschaft zu:

    (Select-Xml -Path '.\MyVM.ovf -XPath "//ns:Item" -Namespace $namespace).Node

    Der Vorteil dieses Vorgehens besteht darin, dass man so ein Objekt vom Typ XmlElement erhält und all dessen Eigenschaften und Methoden nutzen kann. Dazu zählt etwa auch die Ausgabe der bloßen Textknoten mit InnerText oder des enthaltenen Teilbaums mit InnerXML:

    (Select-Xml -Path '.\MyVM.ovf -XPath "//ns:Item" -Namespace $namespace).Node. InnerText

    Keine Kommentare