如果 msiexec 的命令行参数包含 space,则它们会在 PowerShell 上中断

Command line arguments for msiexec break on PowerShell if they contain space

我正在尝试在 InstallShield 安装程序中设置 public 属性,其值包含 space。在 运行 安装 MSI 安装程序时,我在 PowerShell 提示符下使用以下命令。由于该值包含 space 所以我使用双引号来传递值

msiexec -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"

它中断了命令,因为参数值 C:\new folder\data.txt 在字符串 new folder 中有一个 space。结果显示在 msiexec 的错误提示下方。它表明传递给 msiexec 命令的参数有问题:

如果我 运行 在 Windows 默认命令提示符下使用完全相同的命令,那么它工作正常。

到目前为止我尝试过的选项如下:

  1. 使用单引号代替双引号
  2. 根据 this 的回答,在参数 space 之前使用反引号 (`) 字符。

试试这个

msiexec -i "myinstaller.msi" MYDIRPATH=`"C:\new folder\data.txt`"

PowerShell 中的转义符是重音符 (`)。

注:

  • 此答案解决了从 PowerShell 直接但异步 调用 msiexec 的问题,如问题中所示。如果你想要 synchronous 调用,使用 Start-Process with the -Wait switch, as shown in ,这也通过将参数作为 传递来避免引用问题带有 嵌入引号 .

    的单个字符串
  • 此外,如果添加-PassThru开关,您可以获得一个进程信息对象,允许您查询msiexec退出代码稍后:

# Invoke msiexec and wait for its completion, then
# return a process-info object that contains msiexec's exit code.
$process = Start-Process -Wait -PassThru msiexec '-i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"'
$process.ExitCode
  • 注意:有一个简单的技巧甚至可以直接调用msiexec同步:通过管道将调用传递给 cmdlet,例如 Wait-Process
    (msiexec ... | Wait-Process) - 有关详细信息,请参阅

补充

在 PowerShell 中调用 外部程序 是出了名的困难,因为 PowerShell,完成后它 拥有 首先解析,有必要 重建 实际调用的命令行 在幕后 就引用而言,应用的规则远非显而易见。

  • 注意:
    • 虽然在这种特殊情况下重新引用 PowerShell 在幕后执行是合理的(见底部部分),但这不是 msiexec.exe 所要求的。
    • 至少在 PowerShell 7.1 之前,一些重新引用是彻头彻尾的 损坏 ,这些问题以及可能即将进行的(部分)修复总结在 this answer.
    • Marko Tica 的解决方法 依赖于 这种损坏的行为,并且目前 实验性 功能试图修复损坏的行为(PSNativeCommandArgumentPassing,自 Core 7.2.0-preview.5 起可用), 解决方法会中断 。可悲的是,看起来只是 省略 解决方法也不起作用,因为决定 包含特殊报价要求的住宿高调的 CLI,例如 msiexec - 请参阅 GitHub issue #15143.

为了帮助解决这个问题,PSv3+ 提供了 --%, the stop-parsing symbol,这非常适合这里,因为 命令行包含没有PowerShell变量或表达式的引用--%将命令行的其余部分 按原样 传递给外部实用程序,除了可能扩展 %...% 样式的环境变量 :

# Everything after --% is passed as-is.
msiexec --% -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"

如果您需要在您的msiexec中包含 PowerShell 变量或表达式的值call,最安全的选择是 通过 cmd /c 使用包含 整个命令行 的单个参数进行调用;为了引用方便,以下示例使用可扩展的 here-string(请参阅 this answer for an overview of PowerShell's string literals 的底部部分)。

$myPath = 'C:\new folder\data.txt'

# Let cmd.exe invoke msiexec, with the quoting as specified.
cmd /c @"
msiexec --% -i "myinstaller.msi" MYDIRPATH="$myPath"
"@

如果您不介意安装第三方模块ie函数来自Native module (Install-Module Native) 消除了对任何变通方法的需要:它修复了具有 嵌入 " 字符的参数的问题.以及 empty-string 参数,并包含重要的 CLI,例如 Windows、 和 [=134= 上的 msiexec ] 将继续按预期工作,即使 PSNativeCommandArgumentPassing 功能生效:

# `ie` takes care of all necessary behind-the-scenes re-quoting.
ie msiexec -i "myinstaller.msi" MYDIRPATH="C:\new folder\data.txt"

至于你试过的

已翻译 PowerShell
MYDIRPATH="C:\new folder\data.txt"进入
"MYDIRPATH=C:\new folder\data.txt" 在幕后 - 请注意 整个 令牌现在是如何包含在 "...".

中的

可以说,这两种形式应该被msiexec认为是等价,但在无政府主义的世界里,一切都不可能了Windows 命令行参数解析。

这是一般情况下使用 Powershell 安装程序的最佳方式。

这是我自己的脚本中的一个示例:

start-process "c:\temp\SQLClient\sqlncli (x64).msi" -argumentlist "/qn IACCEPTSQLNCLILICENSETERMS=YES" -wait

使用Start-Process "Path\to\file\file.msi or .exe" -argumentlist (Parameters) "-qn or whatever" -wait.

现在 -wait 很重要,如果您有一个安装了大量程序的脚本,wait 命令,如管道到 Out-Null,将强制 Powershell 等待直到程序完成安装再继续前进。