Last month, our monitoring vendor started dropping daily inventory exports as XML files onto a file share. Finance wanted them in Excel. For two weeks, someone on my team opened each file, copied nodes by hand, and pasted them into a workbook. It took an hour a day, and the numbers were wrong twice.
I fixed it with about 30 lines of PowerShell. Once the script was in place, the whole job ran in under 10 seconds, on a schedule, with no human touching it. That’s the real win here — XML is machine-friendly, Excel is human-friendly, and PowerShell sits nicely between the two.
In this guide, I’ll show you how to convert XML to Excel using PowerShell, step by step — reading the XML, flattening nested nodes, writing a real .xlsx file, and handling the pitfalls that break these scripts in production.
What You’ll Be Working With
Let’s keep one consistent example the whole way through: a file server that receives daily hardware inventory exports named Inventory.xml. Here’s a trimmed version of the file:
<Inventory>
<Computer>
<Name>FS-01</Name>
<Model>ProLiant DL380</Model>
<SerialNumber>CZ123456</SerialNumber>
<MemoryGB>64</MemoryGB>
<LastBoot>2026-09-01T04:12:00</LastBoot>
</Computer>
<Computer>
<Name>APP-02</Name>
<Model>PowerEdge R740</Model>
<SerialNumber>DL987654</SerialNumber>
<MemoryGB>128</MemoryGB>
<LastBoot>2026-08-29T22:05:00</LastBoot>
</Computer>
</Inventory>Every <Computer> node becomes one row in Excel. Every child element becomes a column. That mental model — node equals row, child equals column — is the whole job.
You can run everything below in Windows PowerShell 5.1 or PowerShell 7. I’ll flag the differences where they matter.
Step 1: Read the XML File Into PowerShell
PowerShell has a built-in XML type accelerator, [xml], that parses a file into a navigable object. A cmdlet is a built-in PowerShell command like Get-Content; a type accelerator is a shortcut that casts data into a specific .NET type.
$xmlPath = "D:\Reports\Inventory.xml"
[xml]$xml = Get-Content -Path $xmlPath -Raw
$computers = $xml.Inventory.Computer
$computers | Format-Table -AutoSizeLine by line:
$xmlPathholds the source file path. Keep paths in variables so you only change them in one place.Get-Content -Rawreads the file as a single string instead of an array of lines. Without-Raw, large XML files parse slower and sometimes fail.[xml]casts that string into an XML document object.$xml.Inventory.Computerwalks the tree using dot notation — root node, then child nodes.Format-Tablejust previews the result on screen so you can confirm the shape before writing anything.
If your XML is deeply nested, it helps to understand the object model first. My write-up on how to convert XML to an object in PowerShell covers that traversal in more detail.
Step 2: Flatten the XML Into Clean Objects
Raw XML nodes carry extra properties Excel doesn’t need. Build clean custom objects instead — a custom object is a simple data container with the properties you define.
$rows = foreach ($c in $computers) {
[PSCustomObject]@{
ComputerName = $c.Name
Model = $c.Model
Serial = $c.SerialNumber
MemoryGB = [int]$c.MemoryGB
LastBoot = [datetime]$c.LastBoot
}
}What’s happening:
foreachloops through every<Computer>node. The loop output collects straight into$rows.[PSCustomObject]@{}builds one object per computer with named properties. Those property names become your Excel column headers.[int]and[datetime]cast the values to real types. This matters a lot — XML values are always strings, and Excel will treat unconverted numbers as text, breaking sorting and formulas.
Pro Tip: In my experience, casting types at this stage saves more headaches than anything else in the script. I once shipped a report where
MemoryGBstayed a string, and the finance team’s SUM formula returned zero. Nobody noticed for a week. Cast early, cast explicitly.
If a cast fails on bad data, you’ll see errors like “cannot convert value to type System.Int32”. I’ve documented that exact fix in PowerShell cannot convert value to type System.Int32.
Step 3: Convert XML to Excel Using PowerShell With ImportExcel
The cleanest way to write a real .xlsx file is the ImportExcel module. A module is a packaged set of cmdlets you install once. ImportExcel doesn’t need Excel installed on the machine, which makes it perfect for servers.
Install it first:
Install-Module -Name ImportExcel -Scope CurrentUser -ForceThen export:
$rows | Export-Excel -Path "D:\Reports\Inventory.xlsx" `
-WorksheetName "Inventory" `
-AutoSize `
-TableName "InventoryTable" `
-FreezeTopRowBreaking that down:
- The pipeline (
|) passes your objects straight intoExport-Excel. -Pathsets the output workbook. It’s created if missing, overwritten if not.-WorksheetNamenames the tab. Default is “Sheet1”, which looks sloppy in a shared report.-AutoSizewidens columns to fit content. Skip it and everyone sees#####.-TableNameformats the range as a real Excel table with filter dropdowns.-FreezeTopRowkeeps headers visible while scrolling.
That’s it. Five lines, and you have a formatted workbook.
Before installing, it’s worth confirming whether the module is already there. See how to check if a module is installed in PowerShell for the quick test.
Step 4: The No-Module Option — Go Through CSV
Sometimes you can’t install modules. Locked-down servers, change control, no internet. In that case, convert the XML to CSV first, then open or convert that CSV.
$rows | Export-Csv -Path "D:\Reports\Inventory.csv" -NoTypeInformation -Encoding UTF8Export-Csv is built into PowerShell, so there’s nothing to install. -NoTypeInformation removes the junk #TYPE header line — in PowerShell 7 that’s the default, but include it anyway for 5.1 compatibility. -Encoding UTF8 keeps accented characters and symbols intact.
If you truly need .xlsx and Excel is installed locally, you can drive Excel through COM automation:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open("D:\Reports\Inventory.csv")
$workbook.SaveAs("D:\Reports\Inventory.xlsx", 51)
$workbook.Close($false)
$excel.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-NullThe 51 is the file format code for .xlsx. That last ReleaseComObject line is not optional — skip it and you’ll leave orphaned EXCEL.EXE processes running on the server. I’ve cleaned up dozens of those.
For a deeper look at CSV-based workflows, my guide on PowerShell Export-Csv walks through delimiters, appending, and encoding.
Step 5: Handle Nested XML and Attributes
Real XML is rarely flat. Say each computer also has disks and an id attribute:
<Computer id="1001">
<Name>FS-01</Name>
<Disks>
<Disk letter="C" sizeGB="240" />
<Disk letter="D" sizeGB="2000" />
</Disks>
</Computer>Attributes are read with dot notation, same as elements. For child collections, join them into a single readable cell:
$rows = foreach ($c in $xml.Inventory.Computer) {
$diskSummary = ($c.Disks.Disk | ForEach-Object {
"$($_.letter):$($_.sizeGB)GB"
}) -join "; "
[PSCustomObject]@{
Id = $c.id
ComputerName = $c.Name
Disks = $diskSummary
TotalDiskGB = ($c.Disks.Disk | Measure-Object -Property sizeGB -Sum).Sum
}
}ForEach-Objectbuilds a short string per disk.-join "; "collapses the array into one cell value. Excel can’t hold an array, so flatten it or you’ll getSystem.Object[]in your report.Measure-Object -Sumcalculates total disk space, giving you a genuinely useful column.
Alternatively, put disks on their own worksheet with Export-Excel -WorksheetName "Disks" -Append. One sheet per node type keeps things readable.
Step 6: Wrap It in Error Handling and Logging
Scripts that run unattended need to fail loudly, not silently. Use Try/Catch — a block that runs code and catches errors instead of crashing.
$xmlPath = "D:\Reports\Inventory.xml"
$xlsxPath = "D:\Reports\Inventory_$(Get-Date -Format 'yyyy-MM-dd').xlsx"
$logPath = "D:\Reports\Logs\convert.log"
try {
if (-not (Test-Path -Path $xmlPath)) {
throw "Source XML not found: $xmlPath"
}
[xml]$xml = Get-Content -Path $xmlPath -Raw
$rows = foreach ($c in $xml.Inventory.Computer) {
[PSCustomObject]@{
ComputerName = $c.Name
Model = $c.Model
Serial = $c.SerialNumber
MemoryGB = [int]$c.MemoryGB
}
}
if ($rows.Count -eq 0) { throw "No Computer nodes found in XML." }
$rows | Export-Excel -Path $xlsxPath -WorksheetName "Inventory" -AutoSize -TableName "Inv"
"$(Get-Date -f 'u') SUCCESS - $($rows.Count) rows exported" | Add-Content $logPath
}
catch {
"$(Get-Date -f 'u') ERROR - $($_.Exception.Message)" | Add-Content $logPath
Write-Error $_.Exception.Message
}Test-Path confirms the source exists before parsing. throw raises a custom error your catch block handles. The date-stamped filename keeps history instead of overwriting yesterday’s report. Add-Content appends to the log rather than replacing it.
For more on structuring this properly, see Try/Catch in PowerShell and how to log error messages to a file.

