Importing Complex XML into PowerShell

PowerShell Text Purple hero
We’re almost finished with our exploration of using XML and PowerShell. Hopefully, you’ve been following along from the start and I hope you’ve been experimenting with your own XML files. In the previous article, I shared some techniques for bringing XML files back into PowerShell that you might have created using ConvertTo-XML. But you might have customized such a file, as I did in a previous article in this series when I created an inventory XML file.

My inventory XML file
My inventory XML file (Image Credit: Jeff Hicks)

The challenge is that there are several different type of objects described here and there is no explicit type information for anything. In order to import this into PowerShell as an object, you really have to know the XML file structure.
 

 
As before, the first step is to convert the file into an XML document.

[xml]$in = Get-Content C:\work\MyInventory.xml

The imported document
The imported document (Image Credit: Jeff Hicks)

I know that all of the data is in the Computers node. But how you import the data is completely up to you.
Here’s one scenario that “imports” the XML data as a complex object.

$data = foreach ($computer in $in.computers.computer) {
  #initialize an ordered hashtable with the computername
  $hash = [ordered]@{Computername = $Computer.name}
  $os = $computer.OperatingSystem
  #create a child object
  $osObj = [pscustomobject]@{
    OS = $os.Name
    Version = $os.Version
    Architecture = $os.OSArchitecture
    Installed = $os.InstallDate -as [DateTime]
  }
  $hash.Add("OperatingSystem",$osObj)
  $cs = $computer.ComputerSystem
  $csObj = [pscustomobject]@{
    Processors = $cs.NumberOfProcessors -as [int]
    LogicalProcessors = $cs.NumberofLogicalProcessors -as [int]
    PhysicalMemoryGB = $cs.TotalPhysicalMemory/1GB -as [int]
    HyperVisor = $cs.HyperVisorPresent -as [boolean]
  }
  $hash.Add("ComputerSystem",$csObj)
  $svcs = $computer.Services.Service
  $arr =  foreach ($svc in $svcs) {
    [pscustomobject]@{
        Name = $svc.Name
        Displayname = $svc.DisplayName
        State = $svc.State
        StartMode = $svc.StartMode
        StartName = $svc.StartName
    }
  }
  $hash.Add("Services",$arr)
  #write the hashtable as a custom object to the pipeline
  [pscustomobject]$hash
}

Viewing all converted data
Viewing all converted data (Image Credit: Jeff Hicks)

Each of the child nodes in the XML file is a separate object.
Using converted data
Using converted data (Image Credit: Jeff HIcks)

It is beyond the scope of this article, but I could also insert a custom typename and then use custom type and format files. Or I might import the XML file with a different set of commands.
Perhaps I want the object structured a bit differently.

$data = foreach ($computer in $in.computers.computer) {
  #initialize an ordered hashtable with the computername
  $hash = [ordered]@{
    Computername = $Computer.name
    OS = $computer.OperatingSystem.name
    Version = $computer.OperatingSystem.Version
    Architecture = $computer.OperatingSystem.OSArchitecture
    Installed = $computer.OperatingSystem.InstallDate -as [Datetime]
    Processors = $computer.ComputerSystem.NumberOfProcessors -as [int]
    LogicalProcessors = $computer.ComputerSystem.NumberofLogicalProcessors -as [int]
    PhysicalMemoryGB = $computer.ComputerSystem.TotalPhysicalMemory/1GB -as [int]
    HyperVisor = $computer.ComputerSystem.HyperVisorPresent -as [boolean]
  }
  $svcs = $computer.Services.Service
  $arr =  foreach ($svc in $svcs) {
    [pscustomobject]@{
        Name = $svc.Name
        Displayname = $svc.DisplayName
        State = $svc.State
        StartMode = $svc.StartMode
        StartName = $svc.StartName
    }
  }
  $hash.Add("Services",$arr)
  #convert the hashtable as a custom object
  $myObj = [pscustomobject]$hash
  #insert a Typename
  $myobj.psobject.typenames.insert(0,"myInventory")
  #write to the pipeline
  $myobj
}

In this example, I’ve also inserted a new typename for my custom object. I did this because I defined a default set of display properties using Update-TypeData.

Update-TypeData -TypeName myInventory -DefaultDisplayPropertySet 'Computername','OS','Installed','LogicalProcessors','PhysicalMemoryGB'

By default this gives me the view I want.

A customized view
A customized view (Image Credit: Jeff Hicks)

Of course, all of the properties are still accessible.
All properties
All properties (Image Credit: Jeff Hicks)

The bottom line is that no matter how you bring the data into PowerShell, you want to have objects so that you can use the results like anything else in PowerShell.
Data as objects
Data as objects (Image Credit: Jeff Hicks)

These techniques should work for any type of XML file, even something that isn’t Windows or computer specific.  Here’s my BandData.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<!--
 This is a demonstration XML file
