How to query XAML file with Powershell's `select-xml` commandlet? - powershell

I'm trying to use the select-xml cmdlet in Powershell to query some XAML files in my .NET project. Here's what I've tried:
select-xml -content (cat $xaml_file_path) -xpath "//GroupBox"
Where $xaml_file_path is simply a string containing the file path to the XAML file of interest.
This throws an error:
Select-Xml: Cannot validate argument on parameter 'Content'. The argument is null, empty, or an element of the argument collection contains a null value. Supply a collection that does not contain any null values and then try the command again.
I know the XAML is valid since it compiles fine on Visual Studio. So I'm thinking there might be something else going on here.
Is there a way to query XAML files using Powershell? If so how?

To query an XML (XAML) document that uses namespaces, Select-Xml requires you to:[1]
declare all the namespaces that any nodes involved in your XPath query are in, via a hashtable that maps self-chosen prefixes to namespace URIs (parameter -Namespace).
Those self-chosen prefixes may, but needn't, be the same as in the input document, with the exception of the default namespace (xmlns), for which a name must be chosen too, but which can not be xmlns (the example below uses _).
use those self-chosen prefixes for all the nodes referenced in the XPath query (parameter -XPath), including those in the default (implied) namespace.
A simple example:
# Create a sample XAML file.
#"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="450" Width="500">
<Grid>
<TextBox x:Name="UserInnput" Height="140" TextWrapping="Wrap" VerticalAlignment="Top" AcceptsReturn="True" AcceptsTab="True" Padding="4" VerticalScrollBarVisibility="Auto" />
<Button x:Name="Save" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" IsDefault="True" Height="22" Margin="170,150,0,0" />
</Grid>
</Window>
"# | Set-Content sample.xml
# Create the hashtable that uses self-chosen names to the URIs
# of those namespaces involved in the XPath query below.
$namespaces = #{
# This is the default namespace, which the elements in the input
# document that have *no* namespace prefix are *implicitly* in.
# You must NOT use 'xmlns' as the prefix.
_ = 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'
# For non-default namespaces, you may choose the same prefixes
# as in the original document.
# Note:
# * This namespace isn't actually used in the query below.
# * However, if the nodes involved in the query do fall into
# this or other namespaces, they must be declared here too,
# and their prefixes must then be used in the query.
x = 'http://schemas.microsoft.com/winfx/2006/xaml'
}
# Query the XAML file, passing the hashtable to -Namespace, and
# using its keys as namespace prefixes; here, '_:' must be used
# to refer to nodes in the default namespace.
Select-Xml -LiteralPath sample.xml -Namespace $namespaces -XPath '//_:TextBox'
If you want to avoid having to deal with namespaces:
In the context of Select-Xml (as well as the underlying System.Xml.XmlDocument.SelectNodes() .NET method), the only way to avoid having to deal with namespaces is to use the *[local-name()='...'] workaround shown in your own answer.
In the context of PowerShell's adaption of the [xml] DOM, which adds "virtual" properties to instances of [xml] (System.Xml.XmlDocument):
These properties are always named for the non-prefixed node names; that is, namespaces are effectively ignored.
This is convenient and often sufficient, but it limits you to "drilling down" with dot notation into the document, as opposed to having being able to run XPath queries against the document; for the latter, you can use the .SelectNodes() and .SelectSingleNode() methods, which, however, again necessitate namespace management.[1]
The equivalent example with dot notation, building on the same sample file:
# Parse the XML file into an [xml] instance.
($doc = [xml]::new()).Load((Convert-Path sample.xml))
# Drill down to the <TextBox> element.
# Note: If the <Grid> element had *multiple* <TextBox> children,
# they would *all* be returned, as a System.Xml.XmlElement array.
$doc.Window.Grid.TextBox
[1] The need for namespace management applies analogously to direct use of the underlying System.Xml.XmlDocument.SelectNodes() and System.Xml.XmlDocument.SelectSingleNodes() .NET API, although constructing the prefix-to-URI mapping table is a little more cumbersome there - see this answer.

I think that, similarly to xmllint there might be an issue with handling namespaces.
I've found that the following does work:
(select-xml -path $xaml_file_path -xpath "//*[local-name()='GroupBox']").Node
If anyone knows of a cleaner/better way to do this (i.e. using the -namespace flag), I would be curious to hear about it.

Related

Significance of # in AEM slightly

Why we use # symbol in AEM slightly code. What is the significance of #?
For example:
<section data-sly-resource="${'./path' # resourceType='my/resource/type'}"></section>
In above code why # resourceType is used?
# is used in HTL/Sightly to pass parameters to the HTL block statements https://experienceleague.adobe.com/docs/experience-manager-htl/using/htl/block-statements.html?lang=en#request-attributes
If you check the above link you can see that the property settings.settings is passed as requestAttributes to the productdetails.html HTL file. It is same like passing arguments in a function, except that the syntax to do it in HTL/sightly is to use # with the parameter name and value
<sly data-sly-use.settings="com.adobe.examples.htl.core.hashmap.Settings"
data-sly-include="${ 'productdetails.html' # requestAttributes=settings.settings}" />

Extract Key from XML