Things to Keep in Mind
- Test on a copy first. Point the script at a sample XML in a scratch folder. Never debug against the folder finance is actively watching.
- Watch your XML encoding. Files with a BOM or non-UTF8 encoding can throw parse errors. If
[xml]fails, open the file in a text editor and check the declaration line. - Don’t assume node names. If the vendor renames
<Computer>to<Device>, your script silently produces an empty file. Always check$rows.Countand fail if it’s zero. - Mind the execution policy. Servers often block scripts by default. Set it properly with Set-ExecutionPolicy rather than bypassing security wholesale.
- Check file locks before writing. If someone has the workbook open in Excel,
Export-Excelfails. Write to a temp name first, then rename. - Never hardcode credentials. If the XML sits on a remote share, use a credential object rather than a plain-text password in the script body.
Frequently Asked Questions
Do I need Microsoft Excel installed to create an .xlsx file?
No, not if you use the ImportExcel module. It writes native .xlsx files using its own library. You only need Excel installed if you go the COM automation route.
Why do my numbers show up as text in Excel?
XML values are always strings. Cast them explicitly with [int], [double], or [decimal] when building your custom objects. Excel then treats them as real numbers and formulas work correctly.
How do I handle XML with namespaces?
Namespaces break simple dot notation. Use SelectNodes() with an XmlNamespaceManager, or strip the namespace from the raw string with a regex replace before casting to [xml]. The regex approach is quicker for one-off jobs.
Can I export multiple XML files into one workbook?
Yes. Loop through the files with Get-ChildItem, build your rows, then call Export-Excel with -Append or a different -WorksheetName per file. One sheet per source file keeps the data traceable.
How do I run this conversion on a schedule?
Save the script as a .ps1 file and create a Windows Scheduled Task. Point the action at powershell.exe with -File "D:\Scripts\Convert-XmlToExcel.ps1". Run it under a service account that has read and write rights to both folders.
What if my XML is very large?
For files over a few hundred megabytes, [xml] loads everything into memory and can slow down badly. Use XmlReader for streaming, or split the file first. Most admin exports are small enough that [xml] is fine.
You now know how to read an XML file, flatten its nodes into clean objects, and export the result to a formatted Excel workbook using PowerShell. Start small — run it against one sample file, check the output by eye, then wire it into a scheduled task once you trust it. I hope you found this article helpful.
You may also like
- How to convert XML to CSV in PowerShell
- How to convert XML to a table in PowerShell
- How to convert Excel files to CSV using PowerShell
- How to read Excel files into an array in PowerShell
- How to create XML files with content using PowerShell
Bijay Kumar is an esteemed author and the mind behind PowerShellFAQs.com, where he shares his extensive knowledge and expertise in PowerShell, with a particular focus on SharePoint projects. Recognized for his contributions to the tech community, Bijay has been honored with the prestigious Microsoft MVP award. With over 15 years of experience in the software industry, he has a rich professional background, having worked with industry giants such as HP and TCS. His insights and guidance have made him a respected figure in the world of software development and administration. Read more.