Wednesday, June 22, 2011

More PowerShell, XML and XPath -- Select-Xml and multi-node selections

Let's start with the same document as before.

<StyleCopSettings Version="4.3">
<Analyzers>
<Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
<Rules>
<Rule Name="FileMustHaveHeader">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="FileHeaderMustShowCopyright">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
</Rules>
<AnalyzerSettings />
</Analyzer>
</Analyzers>
</StyleCopSettings>
view raw gistfile1.xml hosted with ❤ by GitHub

Now at PowerShell 2, we can also do

>$n = Select-Xml -Xml $x -XPath "//Rule[@Name='FileHeaderMustShowCopyright']//BooleanProperty[@Name='Enabled']"
>$n
Node Path Pattern
---- ---- -------
BooleanProperty InputStream //Rule[@Name='FileHeaderMustShowCopy...
>$n.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False SelectXmlInfo System.Object
>$n.Node
Name #text
---- -----
Enabled False
>$n.Node.ParentNode.ParentNode
Name RuleSettings
---- ------------
FileHeaderMustShowCopyright RuleSettings
view raw gistfile1.ps1 hosted with ❤ by GitHub

which avoids the explicit call to the .net infrastructure, but wraps up the actual content we got in the previous example as the Node field inside an object. This is not so much of a problem when picking a single node, but when you want to do the equivalent of SelectNodes nested (e.g. for each Rule, do something with each setting) some disassembly is required in order to perform the inner selection:

>$s = Select-Xml -Xml $x -XPath '//Rule'
>$s | foreach-object { $_.Node ; Select-Xml $_.Node -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
Name RuleSettings
---- ------------
FileMustHaveHeader RuleSettings
Enabled
FileHeaderMustShowCopyright RuleSettings
Enabled
compare
>$nn = $x.SelectNodes("//Rule")
>$nn | ForEach-Object { $_ ; $_.SelectNodes("descendant::BooleanProperty") }
Name RuleSettings
---- ------------
FileMustHaveHeader RuleSettings
Enabled
FileHeaderMustShowCopyright RuleSettings
Enabled
view raw gistfile1.ps1 hosted with ❤ by GitHub

It is, however, a bit of an oversight that the Select-Xml cmdlet doesn't consume SelectXmlInfo objects through the usual type conversion mechanisms inside the cmdlet infrastructure:

>$s | foreach-object { Select-Xml -xml $_ -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
Select-Xml : Cannot bind parameter 'Xml'. Cannot convert the "<RuleSettings><BooleanProperty Name="Enabled">False</Bool
eanProperty></RuleSettings>" value of type "Microsoft.PowerShell.Commands.SelectXmlInfo" to type "System.Xml.XmlNode".
At line:1 char:38
+ $s | foreach-object { Select-Xml -xml <<<< $_ -XPath '//BooleanProperty' | foreach-object { $_.Node } }
+ CategoryInfo : InvalidArgument: (:) [Select-Xml], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SelectXmlCommand
Select-Xml : Cannot bind parameter 'Xml'. Cannot convert the "<RuleSettings><BooleanProperty Name="Enabled">False</Bool
eanProperty></RuleSettings>" value of type "Microsoft.PowerShell.Commands.SelectXmlInfo" to type "System.Xml.XmlNode".
At line:1 char:38
+ $s | foreach-object { Select-Xml -xml <<<< $_ -XPath '//BooleanProperty' | foreach-object { $_.Node } }
+ CategoryInfo : InvalidArgument: (:) [Select-Xml], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SelectXmlCommand
view raw gistfile1.ps1 hosted with ❤ by GitHub

but once you know there's that gotcha, it can be worked around.

No comments :