44

I have this XML document in a text file:

<?xml version="1.0"?>
<Objects>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DisplayName" Type="System.String">SQL Server (MSSQLSERVER)</Property>
    <Property Name="ServiceState" Type="Microsoft.SqlServer.Management.Smo.Wmi.ServiceState">Running</Property>
  </Object>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DisplayName" Type="System.String">SQL Server Agent (MSSQLSERVER)</Property>
    <Property Name="ServiceState" Type="Microsoft.SqlServer.Management.Smo.Wmi.ServiceState">Stopped</Property>
  </Object>
</Objects>

I want to iterate through each object and find the DisplayName and ServiceState. How would I do that? I've tried all kinds of combinations and am struggling to work it out.

I'm doing this to get the XML into a variable:

[xml]$priorServiceStates = Get-Content $serviceStatePath;

where $serviceStatePath is the xml file name shown above. I then thought I could do something like:

foreach ($obj in $priorServiceStates.Objects.Object)
{
    if($obj.ServiceState -eq "Running")
    {
        $obj.DisplayName;
    }
}

And in this example I would want a string outputted with SQL Server (MSSQLSERVER)

1
  • Your XML appears to be serialized directly from in memory objects. Is there a reason you wouldn't just deserialize them back into memory? Commented Jan 22, 2019 at 18:26

2 Answers 2

55

PowerShell has built-in XML and XPath functions. You can use the Select-Xml cmdlet with an XPath query to select nodes from XML object and then .Node.'#text' to access node value.

[xml]$xml = Get-Content $serviceStatePath
$nodes = Select-Xml "//Object[Property/@Name='ServiceState' and Property='Running']/Property[@Name='DisplayName']" $xml
$nodes | ForEach-Object {$_.Node.'#text'}

Or shorter

[xml]$xml = Get-Content $serviceStatePath
Select-Xml "//Object[Property/@Name='ServiceState' and Property='Running']/Property[@Name='DisplayName']" $xml |
  % {$_.Node.'#text'}
Sign up to request clarification or add additional context in comments.

5 Comments

I've added some explanation.
You can also navigate the document using the familiar property syntax: ($xml.Objects.Object | ? { $_.ServiceState -eq "Running" }).DisplayName
The explicit conversion to [xml] did it for me. Without the conversion, it returns the xml (as string) and the parsing does not work. No error - of course.
What version of Powershell was this introduced in?
Get-Content is the wrong way to load XML, because it doesn't respect the encoding attribute of the XML header. Most of the time we get lucky, only because many XML documents are UTF-8 encoded, which happens to be the default encoding used by Get-Content nowadays. Still it's wrong, for more details see stackoverflow.com/a/65264118/7571258
2

You can also do it without the [xml] cast. (Although xpath is a world unto itself. https://www.w3schools.com/xml/xml_xpath.asp)

$xml = (select-xml -xpath / -path stack.xml).node
$xml.objects.object.property

Or just this, xpath is case sensitive. Both have the same output:

$xml = (select-xml -xpath /Objects/Object/Property -path stack.xml).node
$xml


Name         Type                                                #text
----         ----                                                -----
DisplayName  System.String                                       SQL Server (MSSQLSERVER)
ServiceState Microsoft.SqlServer.Management.Smo.Wmi.ServiceState Running
DisplayName  System.String                                       SQL Server Agent (MSSQLSERVER)
ServiceState Microsoft.SqlServer.Management.Smo.Wmi.ServiceState Stopped

Comments

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.