Public/ConvertFrom-ConnectorXmlToPowerShellObject.ps1

<#
.SYNOPSIS
    Converts an XML node to a JSON-like object (i.e. a PowerShell object with properties corresponding to the XML structure).

.DESCRIPTION
    Recursively converts an XML node and its children to a PowerShell object that can be easily

.EXAMPLE
    [xml] $xml = Get-Content -Path "data.xml"
    $object = $xml | ConvertFrom-ConnectorXmlToPowerShellObject
#>

function ConvertFrom-ConnectorXmlToPowerShellObject {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNull()]
        [System.Xml.XmlNode]$Node,

        # If a node has no attributes and a single child with a node type of 'Text', then return the innerText value of that child node rather than a separate object
        # This eliminates '#text' child nodes where possible
        [Parameter(Mandatory = $false)]
        [switch]$IncludeChildTextNodes
    )

    Process {
        $NODE_TYPES = @(
            [System.Xml.XmlNodeType]::Element,
            [System.Xml.XmlNodeType]::Attribute,
            [System.Xml.XmlNodeType]::Text
        )

        if ($Node.NodeType -eq [System.Xml.XmlNodeType]::Text) {
            Write-Debug ("Returning child node '{0}' as text because NodeType is '{1}'" -f $Node.Name, $Node.NodeType)
            return $Node.InnerText
        }

        if (!$IncludeChildTextNodes.IsPresent -and $Node.HasChildNodes -and $Node.ChildNodes.Count -eq 1 -and !$Node.HasAttributes) {
            $child = $Node.ChildNodes | Select-Object -First 1
            if ($child.NodeType -eq [System.Xml.XmlNodeType]::Text) {
                Write-Debug ("Returning child '{0}' node '{1}' as text for parent '{2}' because the parent has no attributes and the child text node is the only node. Pass IncludeChildTextNodes to prevent this behaviour." -f $child.NodeType, $childName, $Node.Name)
                return $child.InnerText
            }
        }

        $nodeHt = ([System.Management.Automation.OrderedHashtable]@{})

        foreach ($attribute in $Node.Attributes) {
            if ($nodeHt.Contains($attribute.Name)) {
                Write-Warning ("Skipping attribute '{0}' on node '{1}' because the name is a duplicate" -f $attribute.Name, $Node.Name)
                continue
            }

            $thisObject = ConvertFrom-XmlToJSON $attribute -IncludeChildTextNodes:$IncludeChildTextNodes.IsPresent
            if ($null -ne $thisObject) {
                Write-Debug ("Recursively adding attribute '{0}' for parent node '{1}'" -f $attribute.Name, $Node.Name)
                $nodeHt += @{
                    $attribute.Name = $thisObject
                }
            }
            else {
                Write-Warning ("Omitting attribute '{0}' for parent '{1}' because the result was null." -f $attribute.Name, $Node.Name)
            }
        }

        # Skip stuff we're not interested in, e.g. declarations
        foreach ($child in $Node.ChildNodes.Where({ $_.NodeType -in $NODE_TYPES })) {
            $childName = $child.ToString()
            if ($nodeHt.Contains($childName)) {
                # Duplicate node name -- assume an array
                if ($null -ne $nodeHt[$childName] -and $nodeHt[$childName].GetType().Name.StartsWith('List')) {
                    Write-Debug ("Duplicate child node name '{0}' of type '{1}' on parent node '{2}' -- using existing collection" -f $childName, $child.NodeType, $Node.Name)
                    $nodes = $nodeHt[$childName]
                }
                else {
                    Write-Verbose ("Duplicate child node name '{0}' of type '{1}' on parent node '{2}' -- assuming a collection of nodes" -f $childName, $child.NodeType, $Node.Name)
                    $nodes = New-Object System.Collections.Generic.List[object]
                    if ($null -ne $nodeHt[$childName]) {
                        $nodes.Add(($nodeHt[$childName]))
                    }
                }

                $thisObject = ConvertFrom-XmlToJSON $child -IncludeChildTextNodes:$IncludeChildTextNodes.IsPresent
                if ($null -ne $thisObject) {
                    $nodes.Add(($thisObject))
                }
                else {
                    Write-Warning ("Omitting child '{0}' node '{1}' for parent '{2}' (index {3}) because the result was null." -f $child.NodeType, $childName, $Node.Name, $nodes.Count)
                }

                $nodeHt[$childName] = $nodes
            }
            else {
                $thisObject = ConvertFrom-XmlToJSON $child -IncludeChildTextNodes:$IncludeChildTextNodes.IsPresent
                if ($null -ne $thisObject) {
                    Write-Debug ("Recursively adding child node '{0}' of type '{1}' for parent node '{2}'" -f $childName, $child.NodeType, $Node.Name)
                    $nodeHt += @{
                        $childName = $thisObject
                    }
                }
                else {
                    Write-Warning ("Omitting child '{0}' node '{1}' for parent '{2}' because the result was null." -f $child.NodeType, $childName, $Node.Name)
                }
            }
        }

        return (New-Object PSObject -Property $nodeHt)
    }
}