3

I am using Powershell to request a password from a user if not provided, based upon another answer. I then pass the password (no pun intended) to some program, do-something.exe. Rather than have an intermediate variable, I tried to convert the password to a normal string "inline":

[CmdletBinding()]
Param(
  [Parameter(Mandatory, HelpMessage="password?")] [SecureString]$password
)
do-something password=${[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))}

That doesn't work. I could only get it to work using a temporary, intermediate variable:

[CmdletBinding()]
Param(
  [Parameter(Mandatory, HelpMessage="password?")] [SecureString]$password
)
$pwd=[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
do-something.exe password=$pwd

Did I make a mistake trying to evaluate the password inline when invoking do-something.exe? How can this be done?

3
  • 1
    instead of dosomething password=${ } use dosomething password=( ). Also [System.Net.NetworkCredential]::new('', $pwd).Password is a lot easier than Marshal. Commented Oct 26, 2022 at 2:15
  • 1
    "… the inline code uses password=...". Indeed, that was the problem. I copied the solution which had been written based upon my example, not on my original code. My mistake. "[T]his has turned into one big mess of a question." That it has. The original question was focused on value interpolation (which you answered with a great explanation—thank you), but then we got off into a discussion on Powershell's handling of secure strings, and that is a $(mess * 2). I'll tidy up the question. Commented Oct 27, 2022 at 3:44
  • I guess I meant $($mess * 2). (Sigh.) It will take me a while to get the hang of this, I think. Commented Oct 27, 2022 at 3:53

1 Answer 1

2

${...} is a variable reference, and whatever ... is is taken verbatim as a variable name.

  • Enclosing a vairable name in {...} is typically not necessary, but is required in two cases: (a) if a variable name contains special characters and/or (b) in the context of an expandable string ("..."), to disambiguate the variable name from subsequent characters - see this answer

In order to embed an expression or command as part of an argument, use $(...), the subexpression operator, and preferably enclose the entire argument in "..." - that way, the entire argument is unambiguously passed as a single argument, whereas an unquoted token that starts with a $(...) subexpression would be passed as (at least) two arguments (see this answer).

  • If an expression or command by itself forms an argument, (...), the grouping operator is sufficient and usually preferable - see this answer

Therefore:

[CmdletBinding()]
param(
  [Parameter(Mandatory, HelpMessage="password?")]
  [SecureString] $password
)

# Note the use of $(...) and the enclosure of the whole argument in "..."
do-something "password=$([Runtime.InteropServices.Marshal]::PtrToStringBSTR([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)))"

Also note:

  • On Windows it doesn't make a difference (and on Unix [securestring] instances offer virtually no protection and should be avoided altogether), but it should be [Runtime.InteropServices.Marshal]::PtrToStringBSTR(), not [Runtime.InteropServices.Marshal]::PtrToStringAuto()

  • As Santiago Squarzon points out, there is an easier way to convert a SecureString instance to its plain-text equivalent (which should generally be avoided[1], however, and, more fundamentally, use of [securestring] in new projects is discouraged[2]):

    [pscredential]::new('unused', $password).GetNetworkCredential().Password
    

[1] A plain-text representation of a password stored in a .NET string lingers in memory for an unspecified time that you cannot control. More specifically, if it is part of a process command line, as in your case, it can be discovered that way. Of course, if the CLI you're targeting offers no way to authenticate other than with a plain-text password, you have no other choice.

[2] See this answer, which links to this .NET platform-compatibility recommendation.

Sign up to request clarification or add additional context in comments.

19 Comments

Can you elaborate on why all these things should be avoided (especially the "easier way")? In my case I simply want to 1) prevent the password from appearing in my command history, 2) prevent the password from appearing on the screen, and 3) prevent the password from appearing in the terminal window title. Generally from the *nix world I just don't want to be passing a password on the command line, as historically I think it was possible to see other users' commands (although I don't know if that is still the case).
A little-off-topic follow-up: I also have a param $path of type [System.IO.FileInfo]. When I invoke do-something.exe and pass the path as the last argument, do I need to quote it some way? Currently I'm just passing do-something.exe --password=$password $path and it's working fine, even with filenames containing spaces, but I don't understand why that works (unless do-something.exe somehow just takes everything after the last parameter as the filename). This relates to your comment about quoting the entire password arg. I still haven't got a feel for when Powershell requires quoting.
@Garret Wilson, the point is that the SecureString class should be avoided as a secure string, that's why I came up with a HiddenString idea some time ago which basically does the same (besides easier conversions and warnings) but better covers the load with its name: security through obscurity. In general, you should avoid passing passwords in your scripts, instead authenticate the account that runs the script.
@GarretWilson, re [securestring]: please see my update (footnotes). As for using variables (in isolation) as arguments: They never need double-quoting in PowerShell, irrespective of quoting. If the target command is an external program and the variable doesn't already contain a string, its value is stringified, essentially with .ToString()
I should have realized sooner that the [securestring] discussion was ancillary and would be better discussed in a separate question. Getting back to this question, @mkelment0 your answer relating to the interpolation was just what I needed. I had gotten confused because languages such as JavaScript use a single interpolation quoting, e.g. the answer is ${foo +bar()}, while in PowerShell it appears we need different delimiters for variables and for expressions, i.e. the answer is $(${foo} + bar()) (adding curly brackets to make the point). But it's sinking in. Thank you.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.