Dear Powershell fellows,
i have a (maybe) very simple problem, but i have no idea how to solve it. I want to use a String Variable, that contains a single quote, within a XPath notation. I use the CMDlet Select-Xml. If there is no single quote within the String, Select-Xml is working completly fine. But if there is one single quote (for example in: don't) it crashes my script. Let me show you in detail.
Problem
$Name = "Dont display" ##is working completly fine
(Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath "//q1:Options[q1:Name = '$Name']" -Namespace $namespace).Node.InnerText ##is working completly fine
$Name = "Don't display" ##crashes script
(Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath "//q1:Options[q1:Name = '$Name']" -Namespace $namespace).Node.InnerText ##crashes script
The error output of powershell is:
Select-Xml : '//q1:Options[q1:Name = 'Don't display']' has an invalid token.
At line:251 char:41
+ ... ing_Name = (Select-Xml -Path "C:\SomePath\Somexml.xml" -XPath ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Select-Xml], XPathException
+ FullyQualifiedErrorId : System.Xml.XPath.XPathException,Microsoft.PowerShell.Commands.SelectXmlCommand
What i tried so far
Of course i tried different quotations, such as:
$Name = """Don't display"""
$Name = '"Don't display"'
$Name = "'Don't display'"
$Name = "'Don't display'"
$Name = 'Don't display'
$Name = ''Don't display''
It seems like, there might be a problem with powershell quotation rules and XPath notation.
But maybe anyone of you guys have an idea on how to solve it.
Thank you very much
It seems there's no escape character in XPath string literals, so you can't use your string delimiter inside a literal string as it terminates the string - i.e. this:
$name = "Don't display"
# single quotes:
# //q1:Options[q1:Name = 'Don't display']
# ^ terminates string literal
A quick (but naive) solution to your specific issue would be to just use double-quotes as delimiters instead:
$name = "Don't display"
# double quotes:
# //q1:Options[q1:Name = "Don't display"]
# ^ *doesn't* terminate string literal
but what if your data contains double quotes? Then you're back at square one..
$name = "Don""t display"
# double quotes:
# //q1:Options[q1:Name = "Don"t display"]
# ^ terminates string literal again
And in a pathological case, if your literal contains both single and double quotes then you can't use either as delimiters:
$name = "Don't d""isplay"
# single quotes:
# //q1:Options[q1:Name = 'Don't d"isplay']
# ^ terminates string literal
# double quotes:
# //q1:Options[q1:Name = "Don't d"isplay"]
# ^ also terminates string literal
In that case, you could resort to this answer which suggests converting your string literal into a concat
expression so that you get:
$name = "Don't d""isplay"
# //q1:Options[q1:Name = concat('Don', "'", 't d"isplay')]
# ^^^^^ use single quotes
# ^^^ use double quotes
# ^^^^^^^^^^^^ use single quotes
which you could generate with this:
$name = "Don't d""isplay"
$squote = "', `"'`", '"
$expr = "concat('{0}')" -f $name.Replace("'", $squote)
# Select-Xml -Xml $xml -XPath "//q1:Options[q1:Name = $expr]"
# ->
# //q1:Options[q1:Name = concat('Don', "'", 't d"isplay')]
and then the parts of your data that contain double-quotes are delimited with single quotes, and vice versa so they all terminate properly.
Note - you could probably optimise this for literals without one type of quote or the other and for consecutive single quotes, and it'll need some error handling added for $null
and other edge cases, but it basically does the job...
Update
Here's a full code sample to show it in action...
$xml = [xml] "<root><child Name=`"my'name`" /></root>"
$name = "my'name"
$squote = "', `"'`", '"
$expr = "concat('{0}')" -f $name.Replace("'", $squote)
Select-Xml -Xml $xml -XPath "//child[@Name = $expr]"
# Node Path Pattern
# ---- ---- -------
# child InputStream //child[@Name = concat('my', "'", 'name')]