Working with XML. Not sure how to get what I need...

by Eurisko at 2013-01-26 13:10:22

I am fairly new to PowerShell, but I have been writing a large number of functions to utilize a specific products WebAPI via Soap requests/responses.

So far, things were great, I was able to reference the nodes the way I needed, make use of them, and output what I wanted.

Until I ran into output that didn’t have nodes in the way I’m used to, and I was quickly over my head. I’m hoping there are some built in functions to handle this data, or some pre-done routines to handle it, as I don’t have a clue how to even begin to manipulate it.

The listed respose is:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd="http://www.w3.org/2001/XMLSchema&quot; xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
<soap:Body>
<GetUserListResponse xmlns="http://archer-tech.com/webservices/&quot;&gt;
<GetUserListResult>string</GetUserListResult>
</GetUserListResponse>
</soap:Body>
</soap:Envelope>

If I drill down into the response: $SoapResponse.Envelope.Body.GetUserListResponse.GetUserListResult I get this blurb:

<Return>
<User>
<row_num>1</row_num>
<ID>186</ID>
<LastName>Norris</LastName>
<MiddleName/>
<FirstName>Chuck</FirstName>
<AccountStatus>Active</AccountStatus>
<ParamName>Speical Security</ParamName>
<SecurityID>7</SecurityID>
<username>cnorris</username>
<SysAdmin>False</SysAdmin>
<DistinguishedName>Norris, Chuck</DistinguishedName>
<ReturnDomain/>
<TimeZone>Central Standard Time</TimeZone>
<Locale />
<IsLoggedIn>False</IsLoggedIn>
</User>
<User>
<row_num>2</row_num>
<ID>205</ID>
<LastName>Kent</LastName>
<MiddleName/>
<FirstName>Clark</FirstName>
<AccountStatus>Active</AccountStatus>
<ParamName>Special Security</ParamName>
<SecurityID>7</SecurityID>
<username>superman1</username>
<SysAdmin>False</SysAdmin>
<DistinguishedName>Kent, Clark</DistinguishedName>
<ReturnDomain/>
<TimeZone>Central Standard Time</TimeZone>
<Locale />
<IsLoggedIn>False</IsLoggedIn>
</User>
<User>
<row_num>3</row_num>
<ID>221</ID>
<LastName>Flintstone</LastName>
<MiddleName>P.</MiddleName>
<FirstName>Fred</FirstName>
<AccountStatus>Active</AccountStatus>
<ParamName>General User</ParamName>
<SecurityID>1</SecurityID>
<username>fflintstone</username>
<SysAdmin>False</SysAdmin>
<DistinguishedName>Flintstone, Fred</DistinguishedName>
<ReturnDomain>bedrock.com</ReturnDomain>
<TimeZone>Central Standard Time</TimeZone>
<Locale>2</Locale>
<IsLoggedIn>True</IsLoggedIn>
</User>
</Return>

