Importing More Data into PowerShell

PowerShell Text Purple hero
In a previous article, I guided you through using the Export-Clixml and Import-Clixml cmdlets to serialize and deserialize data from a PowerShell expression. However, as I demonstrated in earlier articles in this series, you could also use ConvertTo-XML. This cmdlet will generate XML that is more compatible with non-PowerShell applications. But what if you also wanted to bring the data back to life in PowerShell? There is no ConvertFrom-XML cmdlet, at least as of today, so it will take a little extra work.
 

 
Here is an XML file I created in a previous article from a Get-Service command.

Converted XML
Converted XML (Image Credit: Jeff Hicks)

I converted the result to XML and saved it to a file. To bring this back, I need to convert it back into an XML document, which is actually quite easy.

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

The [xml] tells PowerShell, “treat the content as an XML document.” Which it now becomes.

Imported XML
Imported XML object (Image Credit: Jeff Hicks)

This should look familiar. It is pretty easy to “walk” the object tree.
The XML object
The XML object (Image Credit: Jeff Hicks)

The object properties are also easily discovered.
XML Object Properties
XML Object Properties (Image Credit: Jeff Hicks)

The tricky part is enumerating all of the XML nodes and writing them as objects to the pipeline.

$data = $in.Objects.Object | foreach {
  #initialize an empty hash table
  $hash = @{}
  #enumerate each property and add it to the hashtable
  foreach ($prop in $_.property) {
    $hash.Add($prop.name,$prop.'#text')
  }
  #Write the hashtable to the pipeline as a custom object
  [pscustomobject]$hash
}

The results seem ok at first.

Converted objects
Converted objects (Image Credit: Jeff Hicks)

But look at this with Get-Member.
Converted Object Properties
Converted Object Properties (Image Credit: Jeff Hicks)

By default, everything will be treated as a string or a generic object. This can pose challenges should you want to filter or sort. The clixml cmdlets automatically know how to preserve type. To do that here requires a little extra work.

Fortunately, when I converted the Get-Service command, the result saved property type, which you can see in the screenshot above. This means I can modify my code and add each entry to the hashtable as the necessary type.

$data2 = $in.Objects.Object | foreach {
  #initialize an empty hash table
  $hash = @{}
  #enumerate each property and add it to the hashtable
  foreach ($prop in $_.property) {
    $hash.Add($prop.name,$prop.'#text' -as $prop.Type)
  }
  #Write the hashtable to the pipeline as a custom object
  [pscustomobject]$hash
}

Unfortunately, this may not always be 100 percent successful.

Type Conversion Error
Type Conversion Error (Image Credit: Jeff Hicks)

But almost everything else is now properly typed.
Properly typed object
Properly typed object (Image Credit: Jeff Hicks)

But the overall display shows all properties by default. It doesn’t look like a service object. That’s because the type name is PSCustomObject and what we really want is this:
The actual type name
The actual type name (Image Credit: Jeff Hicks)

Your PowerShell expression should have generated the same type of object for all items. You can’t convert each custom object to this type, but you can insert a new type name.

$theType = $in.Objects.Object[0].Type
$data3 = $in.Objects.Object | foreach {
  #initialize an empty hash table
  $hash = @{}
  #enumerate each property and add it to the hashtable
  foreach ($prop in $_.property) {
    $hash.Add($prop.name,$prop.'#text' -as $prop.Type)
  }
  #Turn the hashtable into custom object
  $obj = [pscustomobject]$hash
  #insert a new typename
  $obj.psobject.typenames.insert(0,$theType)
  #write the object to the pipeline
  $obj
}

Now look at the results:

Properly typed and formatted results
Properly typed and formatted results (Image Credit: Jeff Hicks)

Or you can take a more generic approach where you don’t care what the node names are called but you know there is a Type attribute.

[xml]$in = Get-Content C:\work\wu3.xml
#skip XML declaration node
$nodes = $in.ChildNodes | Select -Skip 1
foreach ($node in $nodes.ChildNodes) {
 $hash = @{}
  #enumerate each property and add it to the hashtable
  #this assumes you don't know or care what the node
  #might be called
  foreach ($item in $node.($node.firstchild.LocalName)) {
    Try {
        $hash.Add($item.name,$item.'#text' -as $item.Type)
    }
    Catch {
        #ignore conversion errors
    }
  }
  #Turn the hashtable into custom object
  $obj = [pscustomobject]$hash
  #insert a new typename
  $obj.psobject.typenames.insert(0,$node.type)
  #write the object to the pipeline
  $obj
}

This example uses properties of the XML document itself to enumerate itself.
The main takeaway from all of this is that you need to know in advance what your XML file looks like so that you can write a script or function to properly import it into PowerShell. If your XML file doesn’t include type information, and it matters to you, then you’ll have to take other steps to convert those items to the proper type.

If you have anything more complex, then bringing it into PowerShell will take even more effort. But I’ll try to guide you through that process next time.