批处理文件中的退出代码不会传播到父 powershell 脚本

mar*_*ark 1 powershell

请注意:

c:\temp\1.cmd

@echo off
setlocal

cmd /c dir aaa
IF %ERRORLEVEL% NEQ 0 GOTO fail
GOTO end
:fail
echo - Script failed

:end
endlocal
Run Code Online (Sandbox Code Playgroud)

现在,如果我在命令提示符下运行它:

C:\temp> cmd
Microsoft Windows [Version 10.0.16299.967]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\temp>c:\temp\1.cmd
 Volume in drive C has no label.
 Volume Serial Number is 4A5E-F223

 Directory of C:\temp

File Not Found
- Script failed

C:\temp>echo %errorlevel%
1

C:\temp>
Run Code Online (Sandbox Code Playgroud)

现在我从 Powershell 运行它:

C:\temp> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.16299.967
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.16299.967
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1


C:\temp> cmd /c C:\temp\1.cmd
 Volume in drive C has no label.
 Volume Serial Number is 4A5E-F223

 Directory of C:\temp

File Not Found
- Script failed
C:\temp> $LASTEXITCODE
0
C:\temp>
Run Code Online (Sandbox Code Playgroud)

据我所知,退出代码应该正确传播。那么,问题是什么?

mkl*_*nt0 5

注意:此答案在新信息曝光后被大幅重写。

用一些一般指导和背景信息补充jazzdelightsme 的有效解决方案

  • 当从外部 调用批处理文件时cmd.exe,例如从 PowerShell,调用它作为 -cmd /c file.cmd ... `& exit注意[1]`-escaping - 以确保它的行为与从内部调用时的退出代码(错误级别)一样,即以确保该批处理文件的退出代码可靠地变成进程退出代码& cmd.execmd.exe

    • 这篇文章详细解释了这个问题和这个 - 晦涩的 - 解决方法。
    • :比较简单的解决方法cmd /c call file.cmd ...的工作原理,但具有不必要的副作用时(的必要性双引号[2]用)参数^的字符传递:由于使用的call^字符(“插入记号”,严格来说抑扬口音U+005E)是总是加倍,所以说,argument "a ^ 2",被批处理文件视为"a ^^ 2"; 目前尚不清楚这种行为的目的是什么,但它似乎一直存在 -有关的解析器的详细信息,请参阅此答案cmd.exe
  • 如果没有`& exit解决办法,你只有当你确保获得所需的行为通过以下方式之一的代码路径退出-和缺少代码路径是一个容易犯的错误:

    • exit没有参数,这些参数正确转发了最近执行的命令的退出代码。

      • 警告exit /b立即cmd.exe作为一个整体退出实例,因此它不适合在可以交互运行或必须从其他批处理文件调用并将控制权返回给这些批处理文件的批处理文件中使用。
    • exit /b <code>exit <code>,其中<code>表示所需的退出代码,即指定显式退出代码,如 jazzdelightsme 的解决方案。

      • 警告:退出与一个批处理文件exit /b 没有明确的<code>说法没有通过通过最近执行的命令的退出代码没有cmd /c <batch-file> ... `& exit解决方法-看到这个答案

您的代码上下文中的其他背景信息

奇怪的是,与外界的批处理文件的调用,而不& exit(或call,报表等ifgotoecho,和endlocal,甚至REM(但是,奇怪的是,不是::复位退出代码cmd.exe后报告0-即使里面cmd.exe会话%ERRORLEVEL%设置,因为它通常是,这意味着此类语句对当前%ERRORLEVEL%值没有影响。

所以:

  • 当你的批处理文件运行从里面 cmd.exe,下面的命令的具体陈述,套%ERRORLEVEL%1cmd /c dir aaa),即ifgotoechoendlocal报表,保存该值,并%ERRORLEVEL%结束了值1退出批处理文件之后。

    • 奇怪的是,错误级别设置为仅涉及该批处理文件调用的声明,让像file.cmd || exit工作,因为||运营商不承认file.cmd呼叫已经有失败的。这就是变通方法使用&而不是的原因||
  • 当您的批处理文件从外部 运行cmd.exe,并且没有使用cmd /c <batch-file> ... `& exit(或cmd /c call <batch-file> ...)调用时,%ERRORLEVEL%-setting 命令cmd.exe后面的相同语句会突然重置其自身稍后报告的退出代码0,而不是保留批处理文件执行后%ERRORLEVEL%价值。


[1] 或者,您可以使用 form cmd /c "<batch-file> ... & exit",但这需要对"作为参数一部分的任何字符进行转义。如果没有使用 PowerShell 变量或表达式,单引号是一个选项,可以避免该问题:cmd /c '<batch-file> ... & exit'

[2]^字符。在不加引号的参数中,像往常一样,被解释为cmd.exe转义字符,因此被删除