If I output the whole response to a file, it looks more like this sample:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="Error; xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:xsd="http://www.w3.org/2001/XMLSchema&quot;&gt;
<soap:Body>
<GetUserListResponse xmlns="somedomain.com
<GetUserListResult>&lt;Return&gt;
&lt;User&gt;
&lt;row_num&gt;1&lt;/row_num&gt;
&lt;ID&gt;186&lt;/ID&gt;
&lt;LastName&gt;Admin&lt;/LastName&gt;
&lt;MiddleName&gt;&lt;/MiddleName&gt;
&lt;FirstName&gt;Speical&lt;/FirstName&gt;
&lt;AccountStatus&gt;Active&lt;/AccountStatus&gt;
&lt;ParamName&gt;Speical Admin Security Parameter&lt;/ParamName&gt;
&lt;SecurityID&gt;7&lt;/SecurityID&gt;
&lt;username&gt;Speicaladmin&lt;/username&gt;
&lt;SysAdmin&gt;False&lt;/SysAdmin&gt;
&lt;DistinguishedName&gt;Admin, Speical&lt;/DistinguishedName&gt;
&lt;ReturnDomain&gt;&lt;/ReturnDomain&gt;
&lt;TimeZone&gt;Central Standard Time&lt;/TimeZone&gt;
&lt;Locale /&gt;
&lt;IsLoggedIn&gt;False&lt;/IsLoggedIn&gt;
&lt;/User&gt;
&lt;User&gt;
&lt;row_num&gt;2&lt;/row_num&gt;
&lt;ID&gt;205&lt;/ID&gt;
&lt;LastName&gt;Admin2&lt;/LastName&gt;
&lt;MiddleName&gt;&lt;/MiddleName&gt;
&lt;FirstName&gt;Speical&lt;/FirstName&gt;
&lt;AccountStatus&gt;Active&lt;/AccountStatus&gt;
&lt;ParamName&gt;Speical Admin Security Parameter&lt;/ParamName&gt;
&lt;SecurityID&gt;7&lt;/SecurityID&gt;
&lt;username&gt;Speicaladmin2&lt;/username&gt;
&lt;SysAdmin&gt;False&lt;/SysAdmin&gt;
&lt;DistinguishedName&gt;Admin2, Speical&lt;/DistinguishedName&gt;
&lt;ReturnDomain&gt;&lt;/ReturnDomain&gt;
&lt;TimeZone&gt;Central Standard Time&lt;/TimeZone&gt;
&lt;Locale /&gt;
&lt;IsLoggedIn&gt;False&lt;/IsLoggedIn&gt;
&lt;/User&gt;
&lt;User&gt;
&lt;row_num&gt;3&lt;/row_num&gt;
&lt;ID&gt;221&lt;/ID&gt;
&lt;LastName&gt;Admin3&lt;/LastName&gt;
&lt;MiddleName&gt;&lt;/MiddleName&gt;
&lt;FirstName&gt;Speical&lt;/FirstName&gt;
&lt;AccountStatus&gt;Active&lt;/AccountStatus&gt;
&lt;ParamName&gt;Speical Admin Security Parameter&lt;/ParamName&gt;
&lt;SecurityID&gt;7&lt;/SecurityID&gt;
&lt;username&gt;Speicaladmin3&lt;/username&gt;
&lt;SysAdmin&gt;False&lt;/SysAdmin&gt;
&lt;DistinguishedName&gt;Admin3, Speical&lt;/DistinguishedName&gt;
&lt;ReturnDomain&gt;&lt;/ReturnDomain&gt;
&lt;TimeZone&gt;Central Standard Time&lt;/TimeZone&gt;
&lt;Locale /&gt;
&lt;IsLoggedIn&gt;False&lt;/IsLoggedIn&gt;
&lt;/User&gt;
&lt;/Return&gt;</GetUserListResult>
</GetUserListResponse>
</soap:Body>
</soap:Envelope>


I had pumped out about 80 or so of these functions I made for handling these methods and everything was nice and simple, now I completely went over my head on this one.

Here is what I’m trying to do:

I basically need to be able to extract a <User> "chunk" at a time to be able to pass it off into another function (that I could just call in a foreach loop). My other function will do some lookups based on those values, manipulate them then output the results to a file.

What’s the best way to pass those <User> sections one at a time to a function, AND, what’s the best format to send them in? (ie, send it as an XML chunk and manipulate the nodes in the sub function to get what I want, or send them each as paramaters?)

Previously I did a chunk like this, but that was just one simple CSV.
Import-CSV $DataFile | ForEach {Set-UserAccountStatus -Token $TokenValue -FromScript $true -ResultsLog $ResultsLog -username $.username -accountStatus $accountStatus}

Any suggestions or recommendations are greatly appreciated. Thanks!
by ps_gregg at 2013-01-26 14:15:02
Hi Eurisko

Looks like you just need to drill down one more level. I saved your whole response to a file and did the following:

$file_xml = [xml](Get-Content test.xml)
$file_xml.Envelope.Body.GetUserListResponse.GetUserListResult.Return | % {$
.User}

