PowerShell XML: Read and Write XML Files

PowerShell XML: Read and Write XML Files

PowerShell Tips Editor 3 min read
PowerShell XML: Read and Write XML Files

Application configuration files, SOAP API responses, and exported data often arrive as XML — a format that PowerShell handles natively without any extra modules. The [xml] type accelerator loads an XML document as a .NET XmlDocument object whose structure you can navigate using dot notation, query with XPath, modify, and save back to disk. This post covers loading, navigating, querying, modifying, extending, and saving PowerShell XML files with practical examples you can adapt to your own configs and data files.

Load XML from File with [xml]

Cast a file path’s content to [xml] to load and parse the document. PowerShell creates a typed object tree where each element becomes an accessible property:

# Method 1: Cast Get-Content output
[xml]$config = Get-Content "C:\Config\app.config"

# Method 2: Load via XmlDocument for larger files (more efficient)
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.Load("C:\Config\app.config")

# Both result in an XmlDocument — check the root element
Write-Host "Root element: $($config.DocumentElement.Name)"
Root element: configuration

Navigate XML as Object Properties

Child elements of an XML node are accessible as properties using dot notation. Attribute values are also accessible as properties:

# Given XML: <servers><server name="web01" ip="10.1.1.10"/></servers>
[xml]$data = @"
<servers>
  <server name="web01" ip="10.1.1.10" role="web" />
  <server name="db01"  ip="10.1.1.20" role="db"  />
</servers>
"@

# Navigate via dot notation
$data.servers.server        # Returns all server nodes as array
$data.servers.server[0].name   # "web01"
$data.servers.server[0].ip     # "10.1.1.10"

# List all server names
$data.servers.server | ForEach-Object { Write-Host $_.name }
web01
db01

Select Nodes with SelectNodes

XPath expressions via SelectNodes and SelectSingleNode are more powerful than dot notation for complex queries across the document tree:

# Select all web servers
$webServers = $data.SelectNodes("//server[@role='web']")
$webServers | ForEach-Object { Write-Host "Web server: $($_.name) at $($_.ip)" }

# Select a single node
$db = $data.SelectSingleNode("//server[@role='db']")
Write-Host "DB server: $($db.name)"

# Select text content from an element
[xml]$appConfig = Get-Content "C:\Config\app.config"
$connString = $appConfig.SelectSingleNode("//connectionStrings/add[@name='Default']")?.connectionString

Modify XML Node Values

Assign directly to a property to update attribute values, or use InnerText to update element text content:

[xml]$config = Get-Content "C:\Config\app.config"

# Change an attribute value
$connNode = $config.SelectSingleNode("//add[@key='DatabaseServer']")
if ($connNode) {
    $connNode.value = "ProdDB02"
    Write-Host "Updated DatabaseServer to ProdDB02"
}

# Change element text content
$timeoutNode = $config.SelectSingleNode("//appSettings/add[@key='TimeoutSeconds']")
if ($timeoutNode) {
    $timeoutNode.value = "60"
}

Add New XML Elements

Create new elements or attributes using the document’s CreateElement and CreateAttribute methods, then append them to the appropriate parent node:

[xml]$config = Get-Content "C:\Config\servers.xml"

$newServer = $config.CreateElement("server")
$newServer.SetAttribute("name", "cache01")
$newServer.SetAttribute("ip", "10.1.1.30")
$newServer.SetAttribute("role", "cache")

$serversNode = $config.SelectSingleNode("//servers")
$serversNode.AppendChild($newServer) | Out-Null

Write-Host "Added cache01 to servers list"
Write-Host "Total servers: $($config.servers.server.Count)"

Save Modified XML to File

Use the .Save() method to write the modified XML document to a file. This preserves the XML declaration (<?xml version="1.0"?>) and properly formats the output, unlike Set-Content which would just write the string representation:

[xml]$config = Get-Content "C:\Config\servers.xml"

# ... make modifications ...

# Save to the same file
$config.Save("C:\Config\servers.xml")

# Save to a new file (backup pattern)
$backupPath = "C:\Config\servers_$(Get-Date -Format 'yyyyMMdd_HHmmss').xml"
$config.Save($backupPath)

# Save with pretty-printing using XmlWriterSettings
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Indent          = $true
$settings.IndentChars     = "  "
$settings.OmitXmlDeclaration = $false
$writer = [System.Xml.XmlWriter]::Create("C:\Config\servers-formatted.xml", $settings)
$config.Save($writer)
$writer.Close()

Write-Host "XML saved"

Common Errors and Fixes

  • Namespace declarations require SelectSingleNode with namespace manager. XML documents with namespace declarations like xmlns="http://..." require an XmlNamespaceManager for XPath queries. Without it, SelectSingleNode("//elementName") returns $null even when the element clearly exists. Create an XmlNamespaceManager, add the namespace prefix, and use it in your XPath: $ns.AddNamespace("x", "http://your.namespace.uri"); $config.SelectSingleNode("//x:element", $ns).
  • Saving with .Save() not Set-Content to preserve XML declaration. Using $config.OuterXml | Set-Content "file.xml" loses the XML declaration and may affect whitespace formatting. Always use $config.Save("path") for proper XML document writing.

Related Cmdlets / See Also

Wrapping Up

The [xml] type accelerator gives you full XML document manipulation in PowerShell without any external libraries. Use dot notation for simple navigation, XPath with SelectNodes/SelectSingleNode for complex queries, and always .Save() the document to disk rather than writing via Set-Content to preserve the XML structure and declaration.

Send-Item -To