当我将参数传递给嵌套的 Start-Process 命令时,PowerShell 删除了多个连续的空格

PowerShell removes multiple consecutive whitespaces when I pass arguments to a nested Start-Process command

此 powershell 代码工作正常:

powershell -NoProfile -Command {Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '"C:\Users\TestAccount\Desktop\cartella  con  spazi\test.vbs" "/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella  con  spazi" "/AppData:C:\Users\TestAccount\AppData\Roaming"'}

但是当我输入完整的命令时,powershell 在单词之间只留下一个 space 但在我的路径中有两个连续的:

Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command & {Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\`"C:\Users\TestAccount\Desktop\cartella  con  spazi\test.vbs\`" \`"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella  con  spazi\`" \`"/AppData:C:\Users\TestAccount\AppData\Roaming\`"'}"

同样的错误:

Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\`"C:\Users\TestAccount\Desktop\cartella  con  spazi\test.vbs\`" \`"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella  con  spazi\`" \`"/AppData:C:\Users\TestAccount\AppData\Roaming\`"'"

这是错误信息:

如您所见,在错误消息中路径仅包含一个 space 单词之间,而在代码中我正确输入了两个连续的。

这是输入凭据的代码(就在出现错误的行之前):

$username = 'Username'
$password = 'Password'
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword

几天来我一直在使用一行代码正常工作,但没有成功。
我输入的那些示例路径实际上是变量(具有不定数量的连续 spaces),因此通过将 -ArgumentList 括在单引号中,变量不会扩展。
你可以采用你认为最合适的策略来帮助我。
Windows10 专业版 64 位
Powershell 版本:5.1.19041.1237(集成于 Windows 10)。

更新:

@mklement0 问题是,实际上有问题的行包含一个可扩展变量:

Start-Process -FilePath powershell.exe -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList @"
-NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"$PWD\test.vbs\" \"/CurrentDirectory:$PWD\" \"/AppData:$env:APPDATA\"'"
"@

因此,如果路径包含单引号,则该行出错。

更新 2:

@mklement0 自从我将它放入脚本后,您的解决方案已停止工作,这正是我所需要的,并出现以下错误消息:

注意:这个答案解决了Start-Process问题;对于 不相关的 问题 .ps1 路径中有空格的文件在文件资源管理器中双击时无法 运行,请参阅 this answer .


尝试以下操作,参数传递逐字

Start-Process `
  -FilePath powershell.exe `
  -WorkingDirectory "$env:ALLUSERSPROFILE" `
  -Credential $credential `
  -WindowStyle Hidden `
  -ArgumentList @'
    -NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"C:\Users\TestAccount\Desktop\cartella  con  spazi\test.vbs\" \"/CurrentDirectory:C:\Users\TestAccount\Desktop\Cartella  con  spazi\" \"/AppData:C:\Users\TestAccount\AppData\Roaming\"'"
'@
  • -Command 参数包含在 "..." 中,以便按原样保留命令内部空间。

  • 命令字符串中的embedded"字符转义为\"

  • 外部 -ArgumentList 使用 here-string 来简化嵌入引用。

    • 此处使用verbatim形式(@'<newline>...<newline>'@);如果您需要字符串插值(嵌入变量引用和表达式的能力),请使用 _expandable 形式 @"<newline>...<newline>"@ - 请参阅下一节。
  • 虽然 PowerShell-内部 `,反引号用作转义字符,PowerShell 的 CLI 期望 " 个字符在命令行中被\-转义,以符合最广泛使用的转义约定。

    • 跨平台 PowerShell (Core) 7+ edition 现在也接受 "" 作为 \" 的替代,Windows.[=69 的另一种常见约定=]

    • 虽然您可能在某些情况下必须 结合 两种转义方法,即使用 `\" 以引用 " first 用于 CLI 和 then 用于 PowerShell 内部使用,在解释 -Command 参数的上下文中,这不是必需的在上面的命令中,因为内部 -ArgumentList 参数使用 引号,其中 " 可以按原样使用。


通用解决方案,提供参数通过变量:

假设要使用其值的变量被命名为 $dir1$dir2$dir3(并且它们的值没有嵌入 "字符。但是 " 无论如何在 Windows 上的文件系统路径中不受支持。

Start-Process `
  -FilePath powershell.exe `
  -WorkingDirectory "$env:ALLUSERSPROFILE" `
  -Credential $credential `
  -WindowStyle Hidden `
  -ArgumentList @"
    -NoProfile -Command "Start-Process -FilePath wscript.exe -Verb RunAs -ArgumentList '\"$($dir1 -replace "'", "''")\" \"/CurrentDirectory:$($dir2 -replace "'", "''")\" \"/AppData:$($dir3 -replace "'", "''")\"'"
"@
  • 此处字符串 (@"<newline>...<newline>"@) 的 插值 形式现在必须用于外部 -ArgumentList 参数。

  • 由于变量值被插入到 powershell.exe 将被视为单引号字符串 ('...') 的内容中,因此变量中嵌入的任何 ' 字符值必须转义为 '',这就是嵌入式 $(...) subexpressions 中的 -replace "'", "''" 所做的。


可选阅读:外部程序调用故障排除:

诊断调用外部程序的问题可能很困难,尤其是在这样的嵌套调用中。

准备:创建助手可执行文件echoArgsPause.exe:

下面创建了一个辅助可执行文件,echoArgsPause.exe,它回显调用它的完整命令行,以及它已将命令行解析成的各个参数,然后等待击键。

注:

  • 作为基于.NET的*.exe,它使用.NET的命令行解析规则,而后者又基于Microsoft's C/C++ conventions。不幸的是,并非所有可执行文件都保证使用相同的约定。而且,事实上,WSH CLI(cscript.exewscript.exe)不支持参数中的 embedded " 字符。

  • 运行 来自 Windows PowerShell 的代码,因为从 PowerShell (Core) 7.2,不支持通过 Add-Type 创建 可执行文件

  • 为了以后重用,将生成的 .\echoArgsPause.exe 文件放在 PATH 的目录中(在 $env:PATH 中列出的目录中)。

  • 要创建不等待击键的变体,请删除 Console.Write("Press a key to exit: ");Console.ReadKey(true); 行并将 -OutputAssembly .\echoArgsPause.exe 更改为 -OutputAssembly ./echoArgs.exe

Add-Type -OutputType ConsoleApplication -OutputAssembly .\echoArgsPause.exe -TypeDefinition @'
  using System;
  static class ConsoleApp {
    static int Main(string[] args) {
      Console.WriteLine("\nraw: [{0}]\n", Environment.CommandLine);
      for (int i = 0; i < args.Length; ++i) {
          Console.WriteLine("arg #{0}: [{1}]", i, args[i]);
      }
      Console.Write("Press a key to exit: ");
      Console.ReadKey(true);
      return 0;
    }
  }
'@

使用 echoArgsPause.exe 解决呼叫问题

  • 以下独立示例代码使用您的命令的简化版本,并使用 echoArgsPause.exe 代替您的目标可执行文件 (wscript.exe) 来打印命令行和解析的参数。
# Specify the full path to the helper executable.
# Here I'm assuming it is in the current dir.
$echoArgsPausePath = "$($PWD.ProviderPath)\echoArgsPause.exe"

# Sample variable values.
$dir1='c:\temp''o  1'
$dir2='c:\temp   2'
$dir3='c:\temp    3'

# Call your command, with echoArgsPause.exe in lieu
# of the target executable (wscript.exe)
Start-Process `
  -FilePath powershell.exe `
  -WorkingDirectory "$env:ALLUSERSPROFILE" `
  -WindowStyle Hidden `
  -ArgumentList @"
  -NoProfile -Command "Start-Process -FilePath "$echoArgsPausePath" -Verb RunAs -ArgumentList '\"$($dir1 -replace "'", "''")\" \"/CurrentDirectory:$($dir2 -replace "'", "''")\" \"/AppData:$($dir3 -replace "'", "''")\"'"
"@

我找到了另一个没有 Here-String 的解决方案:

$username = 'Username'
$password = 'Password'
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword
Start-Process -FilePath powershell -WorkingDirectory "$env:ALLUSERSPROFILE" -Credential $credential -WindowStyle Hidden -ArgumentList "-NoProfile -Command `"Start-Process -FilePath wscript -Verb RunAs -ArgumentList '\`"$((Get-Location).Path -replace "'", "''")\test.vbs\`" \`"/CurrentDirectory:$((Get-Location).Path -replace "'", "''")\`" \`"/AppData:$($env:APPDATA -replace "'", "''")\`"'`""