Powershell: Working with XML

A few tips and tricks for handling XML data with Powershell.

This is only a very brief overview/cheatsheet of common tasks when dealing with XML data (based on this article), because I recently needed to handle XML again, after quite a while (I usually prefer JSON nowadays).

Create a new XML document and save it to a file

The end result will be an XML file that contains the following:

<?xml version="1.0"?>
<!--
    Powershell Version: 7.5.3
    Powershell Edition: Core
-->
<RootNode>
  <MainNode Language="Powershell">
    <FooBar Foo="ABC" Bar="123" />
    <Entry Ticks="638971946370073046">E. #1</Entry>
    <Entry Ticks="638971946370074780">E. #2</Entry>
    <Entry Ticks="638971946370075446">E. #3</Entry>
  </MainNode>
</RootNode>

And it was generated by this code:

$XMLDoc = New-Object System.Xml.XmlDocument

# Adding the declaration: Without the encoding (second parameter, would be "UTF-8"),
# because that would cause .Save() to store it with a BOM (which we don't want!).
# Quick-&-Dirty solution: https://stackoverflow.com/questions/63476408/powershell-xmldocument-save-as-utf-8-without-bom
$XMLDeclaration = $XMLDoc.CreateXmlDeclaration("1.0", $null, $null);
$XMLDoc.AppendChild($XMLDeclaration) | Out-Null

# Adding a header comment:
$HeaderCommentString = @"

    Powershell Version: $($PSVersionTable.PSVersion.ToString())
    Powershell Edition: $($PSVersionTable.PSEdition)`n
"@

$HeaderComment = $XMLDoc.CreateComment($HeaderCommentString)
$XMLDoc.AppendChild($HeaderComment) | Out-Null

# Adding the root node:
$Root = $XMLDoc.CreateElement("RootNode")
$XMLDoc.AppendChild($Root) | Out-Null

# Adding a element beneath the root node:
$MainNode = $XMLDoc.CreateElement("MainNode")
$MainNode.SetAttribute("Language", "Powershell")
$Root.AppendChild($MainNode) | Out-Null

# Adding a tag to the main node:
$FooBar = $XMLDoc.CreateElement("FooBar")
$FooBar.SetAttribute("Foo", "ABC")
$FooBar.SetAttribute("Bar", "123")
$MainNode.AppendChild($FooBar) | Out-Null

# Adding multiple elements to the main node:
1..3 | % {
    $e = $XMLDoc.CreateElement("Entry")
    $e.InnerText = "E. #" + $_
    $e.SetAttribute("Ticks", ((Get-Date).Ticks))
    $MainNode.AppendChild($e) | Out-Null
}

# Save to a file (must be a fully qualified path!):
$FQP = Join-Path -Path (Resolve-Path -Path .).Path -ChildPath "file.xml"
$XMLDoc.Save($FQP)

Read an existing file and access its elements

Just a few simple command-line examples:

> [xml] $XMLDoc = Get-Content -Path ".\file.xml"
> $Root = $XMLDoc.'RootNode'
> $Root.'MainNode'

Language   FooBar Entry
--------   ------ -----
PowerShell FooBar {Entry, Entry, Entry}

> $XMLDoc.'#comment'

Powershell Version: 7.5.3
Powershell Edition: Core

> $Root.MainNode.FooBar

Foo Bar
--- ---
ABC 123

> $Root.MainNode.Entry            # or
> $XMLDoc.RootNode.MainNode.GetElementsByTagName("Entry")

Ticks              #text
-----              -----
638971946370073046 E. #1
638971946370074780 E. #2
638971946370075446 E. #3

> $Root.MainNode.Entry.InnerText

E. #1
E. #2
E. #3

> $XMLDoc.RootNode.MainNode.Entry.Get(1)

Ticks              #text
-----              -----
638971946370074780 E. #2

> $XMLDoc.RootNode.MainNode.Entry.GetValue(1).InnerText

E. #2

> $XMLDoc.RootNode.MainNode.Entry.GetValue(1).GetAttribute("Ticks")

638971946370074780

Modify an existing XML document and save it again

That is a combination of the previous steps:

[xml] $XMLDoc = Get-Content -Path ".\file.xml"

$XMLDoc.RootNode.MainNode.FooBar.InnerXml = "New"
$XMLDoc.RootNode.MainNode.FooBar.Bar = '456'
$XMLDoc.RootNode.MainNode.FooBar.Foo = 'XYZ'

$XMLDoc.Save((Resolve-Path ".\file.xml").Path)

Result (memory):

> $XMLDoc.RootNode.MainNode.FooBar

Foo Bar #text
--- --- -----
XYZ 456 New

Result (file):

<?xml version="1.0"?>
<!--
    Powershell Version: 7.5.3
    Powershell Edition: Core
-->
<RootNode>
  <MainNode Language="Powershell">
    <FooBar Foo="XYZ" Bar="456">New</FooBar>
    <Entry Ticks="638971946370073046">E. #1</Entry>
    <Entry Ticks="638971946370074780">E. #2</Entry>
    <Entry Ticks="638971946370075446">E. #3</Entry>
  </MainNode>
</RootNode>