【问题标题】:Call a batch file from PowerShell with proper exit code reporting while avoiding doubling of carets使用正确的退出代码报告从 PowerShell 调用批处理文件,同时避免插入符号加倍
【发布时间】:2021-04-06 20:13:51
【问题描述】:

我怀疑没有好的解决方案,但也许我忽略了一些东西:

我追求的是一种方法:

  • (a) 从 PowerShell 调用批处理文件的方式稳健地 反映其在 PowerShell 的自动 $LASTEXITCODE 变量中的隐式或显式退出代码。

    • 值得注意的是,调用以whoami -nosuch || exit /b 退出的批处理文件应该导致$LASTEXITCODE 反映whoami 的退出代码,即1。当您从 PowerShell 调用批处理文件(按名称或路径)时,情况不是:退出代码是 0(相比之下,在 cmd.exe 会话中 %ERRORLEVEL% 设置为1)。

    • 另外请注意,调用应该与 PowerShell 的输出流保持集成,所以我在寻找基于 System.Diagnostics.Process 的解决方案。

    • 此外,我了解或控制被调用的批处理文件 - 我正在寻找一个通用的解决方案。

  • (b) 传递给批处理文件的双引号参数不会以任何方式被更改,并且cmd.exe 的行为不会以任何方式被修改;特别是:

    • ^ 字符不应加倍(见下文)。
    • 使用/V:ON 启用延迟扩展不是一个选项。

我知道如何解决 (a) 的唯一方法是通过 cmd /c call 调用批处理文件。

不幸的是,这违反了要求 (b),因为在参数中使用 call 似乎总是将 ^ 字符加倍。 (相反,not 使用 call 不会可靠地报告退出代码。

有没有办法同时满足两个的要求?

请注意,PowerShell 只是这里的信使:问题在于cmd.exe,任何从cmd.exe 会话之外调用批处理文件的人都面临同样的问题。


示例(PowerShell 代码):

# Create a (temporary) batch file that echoes its arguments,
# provokes an error, and exits with `exit /b` *without an explicit argument*.
'@echo off & echo [%*] & whoami -nosuch 2>NUL || exit /b' | Set-Content test.cmd

# Invoke the batch file and report the exit code.
.\test.cmd "a ^ 2"; $LASTEXITCODE

输出应该是

["a ^ 2"]
1

然而,实际上退出代码是报告的:

["a ^ 2"]
0          # !! BROKEN

如果我改为使用cmd /c call .\test.cmd 调用,则退出代码是正确的,但^ 字符会加倍:

PS> cmd /c call .\test.cmd "a ^ 2"; $LASTEXITCODE
["a ^^ 2"]  # !! BROKEN
1           # OK

【问题讨论】:

  • 这对我有用:cmd '/c .\test.cmd "a ^ 2" & exit /b'。它会从您的测试批处理文件中返回正确的输出,如果我把乱码放在那里,我会在 $LASTEXITCODE (MSG_DIR_BAD_COMMAND_OR_FILE) 中得到 9009
  • 太好了,@beatcracker - 请将其发布为答案,我会接受。我试过|| exit /b,它应该工作;尝试& exit /b 我什至没有想到 - 它表明cmd.exe 本身并不知道批处理文件失败,但不知何故确实通过exit /b 传递了它的非零退出代码。 ????‍♂️
  • 你提到%ERRORLEVEL% is set to 1 给了我一个提示,这样的东西可能起作用;)。

标签: powershell batch-file cmd parameter-passing exit-code


【解决方案1】:

我不知道为什么会这样,但确实有效:

cmd /c '.\test.cmd "a ^ 2" & exit'
$LASTEXITCODE

输出:

["a ^ 2"] 
1

【讨论】:

    【解决方案2】:

    感谢beatcrackerhis answer 中找到有效的解决方法;让我添加一些背景信息和指导:

    • 首先,需要明确的是,不需要变通方法; cmd.exe 的行为显然是一个错误。

    • cmd /c '.\test.cmd "a ^ 2" || exit' - 即|| 而不是& - 也是人们期望的一种有效解决方法。只有&,它无条件 对命令进行排序,这一事实表明即使cmd.exe-在内部批处理文件的故障状态仍然未知作为同一语句的一部分 - 仅在之后 - 这似乎是该错误的另一种表现形式。

      • 为什么显式 exit 调用作为同一语句的一部分在批处理文件调用之后确实正确地中继批处理文件的(零或非零)退出代码是任何人的猜测,但它似乎有效。
    • 幸运的是,该解决方法对于解决包含显式 exit /b / exit 调用的批处理文件中的相关退出代码问题也很有效 - 请参阅 this answer

    语法注意事项

    • 在 PowerShell 中,传递 单个 命令字符串的替代方法是将 单个参数转义 & 字符作为 @ 987654338@(使用`,“反引号”,PowerShell 的转义字符)以防止 PowerShell 解释它(引用它为'&' 也可以) :

      cmd /c .\test.cmd "a ^ 2" `& exit
      
    • 不涉及 shell 的环境中,例如从任务计划程序启动时,&`-转义是不需要的(且不得使用)。

    不必将 整个 for-cmd.exe 命令用引号引起来,这样可以更轻松地传递 (a) 单独 需要双引号和 (b) 涉及对 PowerShell 变量和/或表达式的引用,因为后者需要使用 "..." 而不是 '...'

    # Passing *individual* arguments makes double-quoting easier.
    PS> cmd /c .\test.cmd "Version = $($PSVersionTable.PSVersion)" `& exit; $LASTEXITCODE
    ["Version = 7.2.0-preview.4"]
    1
    

    在这种情况下,使用 整个 for-cmd.exe 命令的引用会很尴尬,因为需要转义特定于参数的" 字符。:

    # Embedded double quotes must now be `-escaped.
    PS> cmd /c ".\test.cmd `"Version = $($PSVersionTable.PSVersion)`" & exit"
    ["Version = 7.2.0-preview.4"]
    1
    

    Native 模块(由我编写;安装from the PowerShell GalleryInstall-Module Native)带有函数ie,其中:

    • 自动应用上述解决方法

    • 通常可以弥补由于 PowerShell 将参数传递给外部程序时出现的问题(请参阅this answer)。

    # After having run Install-Module Native:
    # Use of function `ie` applies the workaround behind the scenes.
    PS> ie .\test.cmd "Version = $($PSVersionTable.PSVersion)"; $LASTEXITCODE
    ["Version = 7.2.0-preview.4"]
    1
    

    希望 ie 所做的功能将成为 PowerShell 本身的一部分,作为即将推出的(在 PowerShell v7.2 中)PSNativeCommandArgumentPassing 实验性功能的一部分,该功能旨在作为对损坏的选择性修复参数传递 - 见GitHub issue #15143

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      • 2019-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多