0

I have a monitoring system that returns JSON data. I am using Powershell to get all of the monitored servers and a subset of their properties. Then I want to export those properties to an XML file that matches an XSD, but I am not sure how to do that.

When I run

$allServers | get-member

I can see all of the properties for the servers in a System.Management.Automation.PSCustomObject. So I run:

$filtered = $allServers | Select-Object Id,HostName,Description,Status

Okay, now I have the properties I want and I can run something like this:

$filtered = ($filtered | ConvertTo-Xml)

and now I have a System.Xml.XmlDocument object. Super. I can use the save method:

$filtered.Save("c:\test\test.xml")

The output looks okay, but does not match the required schema file.

<?xml version="1.0" encoding="utf-8"?>
<Objects>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="id" Type="System.Int32">6</Property>
    <Property Name="hostname" Type="System.String">server1</Property>
    <Property Name="description" Type="System.String">dc</Property>
    <Property Name="status" Type="System.Int32">1</Property>
  </Object>
</Objects>

My schema looks like this:

<?xml version="1.0" encoding="Windows-1252"?>
<xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified">
  <xsd:element name="ServerImport">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Servers">
          <xsd:complexType>
            <xsd:sequence>
              <xsd:element maxOccurs="unbounded" name="Server">
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name="Id" type="xsd:int" />
                    <xsd:element name="HostName" type="xsd:string" />
                    <xsd:element name="Description" type="xsd:string" />
                    <xsd:element name="Status" type="xsd:int" />
                  </xsd:sequence>
                </xsd:complexType>
              </xsd:element>
            </xsd:sequence>
          </xsd:complexType>
        </xsd:element>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xs:schema>

So, how do I make my output match the schema?

Thanks.

5
  • Seems like you would need to convert the output from Select-Object to a defined class. What version of Powershell are you using? Commented Mar 29, 2017 at 22:10
  • I think the server that will run my task currently has 4, but I can install 5. Commented Mar 30, 2017 at 0:26
  • If you can find a v5 environment, you can try converting the output to a class and then writing it to XML. Purely theory at this point as I haven't installed v5 yet, either. Commented Mar 30, 2017 at 0:33
  • Seems that naive approach is a no-go. Just installed v5 and ConvertTo-Xml on an object STILL results in <Objects><Object Type="car"><Property Name="make" Type="System.String">Chevy</Property></Object></Objects>. Commented Mar 30, 2017 at 13:29
  • There is a fairly verbose method found here: rogerdelph.com Commented Mar 30, 2017 at 13:31

1 Answer 1

2

v5 of PowerShell supports proper class structures. Therefore, you could create a class which contains the information you desire and a small-ish function to convert the class to an XmlNode:

function makeXML($parentNode, $class) {

    $d = $parentNode.OwnerDocument.CreateElement($class.GetType().Name);
    $class.psobject.properties | foreach {
        $p = $doc.CreateElement($_.Name);
        $p.InnerText = $_.Value;
        $d.AppendChild($p);
    }

    $parentNode.AppendChild($d);
}

class Server { 
    [int]$Id; 
    [string]$HostName; 
    [string]$Description; 
    [int]$Status 
};

# Here you would need to convert the output from 
#     Select-Object ID,HostName,Description,Status
# to instances of type Server 

$s = New-Object server;
$s.Id = 1;
$s.HostName = "localhost";
$s.Description = "Local Server";
$s.Status = 3;

# Create the empty XML document to save your results to with root node of "Servers"
$doc = New-Object System.Xml.XmlDocument;
$doc.LoadXml("<ServerImport><Servers></Servers></ServerImport>");
$serversNode = $doc.SelectSingleNode("/ServerImport/Servers");

# This could likely be a pipe operation against your list of Server objects
# $filtered | { makeXml $serversNode $_ }
makeXML $serversNode $s;
# Save your xml to a file
$doc.Save("C:\test\test.xml");

Which results in the following XML:

<ServerImport>
  <Servers>
    <Server>
      <Id>1</Id>
      <HostName>localhost</HostName>
      <Description>Local Server</Description>
      <Status>3</Status>
    </Server>
  </Servers>
</ServerImport>

Note: I am sure there are more elegant solutions and there are probably a dozen enhancements the above code endure. But, this should get you started.

Edit - overlooked the needed structure of the XML (missed the ServerImport root node).

Sign up to request clarification or add additional context in comments.

1 Comment

I ended up using the System.XMl.XmlTextWriter class, but I like your answer too. I am definitely going to keep this in mind for next time. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.