-->
<Bands>
  <Band>
    <Name Year="1970" City="Boston, MA">Aerosmith</Name>
    <Lead>Steven Tyler</Lead>
    <Members>
      <Member>Tom Hamilton</Member>
      <Member>Joey Kramer</Member>
      <Member>Joe Perry</Member>
      <Member>Brad Whitford</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="1968" City="Toronto">Rush</Name>
    <Lead>Geddy Lee</Lead>
    <Members>
      <Member>Alex Lifeson</Member>
      <Member>Neil Peart</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="1968" City="Birmingham, England">Black Sabbath</Name>
    <Lead>Ozzie Osbourne</Lead>
    <Members>
      <Member>Tony Iommi</Member>
      <Member>Geezer Butler</Member>
      <Member>Bill Ward</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Def Leppard</Name>
    <Lead>Joe Elliott</Lead>
    <Members>
      <Member>Rick Allen</Member>
      <Member>Phil Collen</Member>
      <Member>Tony Kenning</Member>
      <Member>Rick Savage</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Poison</Name>
    <Lead>Bret Michaels</Lead>
    <Members>
      <Member>Rikki Rockett</Member>
      <Member>C.C. DeVille</Member>
      <Member>Bobby Dall</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Motley Crue</Name>
    <Lead>Vince Neil</Lead>
    <Members>
      <Member>Nikki Sixx</Member>
      <Member>Tommy Lee</Member>
      <Member>Mick Mars</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">The Doors</Name>
    <Lead>Jim Morrison</Lead>
    <Members>
      <Member>Ray Manzarek</Member>
      <Member>John Densmore</Member>
      <Member>Robby Krieger</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Nirvana</Name>
    <Lead>Kurt Cobain</Lead>
    <Members>
      <Member>Dave Grohl</Member>
      <Member>Krist Novoselic</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Deep Purple</Name>
    <Lead>Ian Gillan</Lead>
    <Members>
      <Member>Jon Lord</Member>
      <Member>Roger Glover</Member>
      <Member>Ian Paice</Member>
      <Member>Ritchie Blackmore</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Led Zeppelin</Name>
    <Lead>Roger Plant</Lead>
    <Members>
      <Member>Jimmy Page</Member>
      <Member>John Paul Jones</Member>
      <Member>John Bonham</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Reo Speedwagon</Name>
    <Lead>Kevin Cronin</Lead>
    <Members>
      <Member>Neal Doughty</Member>
      <Member>Alan Gratzer</Member>
      <Member>Gary Richrath</Member>
      <Member>Bruce Hall</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Lynrd Skynrd</Name>
    <Lead>Ronnie Van Zant</Lead>
    <Members>
      <Member>Allen Collins</Member>
      <Member>Gary Rosssington</Member>
      <Member>Leon Wilkeson</Member>
      <Member>Billy Powell </Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Iron Maiden</Name>
    <Lead>Bruce Dickinson</Lead>
    <Members>
      <Member>Steve Harris</Member>
      <Member>Dave Murray</Member>
      <Member>Adrian Smith</Member>
      <Member>Nicko McBrain</Member>
      <Member>Janick Gers</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">REM</Name>
    <Lead>Michael Stipe</Lead>
    <Members>
      <Member>Peter Buck</Member>
      <Member>Mike Mills</Member>
      <Member>Bill Berry</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Guns N Roses</Name>
    <Lead>Axl Rose</Lead>
    <Members>
      <Member>Saul Hudson</Member>
      <Member>Izzy Stradlin</Member>
      <Member>Steven Adler</Member>
      <Member>Duff McKagan</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Skid Row</Name>
    <Lead>Sebastian Bach</Lead>
    <Members>
      <Member>Dave Sabo</Member>
      <Member>Scott Hill</Member>
      <Member>Rachel Bolen</Member>
      <Member>Rob Affuso</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Pink Floyd</Name>
    <Lead>David Gilmour</Lead>
    <Members>
      <Member>Nick Mason</Member>
      <Member>Roger Waters</Member>
      <Member>Richard Wright</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Jethro Tull</Name>
    <Lead>Ian Anderson</Lead>
    <Members>
      <Member>Martin Barre</Member>
      <Member>Doane Perry</Member>
      <Member>Dave Pegg</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">The Clash</Name>
    <Lead>Joe Strummer</Lead>
    <Members>
      <Member>Mick Jones</Member>
      <Member>Paul Simonen</Member>
      <Member>Nicky Headon</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Queen</Name>
    <Lead>Freddie Mercury</Lead>
    <Members>
      <Member>Brian May</Member>
      <Member>Roger Taylor</Member>
      <Member>John Deacon</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Cream</Name>
    <Lead>Eric Clapton</Lead>
    <Members>
      <Member>Ginger Baker</Member>
      <Member>Jack Bruce</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Foo Fighters</Name>
    <Lead>Dave Grohl</Lead>
    <Members>
      <Member>Chris Shiflett</Member>
      <Member>Pat Smear</Member>
      <Member>Nate Mendel</Member>
      <Member>Taylor Hawkins</Member>
    </Members>
  </Band>
  <Band>
    <Name Year="" City="">Triumph</Name>
    <Lead>Rik Emmett</Lead>
    <Members>
      <Member>Gil Moore</Member>
      <Member>Mike Levine</Member>
    </Members>
  </Band>
</Bands>

I want to bring this into PowerShell as a set of objects.

[xml]$bandData = Get-Content S:\BandData.xml
$bands = foreach ($band in $bandData.Bands.band) {
    Write-Host "Converting $($band.Name.'#text')" -ForegroundColor Cyan
    [PSCustomObject]@{
        Name = $band.Name.'#text'
        Founded = $band.Name.Year
        Origin = $band.Name.City
        Lead = $band.Lead
        Members = $band.Members.member
    }
}

Again, I can’t stress enough that you really need to know in advance how your XML file is structured. But now I have a set of objects I can work with.

Using other types of data
Using other types of data (Image Credit: Jeff Hicks)


I think this example should bring us full circle in this series. I didn’t intend this to be an exhaustive series, although it may have tired you out in spots. For example, at some point I should probably give you some exposure to use XPath and finding data inside an XML file. But I’ll leave that for the future. I expect you have had your fill of XML for awhile, so next time we’ll play with something else.