42

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)

Mark Allison
  • 6,838
  • 33
  • 102
  • 151
  • Your XML appears to be serialized directly from in memory objects. Is there a reason you wouldn't just deserialize them back into memory? – jpmc26 Jan 22 '19 at 18:26

2 Answers2

54

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'}
Eric Pohl
  • 2,324
  • 1
  • 21
  • 31
mswietlicki
  • 1,413
  • 12
  • 16
  • 4
    I've added some explanation. – mswietlicki Aug 29 '13 at 12:02
  • 2
    You can also navigate the document using the familiar property syntax: `($xml.Objects.Object | ? { $_.ServiceState -eq "Running" }).DisplayName` – zneak Aug 05 '16 at 17:20
  • 1
    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. – Anthony Horne May 17 '18 at 06:47
  • What version of Powershell was this introduced in? – alex Mar 23 '20 at 17:40
  • 1
    `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 https://stackoverflow.com/a/65264118/7571258 – zett42 Dec 12 '20 at 11:47
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
js2010
  • 23,033
  • 6
  • 64
  • 66