PowerShell (Core)的7.3.0版本引入了一个与外部程序(如winscp
)有关的重大变更,涉及到如何传递带有嵌入的"
字符(以及空字符串参数)[1]。[2]
虽然这个变更在很大程度上是有益的,因为它修复了自v1以来一直存在的基本错误行为(本答案讨论了旧的错误行为),但它也不可避免地破坏了建立在错误行为基础上的现有解决方法,除了对于调用批处理文件和WSH CLIs(wscript.exe
和cscript.exe
)以及它们关联的脚本文件(文件名扩展名为.vbs
和.js
)的解决方法。
为了使现有的解决方法继续工作,请将
$PSNativeCommandArgumentPassing
preference variable(暂时)设置为
'Legacy'
。
& {
$PSNativeCommandArgumentPassing = 'Legacy'
& winscp `
/log `
/command `
'echo Connecting...' `
"open sftp://kjhgk:jkgh@example.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`""
}
很不幸的是,因为只接受这种形式的进程命令行(即嵌入的<">被转义为<"">),而不接受更常用的形式(嵌入的<">被转义为<\">),这是现在修复后的行为,对于,特别是,仍然需要一个解决方法。
如果你不想依赖于修改< $PSNativeCommandArgumentPassing>来解决问题,这里有一些在v7.2-和v7.3+中都有效的解决方法:
使用
--%
,停止解析标记,但是这种方法有缺陷和严格限制,特别是不能在其后的参数中(直接)使用PowerShell
变量或子表达式 - 有关详细信息,请参阅
此答案;但是,如果您将
--%
作为您构建并分配给变量的
数组的一部分,然后通过
splatting传递,您可以绕过这些限制:
winscp /log /command 'echo Connecting...' --% "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces"""
$argList = '/log', '/command', 'echo Connecting...',
'--%', "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces"""
winscp @argList
或者,
通过cmd /c
调用:
cmd /c @"
winscp /log /command "echo Connecting..." "open sftp://kjhgk:jkgh@example.com/ -hostkey=""ssh-ed25519 includes spaces"""
"@
注意:您不一定需要使用
here-string(
@"<newline>...<newline>"@
或
@'<newline>...<newline>'@
),但它有助于提高可读性并简化嵌入引用的使用。
无论是哪种解决方法,都允许您直接传递参数,但不幸的是,这也要求您将整个(传递)命令制定为一行 - 除非将
--%
与扩展操作符结合使用。
背景信息:
在Windows上,v7.3+的默认$PSNativeCommandArgumentPassing
值为'Windows'
:
遗憾的是,对于调用批处理文件和WSH CLIs(wscript.exe和cscript.exe)以及它们关联的脚本文件(文件名扩展名为.vbs和.js等),PowerShell仍然保留了旧的、有问题的行为。
对于这些程序而言,这样做可以使现有的解决方法继续正常工作,但是只需要在v7.3+中运行的未来代码仍然需要依赖这些晦涩的解决方法,这些解决方法是基于有问题的行为构建的。
另一种选择是将这些程序以及一些与程序无关的适应措施纳入PowerShell中,这样在绝大多数情况下,未来将不再需要解决方法。但是这种选择并没有被实施,具体信息请参见GitHub问题#15143。
此外,还有一些令人头疼的迹象表明,这个例外列表还将逐步扩充,这几乎可以保证在给定的PowerShell版本中,对于哪些程序需要解决方法以及哪些不需要会产生混淆。
值得赞扬的是,对于所有其他程序,PowerShell在必要时重新构建命令行时对参数进行了编码,具体规则如下:
对于遵循C++命令行解析规则(如C/C++/.NET应用程序所使用的规则)/CommandLineToArgv WinAPI函数解析规则的程序,PowerShell对参数进行编码,这是解析进程命令行的最常见约定。
简而言之,这意味着将嵌入在参数中的“字符”作为目标程序的“原样部分”进行转义,转义为\",如果\本身需要转义(作为\\)只有在它之前是一个“字符”,但是要被原样解释。
请注意,如果将$PSNativeCommandArgumentPassing值设置为“Standard”(这是类Unix平台上的默认值,在这种模式下,解决了所有问题,使v7.3+代码不再需要解决方法),则此行为适用于所有外部程序,即上述例外不再适用。
关于 v7.3 变更的影响摘要,请参阅 GitHub 上的此评论。
如果您需要编写跨版本、跨平台的 PowerShell 代码:Native
模块(由我编写,使用 Install-Module Native
安装),提供了一个名为 ie
的函数(即 Invoke Executable 的缩写),它是一个填充函数,可以在绝大多数情况下实现无需解决问题的跨版本(v3+)、跨平台和跨版本行为 - 只需在外部程序调用前加上 ie
。
注意:在当前特定情况下,它将无法工作,因为它不知道 winscp.exe
需要进行 ""
转义。
[1] 有关详细信息和解决方法,请参阅
this answer。
[2] 在后续版本中撤销该更改并将新行为设置为“选择加入”曾经被考虑过,但最终决定不这样做 - 请参阅
GitHub issue #18694。
choco install echoargs -y
安装echoargs.exe
,该工具显示了 PowerShell 在幕后构建的原始命令行以及(大多数)外部程序如何将其解析为参数。或者,您可以即席编译一个实用程序:请参见此答案。在 PowerShell 7.3+ 中,只有在您首先显式(临时)设置$PSNativeCommandArgumentPassing = 'Legacy'
时才会看到破损的旧行为。 - mklement0