I am invoking an API using "Invoke-WebRequest" and getting the below as return XML.
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://www.sft.com/">a879-956c3452f55e3c</string>
How can I extract the "a879-956c3452f55e3c" part from this retun XML.
I tried this code
$tmpstring1 = $temp.Content -split 'com/">'
$finalString1 = $tmpstring1[1] -split "</"
$Key = $finalString1[0]
String parsing of XML text is best avoided, because it is brittle; it's always preferable to us a dedicated XML parser; fortunately, PowerShell provides easy access to .NET's System.Xml.XmlDocument type ([xml]):
$xmlText = #'
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://www.sft.com/">a879-956c3452f55e3c</string>
'#
([xml] $xmlText).string.InnerText # .'#text' works too
Note: PowerShell conveniently adapts the XML DOM for dot notation, so that elements and attributes can be accessed as if they were regular object properties - see this answer for more information.
Normally, an XML element that only has text content (a text child node, such as the <string> element's a879-956c3452f55e3c value here) directly returns that text content when accessed with dot notation (.string).
However, because the <string> element has a namespace declaration (xmlns=...), .string actually returns a [System.Xml.XmlElement] instance whose text child node must explicitly be accessed, either via its .InnerText property or via the adapted property named for the (generic) node name of the text child element, .'#text'.

Strange Get-Item behaviour

If I simply do
Get-Item "HKLM:\SOFTWARE\MozillaPlugins\#microsoft.com/GENUINE"
I get this on the console
NAME
----
#microsoft.com/GENUINE
However, this
$test = Get-Item "HKLM:\SOFTWARE\MozillaPlugins\#microsoft.com/GENUINE"
Write-Host "$($test.name)"
returns the full path, not just the name of the key.
Is this a bug? Intended behavior? Me doing something incorrectly?
Is this a bug? Intended behavior? Me doing something incorrectly?
The middle one.
PowerShell formatting files (*.format.ps1xml located in the directory defined in $pshome by default) explain the difference here. In a default output scenario PowerShell checks for known formatting definitions, which are loaded from aforementioned files, for given object types. If present, it will use those, which govern what data and how that data is outputted.
So, in your case, you have Microsoft.Win32.RegistryKey objects. The format definitions for that are stored in registry.format.ps1xml. Just going to show a truncated section of that file so you can see how your top example is created.
.... output truncated ....
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>30</Width>
<Label>Name</Label>
</TableColumnHeader>
<TableColumnHeader>
<Label>Property</Label>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<Wrap/>
<TableColumnItems>
<TableColumnItem>
<PropertyName>PSChildName</PropertyName>
.... output truncated ....
So this defines an output table with 2 columns: name, property. The name column is actually the objects pschildname property. This is separate from the actual name property of the object. This is why you get the difference you see above.
There are other examples of this misconception in the PS world as well. The more you know.
The go-to resource on formatting files would be about_format.ps1xml. If you have issues with the default formatting and are not satisfied with simple Select-Object then you can create your own from copies.
FWIW you could have found both properties by just doing something like
Get-Item "HKLM:\SOFTWARE\MozillaPlugins\#microsoft.com/GENUINE" | Format-List *
That would have forced all properties to show and you would have seen name and pschildname.

Possibility of more then one xml input to jet?

Can we give more then one xml input to jet(java emitter template)?
If we can, then please give me an example.
You can't do it directly, but there are several techniques you can use to specify multiple XML's as input.
You can name one XML file inside the other or create a third XML file that simply names the two inputs (or however many you have). Say you have an XML file named in the input XML to JET:
<inputs>
<input name="...full file name here..." />
<inputs>
You can load this file in for use in JET like this:
<c:load url="{/inputs/input/#name}" var="root"/>
The load tag reads a file (defaults to XML, but other types can be input.) and makes its contents available via the variable name you specify ("root" in this case). Normally the url attribute expects a String value that is the file name, but since we're using an attribute out of the model we use the curly brackets and xpath notation to indicate the attribute value to use as the file name.
Once you've loaded the file in you can access its contents. For example, if the root element in that XML file was and it had an attribute named company, then you attribute could access that attribute value with the c:get
<c:get select="$root/policies/#company" />
You can load as many files as you want. Just use a different variable name to refer to the root of each parsed file.
You could get a bit more complex and store your multiple files in the same directory using a naming convention. Then you could just specify the one directory path and derive the file names for all of your inputs.

XPath Powershell query for a string

I'd like to know how to search for a string within an xml document. The object type is System.Xml.XmlNode.XmlDocument. The string can be anything with the document. I.e. attribute or element.
I tried
Select-Xml -Xml $xml -XPath "./Test"
but got no results
The pattern you are trying to use selects root nodes named Test.
You could use the pattern (//text()|//#*)[contains(string(), "test")], that selects the attributes that contain the string test or the text nodes that contain it (i.e. not the elements).
But you want to select the elements, right? Using (//*|//#*)[contains(., "test")] does that, but it selects elements that contain the string test, even if it is through some child element, which is not what is wanted either.
So I guess you'll have to use something like (//*[contains(text(), "test")]|//#*[contains(., "test")]), which gives you what you want, but is not very pretty.