-1

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

LKo.exp
  • 164
  • 7
  • 1
    Ideally your XPath API would provide you with a way to pass in a variable value so that your XPath would be e.g. `//q1:Options[q1:Name = $dp]` where you could pass in a string value "Don't display" for the variable `dp`. But I don't think the Powershell or .NET XPath API has an easy interface for XPath variables. – Martin Honnen Sep 01 '22 at 08:52
  • Maybe try escaping the apostrophe with `$Name = "Don't display" -replace "'", '\"''\"'` ? (results in a string `Don\"'\"t display`) – Theo Sep 01 '22 at 09:13
  • What does the xml look like? – js2010 Sep 01 '22 at 13:57

1 Answers1

1

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')]

mclayton
  • 8,025
  • 2
  • 21
  • 26
  • Thank you very much. This is a really strong answer and the solution with the $expr solved error message "terminates string literal". But unfortunately now my Select-Xml gives me back a null. Dont know why exactly yet, maybe Select-XML cant handle the concat input. I will investigate this problem further. Thank you for help ! – LKo.exp Sep 01 '22 at 12:16
  • 1
    @LKo.exp - I've added a stand-alone example to show it working with ```Select-Xml``` - you might need to check your xpath is correct, but ```concat``` seems to work fine if it actually matches something (at least in PS 7.2.6...) – mclayton Sep 01 '22 at 12:42
  • Thank you very much!!! Found the additional mistake in my script and now it is working like a charme. :) – LKo.exp Sep 01 '22 at 12:59