Which returns XmlElement objects that look like this:

row_num : 1
ID : 186
LastName : Admin
MiddleName :
FirstName : Speical
AccountStatus : Active
ParamName : Speical Admin Security Parameter
SecurityID : 7
username : Speicaladmin
SysAdmin : False
DistinguishedName : Admin, Speical
ReturnDomain :
TimeZone : Central Standard Time
Locale :
IsLoggedIn : False

row_num : 2
ID : 205
LastName : Admin2
MiddleName :
FirstName : Speical
AccountStatus : Active
ParamName : Speical Admin Security Parameter
SecurityID : 7
username : Speicaladmin2
SysAdmin : False
DistinguishedName : Admin2, Speical
ReturnDomain :
TimeZone : Central Standard Time
Locale :
IsLoggedIn : False

row_num : 3
ID : 221
LastName : Admin3
MiddleName :
FirstName : Speical
AccountStatus : Active
ParamName : Speical Admin Security Parameter
SecurityID : 7
username : Speicaladmin3
SysAdmin : False
DistinguishedName : Admin3, Speical
ReturnDomain :
TimeZone : Central Standard Time
Locale :
IsLoggedIn : False

I would take those objects and using your example command, I would pass the object properties as parameter values to my other commands – it would look something like this:

$file_xml = [xml](Get-Content test.xml)
$users = $file_xml.Envelope.Body.GetUserListResponse.GetUserListResult.Return
$users | ForEach { Set-UserAccountStatus -Token $TokenValue -FromScript $true -ResultsLog $ResultsLog -username $.User.username -accountStatus $AccountStatus }

This can be consolidated into one less line, but I figured more lines were better for explaination purposes.

$
.User.username will get the username property of each of the XmlElement objects as they go through the Foreach loop.

I have also seen it written as ($_.User).username which will work too.

Hope that helps.

Cheers
-Gregg
by Eurisko at 2013-01-26 15:59:14
That appears to have worked perfectly!

Thank you again so much!
by Eurisko at 2013-01-26 17:34:43
One final piece for my project stumps me. Some of the values witn a single XML node.

Drilling down in my variable that I stored the Soap response:

$GetUser.Envelope.Body.GetUserResponse.GetUserResult

Which contains this:

<user><name first="Fred" middle="" last="Flintstone" /><system userName="fflintstone" userId="293" status="Active" securityParameter="1" lastLogin="" forcePasswordChange="False" company="Bedrock Rubble Company" title="Cheif Dino Operator" locale="" /><timeZone id="Central Standard Time" /><address>&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;p style=&quot;margin: 0px&quot;&gt;123 Bedrock Lane,&lt;/p&gt;&lt;p style=&quot;margin: 0px&quot;&gt;Bedrock, FL 68513&lt;/p&gt;&lt;/html&gt;</address><notes/><contactItems><item id="231" type="EMail" value="fred@bedrock.com" default="True" /></contactItems><roles><role id="1" name="General User" /><role id="2" name="Default Administrator" /><role id="25" name="Access Control Administrator" /><role id="40" name="EM: Admin" /><role id="2" name="Default Administrator" /><role id="51" name="CM: Admin" /></roles><groups><group id="38" name="Risk Management" /><group id="54" name="CM: Admin" /></groups></user>

I have a bad feeling this is much more difficult to extract my values from? Ideally, what I need to do is be able to access each of these as a variable, or convert them to variables. I have to be able to evaluate each value in this block, so I can compare it to other values being passed to this function to make comparisons and selections based upon my findings.

Is this as ugly as I fear?
by Eurisko at 2013-01-26 18:29:39
To add to it, I realize it has that as a string, and not an xml element like I thought. (when using Get-Member -Force, it sees that section as Type System.String)
by Eurisko at 2013-01-26 19:21:28
After trying every advanced trick I could find, I simply cast the string I got once I drilled down as xml, then I could again navigate that section. ($XMLGetUser = [xml]$GetUser.Envelope.Body.GetUserResponse.GetUserResult )