为什么 invoke operator (&) 和 Invoke-Expression 对于相同的输入会产生不同的结果? [英] Why does invoke operator (&) and Invoke-Expression produce different results for the same input?

查看:40
本文介绍了为什么 invoke operator (&) 和 Invoke-Expression 对于相同的输入会产生不同的结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据我的理解,调用运算符 (&) 和 Invoke-Expression cmdlet 的行为应该相似.但是,如下所示,情况并非如此:

PS C:\Users\admin>powershell -Command "& {""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc=')))""}"回声你好世界"PS C:\用户\管理员>powershell -Command "IEX ""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc=')))"""你好,世界

这里,'ZWNobyAnaGVsbG93b3JsZCc=' 是 Base64 编码的字符串 "echo helloworld".

有人能澄清一下吗?

解决方案

Invoke-Expression(其内置别名是 iex)和 &,调用运算符,有不同的用途:>

  • Invoke-Expression 评估给定的字符串作为 PowerShell 源代码,就好像您已经执行了字符串的内容 直接作为命令.

    • 因此,它类似于 bash 中的 eval,因此 仅用于完全受调用者控制或输入的输入调用者信任.

    • 通常有更好的解决方案可用,所以 Invoke-Expression 通常应该避免

  • & 用于调用命令 (& [...]) 或 脚本块 (& { ... } [...]):

    • 这两种情况都不涉及将字符串评估为源代码.
<小时>

手头的案例:

您的命令的核心是以下表达式,它返回字符串
"echo 'helloworld'"(它的内容不包括封闭的 " - 这只是将结果字符串表示为 PowerShell 字符串文字):

[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc='))

另请注意,由于命令行的解析方式,原始命令中核心表达式周围的 ""..."" 被有效地忽略,这解释了为什么表达式被执行而不是被视为字符串的内容.[1]

因此,您的两个命令相当于:

  • <代码>&{ "echo 'helloworld'" }

    • & 执行脚本块内的语句,它恰好是一个 string 和一个单独的字符串 - 如果它没有被分配给变量或重定向到其他地方 - 只是按原样输出.
      在这种情况下,该命令实际上与单独执行 "echo 'helloworld'" 相同(包括封闭的 ",您可以认为是
      echo "echo 'helloworld'"),所以 echo 'helloworld' 打印到控制台.

    • 请注意,echoWrite-Output cmdlet 的内置别名,很少需要显式使用:命令或表达式的返回值是隐式输出,如果它们没有以某种形式捕获,如在这种情况下,将字符串本身作为语句执行只是输出字符串.(例如,您可以通过在提示处仅提交 'hi' 来尝试此操作).

  • iex "echo 'helloworld'"

    • 这使得 iex (Invoke-Expression) 将字符串的内容评估为源代码,因此执行 echo 'helloworld',它打印helloworld 到控制台.
<小时>

[1] 选读:PowerShell 在调用外部程序时引用问题

注意:

  • 据我所知,处理外部程序或从外部程序调用时的引用不是官方文档的一部分(在撰写本文时,about_Parsingabout_Quoting_Rulesabout_Special_Characters 提到了它 - 我已经打开了 GitHub 上的此问题 以解决该问题.

  • 现有处理方式存在缺陷,但在不破坏向后兼容性的情况下无法修复这些缺陷.

  • 从 PowerShell 调用时,最好的方法是使用 脚本块,它绕过引用问题 - 见下文.

即使您通过将 "" 转义为 "" 正确地嵌入了 " 来自 PowerShell 的整个 "..." 字符串-内部视角,需要使用 \" 进行额外转义,以便将它们传递给 外部程序,即使该外部程序是通过 powershell.exe 调用的另一个 PowerShell 实例.

以这个简化的例子为例:

powershell.exe -command " ""hi"" " # !!破碎的powershell.exe -command ' "hi" ' # !!破碎的

  • PowerShell 内部," ""hi"" "' "hi" ' 评估为具有文字内容的字符串 "hi",执行时打印 hi.

  • 遗憾的是,PowerShell 将此字符串作为 " "hi" " 传递给 powershell.exe - 注意 "" 是如何转变的变成普通的 " 并且封闭的 引号被替换为 引号 - 在解析后有效地导致 hi 新实例(因为 " "hi" " 被解析为子串 " ", hi串联,和 " "),所以 PowerShell 最终会尝试执行一个(可能不存在的)command,名为 hi.

相比之下,如果您设法将嵌入的作为 " 作为 \" (sic) - after 满足 PowerShell 自己的转义需求 -该命令按预期工作.因此,如上所述,您需要结合 PowerShell 内部转义和 for-the-CLI 转义,以便传递嵌入式 ",因此那个:

  • 在整个 "..." 中,每个嵌入的 " 必须转义为 \"" (sic) 或 \`" (原文如此)
  • 在整体'...'中,\"可以原样使用.

powershell.exe -command " \""hi\"" " # OKpowershell.exe -command " \`"hi\`" " # OKpowershell.exe -command ' \"hi\" ' # OK

或者,使用脚本块代替命令字符串,这样可以绕过引用问题:

powershell.exe -command { "hi" } # 好的,但只在从 PS 调用时有效

请注意,脚本块技术仅适用于从 PowerShell 调用,而不是从 cmd.exe 调用.

<小时>

cmd.exe 有自己的引用要求:值得注意的是,cmd.exe 仅支持 "" 嵌入双引号(不支持 `");因此,在上述解决方案中,只有
powershell.exe -command " \""hi\"" "cmd.exe (批处理文件)开始工作,无需额外转义.

\"" 的缺点是 \""...\"" 之间的内部空白被折叠成一个空格每个.为避免这种情况,请使用 \"...\",但是 cmd.exe 然后将 \" 实例之间的子字符串视为 未加引号,如果该子字符串包含诸如 |& 之类的元字符,则会导致命令中断;例如,powershell.exe -command "\"a|b\" "; 要解决您必须单独^-转义以下字符的问题:& | < > ^

powershell.exe -command ' "hi" ' 同样脆弱,因为 cmd.exe 不能将 ' 识别为字符串分隔符,因此嵌入 "..." 之外的任何元字符都会再次由 cmd.exe 本身解释;例如,powershell.exe -command ' "hi" |测量对象'

最后,仅使用 cmd.exe 中的 "" 来嵌入 " 有时 有效,但不是 reliably; 例如,powershell.exe -command " 'Nat ""King"" Cole' " 打印 Nat "King Cole (结束 " 缺失).
这似乎已在 PowerShell Core 中修复.

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:

  • & { "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.

这篇关于为什么 invoke operator (&amp;) 和 Invoke-Expression 对于相同的输入会产生不同的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