From my understanding, the invoke operator (&) and the Invoke-Expression cmdlet should behave similar. However, as can be seen below, this is not the case:
PS C:\Users\admin> powershell -Command "& {""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsb
G93b3JsZCc=')))""}"
echo 'helloworld'
PS C:\Users\admin> powershell -Command "IEX ""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVs
bG93b3JsZCc=')))"""
helloworld
Here, 'ZWNobyAnaGVsbG93b3JsZCc='
is the Base64 encoded string "echo helloworld"
.
Can someone clarify?
Invoke-Expression
(whose built-in alias is iex
) and &
, the call operator, serve different purposes:
Invoke-Expression
evaluates a given string as PowerShell source code, as if you had executed the string's content directly as a command.
As such, it is similar to eval
in bash
and therefore only to be used with input that is fully under the caller's control or input that the caller trusts.
There are often better solutions available, so Invoke-Expression should generally be avoided
&
is used to invoke a command (& <nameOrPath> [...]
) or a script block (& { ... } [...]
):
- Neither case involves evaluating a string as source code.
In the case at hand:
The core of your command is the following expression, which returns the string
"echo 'helloworld'"
(its content doesn't include the enclosing "
- this is simply the representation of the resulting string as a PowerShell string literal):
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc='))
Also note that, due to how the command line is parsed, the ""...""
surrounding the core expression in your original commands are effectively ignored, which explains why the expression is executed rather than being treated as the content of a string.[1]
Therefore, your two commands amount to:
[1] Optional reading: PowerShell quoting woes when calling external programs
Note:
Handling of quoting with respect to external programs or when calling from an external programs is not part of the official documentation, as far as I can tell (as of this writing, neither about_Parsing nor about_Quoting_Rules nor about_Special_Characters mentions it - I've opened this issue on GitHub to address that).
There are flaws in the existing handling, but they cannot be fixed without breaking backward compatibility.
When calling from PowerShell, the best approach is to use a script block, which bypasses the quoting problems - see below.
Even though you correctly embedded "
by escaping them as ""
inside the overall "..."
string from a PowerShell-internal perspective, additional escaping of "
with \
is needed in order to pass them through to an external program, even if that external program is another instance of PowerShell called via powershell.exe
.
Take this simplified example:
powershell.exe -command " ""hi"" " # !! BROKEN
powershell.exe -command ' "hi" ' # !! BROKEN
PowerShell-internally, " ""hi"" "
and ' "hi" '
evaluate to a string with literal contents "hi"
, which, when executed, prints hi
.
Regrettably, PowerShell passes this string to powershell.exe
as " "hi" "
- note how the ""
turned into plain "
and the enclosing single quotes were replaced with double quotes - which effectively results in hi
after parsing by the new instance (because " "hi" "
is parsed as the concatenation of substrings " "
, hi
, and " "
), so PowerShell ends up trying to execute a (presumably nonexistent) command named hi
.
By contrast, if you manage to pass the embedded as "
as \"
(sic) - after meeting PowerShell's own escaping needs - the command works as intended.
Therefore, as stated, you need to combine PowerShell-internal escaping with for-the-CLI escaping in order to pass an embedded "
, so that:
- inside overall
"..."
, each embedded "
must be escaped as \""
(sic) or \`"
(sic)
- inside overall
'...'
, \"
can be used as-is.
powershell.exe -command " \""hi\"" " # OK
powershell.exe -command " \`"hi\`" " # OK
powershell.exe -command ' \"hi\" ' # OK
Alternatively, use a script block instead of a command string, which bypasses the quoting headaches:
powershell.exe -command { "hi" } # OK, but only works when calling from PS
Note that the script-block technique only works when calling from PowerShell, not from cmd.exe
.
cmd.exe
has its own quoting requirements:
Notably, cmd.exe
only supports ""
for embedding double quotes (not also `"
); thus, among the solutions above, only
powershell.exe -command " \""hi\"" "
works from cmd.exe
(a batch file) without additional escaping.
The down-side of \""
, however, is that runs of interior whitespace between \""...\""
are collapsed to a single space each. To avoid that, use \"...\"
, but cmd.exe
then sees substrings between the \"
instances as unquoted, which would cause the command to break if that substring contained metacharacters such as |
or &
; e.g., powershell.exe -command " \"a|b\" "
; to fix that you must individually ^
-escape the following characters: & | < > ^
powershell.exe -command ' "hi" '
is similarly brittle, because cmd.exe
doesn't recognize '
as a string delimiter, so any metacharacters outside embedded "..."
are again interpreted by cmd.exe
itself; e.g., powershell.exe -command ' "hi" | Measure-Object '
Finally, using just ""
from cmd.exe
for embedding "
sometimes works, but not reliably; e.g., powershell.exe -command " 'Nat ""King"" Cole' "
prints Nat "King Cole
(the closing "
is missing).
This appears to have been fixed in PowerShell Core.