Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to escape the contents of a dynamic variable in PowerShell?

Tags:

powershell

Let's say I have the variable $password = Get-Passwd ACME\bob where I get the password of a given user. The password contains all sorts of special characters including $, that is used by PowerShell.

I need to dynamically use the password in the following command:

cmdkey.exe /add:$hostname /user:"$user" /pass:"`"$password`""

I need to escape the " character on both sides so the command is parsed as pass:"123qwe" and not pass:123qwe. The issue is that it breaks when the password includes a $ character. How can I pass the password to this command without breaking it?

like image 743
JChris Avatar asked Nov 27 '25 18:11

JChris


2 Answers

Running Executables in PowerShell

For most executables, it's not necessary to manually quote the parameters in PowerShell. Just write the command using any variables, and the PowerShell parser will automatically quote the arguments for the program's command line. This usually "just works."

cmdkey.exe /add:hostname /user:username /pass:$pass

You can inspect the actual command line that PowerShell passes to an executable by using a small program I wrote called showargs.exe, available at the following page:

https://www.itprotoday.com/management-mobility/running-executables-powershell

Example:

showargs cmdkey.exe /add:hostname /user:username /pass:$pass

The showargs.exe program simply echoes its command line to standard output so you can see the literal command line that PowerShell actually passes to the executable.

An embedded $ character shouldn't be a problem. If that character is in a PowerShell string variable, PowerShell will pass it along as a part of the command line string. Example:

$pass = 'my$pass'
cmdkey.exe /add:hostname /user:username /password:$pass

No matter how you pass the command line to the program, it's important to note that the interpretation of that command line is up to that individual program. (For example, if an executable's parser doesn't support certain characters, no amount of quoting or parsing will allow you to work around the limitation.)

Cmdkey.exe Uses a Non-Standard Parser

In my prefunctory testing of of cmdkey.exe, it seems it does not have a way of "escaping" the " character on its command line. Since this seems to be the case, you will not be able to use cmdkey.exe to store a credential that contains an embedded " character.

Embedding a Space in a Cmdkey.exe Command Line Argument

Because cmdkey.exe uses a non-standard command-line parser, you can't use a variable on its command line that contains embedded spaces. For example:

PS C:\> $pass = "my pass"
PS C:\> showargs cmdkey.exe /add:hostname /user:username /password:$pass
cmdkey.exe /add:hostname /user:username "/password:my pass"

The "/password:my pass" evidently confuses the cmdkey.exe parser, so we have to work around this behavior by bypassing PowerShell's default parsing behavior. The simplest way to do this is by escaping the quotes around the argument containing the space:

PS C:\> showargs.exe cmdkey.exe /add:hostname /user:username /password:`"$pass`"
cmdkey.exe /add:hostname /user:username /password:"my pass"

In any case, you can use showargs.exe to diagnose the trouble and work out a solution appropriate to the executable you need to run.

like image 137
Bill_Stewart Avatar answered Nov 30 '25 06:11

Bill_Stewart


tl;dr

  • Your command should work - except if $password contains " chars.

    • Embedded $ chars., by contrast, should not be a problem.
  • Your /pass:"`"$password`"" technique - i.e., explicit, embedded double-quoting - also handles values with embedded spaces correctly, unlike the /pass:$password technique (also) suggested in Bill Stewart's helpful answer.

    • You can simplify the command by omitting the outer "..." quoting, as also suggested in Bill's answer:
      /pass:`"$password`"
    • Caveat: If PowerShell's argument-passing worked correctly, these techniques shouldn't work and if it ever gets fixed, such techniques will stop working - see this answer for background.
  • As for supporting " chars. in values: even \-escaping them doesn't always work, namely when spaces are also involved - see details below.


The issue is that it breaks when the password includes a $ character.

Your command passes any $ characters embedded in the value of $password through to the target program as-is.

Therefore, if there is a problem, the implication is that your target program - cmdkey.exe - interprets $ characters, but note that the docs make no mention of that.
If it indeed does, however, you would have to escape $ characters as required by the target program in order to pass them literally.

Caveat: There is a fundamental limitation, however:

Commands typically break if the argument value contains spaces AND embedded " chars, whether the latter are properly escaped for the target program or not.

Normally, you'd escape value-internal " as \" so as not to break the enclosing double-quoting of the value:

# !! Only works if:
# !!  * $password contains NO "
# !!  * $password contains " but NOT ALSO SPACES
# !!  * $password contains PAIRS of " and what is between each
# !!    pair does not contain spaces
cmdkey.exe /add:$hostname /user:"$user" /pass:`"$($password -replace '"', '\"')`"

Note:

  • Escaping embedded " as \" is a not a standard per se, but it is recognized by most external programs; the only notable exceptions are batch files - see this answer for details.
  • Arguably, PowerShell should handle this escaping automatically - see this GitHub issue for details.

If Windows PowerShell thinks it cannot pass the resulting token as-is as a single argument to the target program, it blindly applies double-quoting around the entire token[1], which, in combination with the escaped ", can result in invalid syntax:

E.g., if $password literally contains a " b and is manually escaped as a \" b by the command above, PowerShell ends up passing the following behind the scenes:

 ...  "/pass:"a \" b""

That is, the resulting literal token that PowerShell saw - /pass:"a \" b" - was blindly enclosed in double quotes, as a whole, even though most target programs would parse /pass:"a \" b" correctly as-is.
As a result, the explicitly provided double-quoting is invalidated (as it would then require another level of escaping) - and short of using --%, the stop-parsing symbol, which then limits you to literals (or %...%-style environment-variable references), there is no way around that.

If the target program recognizes an argument if it is double-quoted as a whole (e.g.,
"/pass:a b" instead of /pass:"a b"), you can omit the explicit double-quoting around the argument value.
Do note, however, that some target programs - including cmdkey.exe and notably msiexec - do not recognize the argument if it is double-quoted as a whole.

... /pass:$($password -replace '"', '\"')

With $password literally containing a " b, PowerShell then passes behind the scenes:

... "/pass:a \" b"

This is syntactically valid - for target programs that recognize \" as an escaped ", which is the norm - but, as stated, the fact that the entire argument is enclosed in `"...", and not just the value may not be supported by the target program.


[1] Windows PowerShell ignores any \-escaping in the resulting token and considers all embedded " to have syntactic function: From that perspective, if the token is not composed of any mix of directly concatenated unquoted and double-quoted strings, enclosing double-quoting is blindly applied - which may break the command.
This behavior is not only obscure, it prevents robust, predictable parameter passing.
PowerShell Core now recognizes \" as as escaped; however, quotes that aren't pre-escaped as \" still result in broken quoting; e.g., 'a "b c" d' is passed as "a "b c" d", which target programs parse as 2 arguments, a b and c d (after quote removal).

like image 38
mklement0 Avatar answered Nov 30 '25 04:11

mklement0