PowerShell 7.3.0中断命令调用

wr98u20j  于 2023-01-02  发布在  Shell
关注(0)|答案(1)|浏览(142)

我在一个PowerShell脚本中使用WinSCP。它突然停止工作。过了一段时间我可以弄清楚这个问题是从一个更新版本的PowerShell中出现的:
简化代码:

& winscp `
    /log `
    /command `
        'echo Connecting...' `
        "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`""

使用v7.2.7的错误消息
您lkjhlk.com不存在!
使用v7.3.0的错误消息
命令“open”的参数太多。
正如你所看到的v7.3.0 WinSCP接收不同的输入取决于版本的PS.我发现,差异有一些做的空格在主机键.如果他们被省略v7.3.0输出相同的错误.
PowerShell的什么更改导致了这个问题,我该如何修复它?(我该如何调试这样的问题?我玩了一点转义,但字符串看起来都一样,无论版本如何,没有明显的破坏性更改可以负责)

zzwlnbp8

zzwlnbp81#

    • PowerShell (Core)的7.3.0版本引入了一项 * 突破性的更改 ,涉及如何将 * 带有嵌入式"字符的参数 * 传递给 * 外部程序**,例如winscp

虽然此更改 * 大部分 * 是有益的,因为它修复了自v1以来从根本上破坏的行为this answer讨论旧的、破坏的行为),它**还 * 总是 * 破坏建立在破坏的行为上的现有变通办法***, 除了 * 调用 * 批处理文件 * 和WSH CLI(wscript.execscript.exe)及其关联脚本文件(文件扩展名为.vbs.js)的命令。

    • 注意事项**:
  • GitHub issue #18694意味着*7.3.1之后的某些 * 版本 * 以及所有更高版本将进行此突破性更改 * 选择加入 ,但 * 仅限于Windows,以实现向后兼容性;即:
  • 在Windows上:
    • 默认情况下 *,旧的解决方法将继续有效。
  • 要获得新的正确行为,需要(临时)设置

$PSNativeCommandArgumentPassing = 'Standard'

  • 在类似 * Unix * 的平台上:
  • 新的正确行为($PSNativeCommandArgumentPassing = 'Standard')将保留默认值
  • 旧的解决方法需要(临时)设置$PSNativeCommandArgumentPassing = 'Legacy'才能继续工作,7.3.0中已经是这种情况
    • 要使现有的变通方法继续起作用,请将$PSNativeCommandArgumentPassing首选项变量(临时)设置为'Legacy'**:
# Note: Enclosing the call in & { ... } makes it execute in a *child scope*
#       limiting the change to $PSNativeCommandArgumentPassing to that scope.
& {
  $PSNativeCommandArgumentPassing = 'Legacy'
  & winscp `
    /log `
    /command `
        'echo Connecting...' `
        "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=`"`"ssh-ed25519 includes spaces`"`"" 
}

不幸的是因为winscp.exe只接受
"open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces"""(即,嵌入的"转义为""),并且也不是最广泛使用的形式
"open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=\"ssh-ed25519 includes spaces\""(嵌入的"转义为\"),对于winscp.exe,具体而言,将继续需要变通方法

    • 如果您 * 不 * 希望依赖于必须修改$PSNativeCommandArgumentPassing作为变通方法,以下是在 * v7.2-和v7.3 +中均起作用的变通方法:
  • 使用**--%,即停止解析标记**,但是它有缺陷和严重的限制,特别是不能(直接)在其后的参数中使用PowerShell * 变量 * 或子表达式-请参阅this answer了解详细信息;但是,如果您将--%用作 * array * 的一部分,则可以绕过这些限制,首先构造 * array * 并将其赋给变量,然后通过splatting传递:
# Note: Must be single-line; note the --% and the 
#       unescaped use of "" in the argument that follows it.
#       Only "..." quoting must be used after --% 
#       and the only variables that can be used are cmd-style 
#       *environment variables* such as %OS%.
winscp /log /command 'echo Connecting...' --% "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces""" 

# Superior alternative, using splatting:
$argList = '/log', '/command', 'echo Connecting...', 
           '--%', "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces"""
winscp @argList
  • 或者,通过cmd /c呼叫
# Note: Pass-through command must be single-line,
#       Only "..." quoting supported, 
#       and the embedded command must obey cmd.exe's syntax rules.
cmd /c @"
  winscp /log /command "echo Connecting..." "open sftp://kjhgk:jkgh@lkjhlk.com/ -hostkey=""ssh-ed25519 includes spaces"""
"@
  • 注意:您并不一定需要使用here-string(@"<newline>...<newline>"@@'<newline>...<newline>'@),但它有助于提高可读性并简化嵌入式引号的使用。

这两种变通方法都允许您 * 直接以带引号的形式 * 传递参数,但不幸的是,还需要在一行 * 上制定整个(传递)命令 *-除非--%与splitting组合使用。

背景信息:

Windows上v7.3的默认$PSNativeCommandArgumentPassing'Windows'

  • 遗憾的是,保留了 * 旧的、损坏的行为,用于调用批处理文件 * 和WSH CLI(wscript.execscript.exe)及其关联的脚本文件(文件扩展名为.vbs.js
  • 虽然,仅对于这些程序,这允许现有的变通方法继续发挥作用,但 * 未来 * 只需要在v7.3+中运行的代码将继续承受对这些模糊变通方法的需求,这些变通方法 * 建立在破坏的行为之上 *。
  • 另一种 * 未 * 实施的方案是在PowerShell * 中为这些程序以及一些程序无关的适应 * 构建适应机制,因此在绝大多数情况下,将来甚至不需要解决方案:参见GitHub issue #15143
  • 还有troublesome signs that this list of exceptions will be appended to, piecemeal,它几乎保证了给定PowerShell版本的混淆,即哪些程序需要变通方法,哪些不需要。
  • 值得推荐的是,**对于所有其他程序,*使PowerShell在必要时对参数进行编码- 在后台重新构建 * 命令行,如下所示(针对"):
  • 为遵循C++命令行解析规则(如C/C ++/. NET应用程序所使用的)/CommandLineToArgv WinAPI函数的解析规则的程序编码参数,这是解析进程命令行的 * 最广泛遵守的约定 *。
  • 简单地说,这意味着***嵌入 * "字符***嵌入 * 在参数中,被目标程序视为它的 * 逐字部分 *,被转义为\",而\本身 * 仅 * 在它位于"之前但要被解释为 * 逐字 * 时才需要转义 *(作为\\)。
  • 请注意,如果您将$PSNativeCommandArgumentPassing值设置为'Standard'(这是类Unix平台上的默认值,其中此模式修复 * 所有 * 问题,并使v7.3+代码永远不需要解决方案),则此行为适用于 * 所有 * 外部程序,即上述例外不再适用)。

有关v7.3重大变更的影响总结,请参见GitHub上的此评论。

如果您需要编写跨版本的PowerShell代码:将Native moduleInstall-Module Native;作者),具有**ie**函数(缩写为:Invoke Executable)是一个polyfill,在 * 绝大多数 * 情况下,它提供了无变通方案的跨版本(v3+)、跨平台和跨版本行为-只需将ie前置到外部程序调用中。

警告:在当前的特定情况下,它将 * 不 * 工作,因为它不知道winscp.exe需要""-转义。

相关问题