0

I am trying to convert a standard XML document to that is stored in a series of folder aggregate them together to build an automated patching system. XML document format offer the best mix of flexibility/ease of use. Unfortunately, the XML subsystem of PowerShell is case sensitive once the XML document is formatted which can leave room for unnecessary headaches so I am trying to convert the imported XML documents to PSCustomObjects but I am stuck.

I am unable to find a way to get it to detect if there are child nodes for a specific property so I can reiterate through the Convert-XMLtoArray again so it will convert all of the XML child nodes to PSCustomObjects.

Result:

Application     Version InstallType Installers
-----------     ------- ----------- ----------
Mozilla Firefox 64.0.2  Install               

Expected result:

Application     Version InstallType Installers
-----------     ------- ----------- ----------
Mozilla Firefox 64.0.2  Install     {Windows 10,Windows7...}     

Code:

function Convert-XmltoArray($xml) {
    $Return = New-Object -TypeName 'PSCustomObject'
    $XML | Get-Member -MemberType Property | ForEach {
        $Property = New-Object -TypeName 'PSCustomObject'
        $Name = $_.name
        $Value = $XML.($Name)
        if ($Value.HasChildNodes) {
            foreach ($Child in $Value.ChildNodes) {
                $Return | Add-Member -Type NoteProperty -Name $Child.localname -Value $($Child.'#text')
                #<SomethingHere>
            }
        }
    }
    $Return
}

$Test = [XML]@"
<Package>
    <Application>Java</Application>
    <Version>8.2.9.23</Version>
    <InstallType>Install</InstallType>
    <Installers>
        <Windows10>
            <x86>
                <File1>
                    <FileName>jre-8u201-windows-i586.exe</FileName>
                    <Parameters>/s</Parameters>
                </File1>
            </x86>
            <x64>
                <file1>
                    <FileName>jre-8u201-windows-x64.exe</FileName>
                    <Parameters>/s</Parameters>
                </file1>
            </x64>
            <IA64>
                <File1>
                    <FileName></FileName>
                    <Parameters></Parameters>
                    <CustomSuccessCodes></CustomSuccessCodes>
                    <CustomErrorCodes></CustomErrorCodes>
                </File1>
            </IA64>
        </Windows10>
        <Windows7>
            <x86>
                <File1>
                    <FileName>jre-8u201-windows-i586.exe</FileName>
                    <Parameters>/s</Parameters>
                </File1>
            </x86>
            <x64>
                <file1>
                    <FileName>jre-8u201-windows-x64.exe</FileName>
                    <Parameters>/s</Parameters>
                </file1>
            </x64>
            <IA64>
                <File1>
                    <FileName></FileName>
                    <Parameters></Parameters>
                    <CustomSuccessCodes></CustomSuccessCodes>
                    <CustomErrorCodes></CustomErrorCodes>
                </File1>
            </IA64>
        </Windows7>
    </Installers>
</Package>
"@
$Result = Convert-XMLToArray -xml $test
$Result

Update

Finally got it figured out, its even compatible with PSv2.

