Home > Enterprise >  Powershell 7: using ampersand (&) in string literals
Powershell 7: using ampersand (&) in string literals

Time:01-25

I'm trying to execute following command in powershell but have no how to escape the ampersand character as this is part of the url

  az rest `
    --method GET `
    --uri ("https://graph.microsoft.com/v1.0/groups?`$count=true&`$filter=startsWith(displayName,'some filter text')&`$select=id,displayName") `
    --headers 'Content-Type=application/json'

As the & character is used to start a new command, it breaks the url and want to execute the remainder.

Is there a way to tell powershell not to do that?

CodePudding user response:

Olaf's answer provides an effective solution; let me add an explanation:

The source of the problem is a confluence of two behaviors:

  • When calling external programs, PowerShell performs on-demand double-quoting of each argument solely based on whether a given argument value contains spaces - otherwise, the argument is passed unquoted - irrespective of whether or not the value was originally quoted in the PowerShell command (e.g., cmd /c echo ab, cmd /c echo 'ab', and cmd /c echo "ab" all result in unquoted ab getting passed as the last token on the command line PowerShell rebuilds behind the scenes to ultimately use for execution).

  • The Azure az CLI is implemented as a batch file (az.cmd) and when a batch file is called, it is cmd.exe that parses the arguments given; surprisingly - and arguably inappropriately - it parses them as if the command had been submitted from inside a cmd.exe session.

As a result, if an argument is passed from PowerShell to a batch file that (a) contains no spaces, yet (b) contains cmd.exe metacharacters such as &, the call breaks.

A simple demonstration, using a cmd /c echo call as a stand-in for a call to a batch file:

# !! Breaks, because PowerShell (justifiably) passes *unquoted* a&b
# !! when it rebuilds the command line to invoke behind the scenes.
PS> cmd /c echo 'a&b'
a
'b' is not recognized as an internal or external command,
operable program or batch file.

There are three workarounds:

  • Use embedded "..." quoting:
# OK, but with a CAVEAT: 
#   Works as of PowerShell 7.2, but arguably *shouldn't*, because
#   PowerShell should automatically *escape* the embedded " chars. as ""
PS> cmd /c echo '"a&b"'
"a&b"
# OK, but with a CAVEAT:
#  Requires "..." quoting, but doesn't recognize *PowerShell* variables,
#  also doesn't support single-quoting and line continuation.
PS> cmd /c echo --% "a&b"
 "a&b"
  • Call via cmd /c and pass a single string encompassing the batch-file call and all its arguments, (ultimately) using cmd.exe's syntax.
# OK (remember, cmd /c echo stands for a call to a batch file, such as az.cmd)
# Inside the single string passed to the outer cmd /c call,
# be sure to use "...", as that is the only quoting cmd.exe understands.
PS> cmd /c 'cmd /c echo "a&b"'
"a&b"

Taking a step back:

Now, wouldn't it be nice if you didn't have to worry about all these things? Especially since you may not know or care if a given CLI - such as az - just so happens to be implemented as a batch file?

As a shell, PowerShell should do its best to relay arguments faithfully behind the scenes, and allow the caller to focus exclusively on satisfying only PowerShell's syntax rules:

  • Unfortunately, PowerShell has to date (PowerShell 7.2) generally done a very poor job in this regard, irrespective of cmd.exe's quirks - see this answer for a summary.

  • With respect to cmd.exe's (batch-file call) quirks, PowerShell could predictably compensate for them in a future version - but it looks like that isn't going to happen, unfortunately; see GitHub issue #15143.

CodePudding user response:

I don't have access to an Azure tennant right now to test and I actually don't have experiences with the Azure CLI in general but I'd expect this to work:

az rest `
    --method GET `
    --uri 'https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some filter text)&$select=id,displayName' `
    --headers 'Content-Type=application/json'

or this:

az rest --method GET --headers "Content-Type=application/json" `
    --% --uri "https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some filter text)&$select=id,displayName"

I only added the backticks for better readability - you may remove them in your actual code.

  •  Tags:  
  • Related