Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does invoke operator (&) and Invoke-Expression produce different results for the same input?

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?

like image 903
Shuzheng Avatar asked Apr 25 '18 09:04

Shuzheng


People also ask

What is invoke () in Kotlin?

a.invoke(i_1, ..., i_n) Parentheses are translated to calls to invoke with appropriate number of arguments.

What does += mean in Kotlin?

+= Add a to b and assign to a. -= Subtract b from a and assign to a. *= Multiply a and b and assign to a. /= Divide a by b and assign to a. %= Divide a by b and assign the remainder to a.

Does Kotlin support Operatoroverloading?

Since Kotlin provides user-defined types, it also provides the additional functionality to overload the standard operators, so that working with user-defined types is easier. All of the unary, binary, relational operators can be overloaded.


1 Answers

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:

  • & { "echo 'helloworld'" }

    • & executes the statement inside the script block, which happens to be a string, and a string by itself - if it isn't assigned to a variable or redirected elsewhere - is simply output as-is.
      In this case, the command is effectively the same as just executing "echo 'helloworld'" by itself (including the enclosing ", which you can think of as
      echo "echo 'helloworld'"), so echo 'helloworld' prints to the console.

    • Note that echo is a built-in alias for the Write-Output cmdlet, whose explicit use is rarely necessary: Return values from commands or expressions are implicitly output, if they are not captured in some form, as in this case, where executing a string by itself as a statement simply outputs the string. (You can try this by submitting just 'hi' at the prompt, for instance).

  • iex "echo 'helloworld'"

    • This makes iex (Invoke-Expression) evaluate the string's content as source code, which therefore executes echo 'helloworld', which prints helloworld to the console.

[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.

like image 50
mklement0 Avatar answered Oct 26 '22 11:10

mklement0