Function Convert-XMLtoPSObject {
    Param (
        $XML
    )
    $Return = New-Object -TypeName PSCustomObject
    $xml |Get-Member -MemberType Property |Where-Object {$_.MemberType -EQ "Property"} |ForEach {
        IF ($_.Definition -Match "^\bstring\b.*$") {
            $Return | Add-Member -MemberType NoteProperty -Name $($_.Name) -Value $($XML.($_.Name))
        } ElseIf ($_.Definition -Match "^\System.Xml.XmlElement\b.*$") {
            $Return | Add-Member -MemberType NoteProperty -Name $($_.Name) -Value $(Convert-XMLtoPSObject -XML $($XML.($_.Name)))
        } Else {
            Write-Host " Unrecognized Type: $($_.Name)='$($_.Definition)'"
        }
    }
    $Return
}
Nick W.
  • 1,536
  • 3
  • 24
  • 40
  • i dont understand, xml is an object already. why would you do that? – 4c74356b41 Jan 20 '19 at 19:54
  • Please take a step back and describe the actual problem you're trying to solve instead of what you perceive as the solution. Why do you think you need this? – Ansgar Wiechers Jan 20 '19 at 19:55
  • Possible duplicate of [Convert XML to PSObject](https://stackoverflow.com/questions/3242995/convert-xml-to-psobject) – Andrei Odegov Jan 20 '19 at 20:32
  • @4c74356b41Because PSCustomObeject has significantly more flexability, additional properties (.count() for example) and is not case sensitive. – Nick W. Jan 20 '19 at 21:46
  • @AnsgarWiechers I need to be able to itterate through all of the sublevels of the property to convert all of the sub properties from XML to PSCustomObject. – Nick W. Jan 20 '19 at 21:47
  • It was already perfectly clear to me *what* you're trying to do. I was asking *why* you're trying to do it. What are you trying to accomplish in the end, i.e. what do you want do with the result? – Ansgar Wiechers Jan 20 '19 at 22:00
  • Because PSCustomObeject has significantly more flexibility, additional properties (.count() for example) and is not case sensitive. PSCustomObject is exceptionally intuitive over .Net XML framework that PowerShell uses. – Nick W. Jan 20 '19 at 22:29
  • That still doesn't answer my question. Anyway, I advise you to throw away your code and use [JSON](http://powershelldistrict.com/powershell-json/) instead. That will do exactly what you're asking. XML is apparently not the right tool for whatever it is you're trying to do. – Ansgar Wiechers Jan 20 '19 at 22:41
  • @AnsgarWiechers Yeah not sure what is possibly so difficult to understand about this. JSON is similar and I think would give me the powershell flexability I need but the XML files I am building to be imported are being utilized by Jr. Sys Admins so that format is much easier. – Nick W. Jan 21 '19 at 02:00
  • If you need to build XML then I suggest you start getting accustomed to how XML and the tools for dealing with it work. Not sure what is so difficult to understand about that. This is my final response. – Ansgar Wiechers Jan 21 '19 at 08:47

1 Answers1

2

You'll never get this …

{Windows 10,Windows7...}  

… based on the posted XML sample, it does not contain multiple OS versions.

Here is a sample approach that will populate 'Installers' column, but based only on your posted XML sample.

$Test = [XML]@"
<package>
    <Application>Mozilla Firefox</Application>
    <Version>64.0.2</Version>
    <InstallType>Install</InstallType>
    <Installers>
        <Windows10>
            <x86>
                <File1>
                    <FileName>Firefox_Setup_64.0.2_x86.exe</FileName>
                    <Parameters>/s</Parameters>
                </File1>
            </x86>
            <x64>
                <file1>
                    <FileName>Firefox_Setup_64.0.2_x64.exe</FileName>
                    <Parameters>/s</Parameters>
                </file1>
            </x64>
            <IA64>
                <File1>
                    <FileName></FileName>
                    <Parameters></Parameters>
                    <CustomSuccessCodes></CustomSuccessCodes>
                    <CustomErrorCodes></CustomErrorCodes>
                </File1>
            </IA64>
        </Windows10>
    </Installers>
</package>
"@

function ConvertFrom-XmlPart($xml)
{
    $hash = @{}

    $xml | 
    Get-Member -MemberType Property |
        % {
        $name = $_.Name
        if ($_.Definition.StartsWith("string "))
        {
            $hash.($Name) = $xml.$($Name)
        }
        elseif ($_.Definition.StartsWith("System.Object[] "))
        {
            $obj = $xml.$($Name)
            $hash.($Name) = $($obj | 
            % { $_.tag }) -join "; "
        }
        elseif ($_.Definition.StartsWith("System.Xml"))
        {
            $obj = $xml.$($Name)
            $hash.($Name) = @{}
            if ($obj.HasAttributes)
            {
                $attrName = $obj.Attributes | 
                Select-Object -First 1 | 
                % { $_.Name }

                if ($attrName -eq "tag")
                {
                    $hash.($Name) = $($obj | 
                    % { $_.tag }) -join "; "
                }
                else
                {
                    $hash.($Name) = ConvertFrom-XmlPart $obj
                }
            }
            if ($obj.HasChildNodes)
            {
                $obj.ChildNodes | 
                % { $hash.($Name).($_.Name) = ConvertFrom-XmlPart $($obj.$($_.Name)) }
            }
        }
    }
    return $hash
}

function ConvertFrom-Xml($xml) 
{
    $hash = @{}
    $hash = ConvertFrom-XmlPart($xml)
    return New-Object PSObject -Property $hash
}

ConvertFrom-XmlPart -xml $Test

# Output:
# =======
# Name                           Value
# ----                           -----
# package                        {InstallType, Version, Installers, Application}

# Walking the data
$Test.package

# Output:
# =======
# Application     Version InstallType Installers
# -----------     ------- ----------- ----------
# Mozilla Firefox 64.0.2  Install     Installers


$Test.package.Installers

# Output:
# =======
# Windows10
# ---------
# Windows10
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
postanote
  • 15,138
  • 2
  • 14
  • 25
  • You are correct, I addidently added the incorrect XML. I have it normally loaded as an external file but figured adding it to a variable would make it easier to read for the purpose of this post. – Nick W. Jan 20 '19 at 21:49
  • Actually, when running that exact piece of code it walks through the childnodes but all of the values show up empty as {} – Nick W. Jan 21 '19 at 07:43
  • Marking this as answer as this helped me get it figured out. TY. – Nick W. Jan 21 '19 at 17:32