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).
Contents
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>