批处理文件中的退出代码不会传播到父 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

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

C:temp> cmd
Microsoft Windows [Version 10.0.16299.967]
(c) 2017 Microsoft Corporation. All rights reserved.
C:temp>c:temp1.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>

现在我从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:temp1.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>

据我所知,退出代码应该正确传播。那么,问题出在哪里呢?

注意:在新信息曝光后,这个答案被大大重写了。

为了补充Jazzdelightsme的有效解决方案,并提供一些一般指导和背景信息

  • 当从cmd.exe外部调用批处理文件时,例如从 PowerShell 调用批处理文件,请将其作为cmd /c 'file.cmd ... & exit'调用,或者 - 如果需要字符串插值 - 作为
    cmd /c "file.cmd ... & exit",然后需要转义嵌入"作为`"[1]。这确保了它的行为与从内部调用时的行为一样cmd.exe关于其退出代码(错误级别),即确保批处理文件的退出代码可靠地成为cmd.exe的进程退出代码

    • 这篇文章详细解释了这个问题和这个 - 晦涩难懂 - 解决方法。
    • 注意:基于call-cmd /c call file.cmd ...- 的更简单的解决方法原则上有效,但是当传递(必须双引号[2])带有^字符的参数时会产生不必要的副作用: 由于使用了call^字符("插入符号",严格来说回旋重音U+005E)总是加倍,因此,参数"a ^ 2"被批处理文件视为"a ^^ 2";目前还不清楚这种行为的目的是什么,但它似乎一直存在 - 有关cmd.exe解析器的详细信息,请参阅此答案。
  • 如果没有& exit解决方法,则只有在确保所有代码路径以下列方式之一退出时,才会获得所需的行为 - 并且缺少代码路径很容易出错:

    • exit没有参数,可以正确中继最近执行的命令的退出代码。

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

      • 警告:在没有显式<code>参数的情况下退出带有exit /b的批处理文件不会传递最近执行的命令的退出代码,而无需cmd /c <batch-file> ... `& exit解决方法 - 请参阅此答案。

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

奇怪的是,在没有& exit(或call)的情况下外部调用批处理文件,诸如ifgotoechoendlocal之类的语句,甚至REM(但奇怪的是,不是::)重置cmd.exe稍后向0报告的退出代码- 即使在该cmd.exe会话%ERRORLEVEL%通常设置为这样,这意味着此类陈述对当前%ERRORLEVEL%值没有影响。

因此:

  • 当你的批处理文件从cmd.exe内部运行时,%ERRORLEVEL%设置为1(cmd /c dir aaa) 的命令后面的特定语句,即ifgotoechoendlocal语句,保留此值,%ERRORLEVEL%在退出批处理文件后以值1结束。

    • 奇怪的是,错误级别仅在涉及批处理文件调用的语句之后设置,因此类似于file.cmd || exit的操作不起作用,因为||运算符无法将file.cmd调用识别为失败。这就是解决方法使用&而不是||的原因。
  • 当你的批处理文件从外部运行cmd.exe并且没有cmd /c "<batch-file> ... & exit"(或cmd /c call <batch-file> ...)调用时,%ERRORLEVEL%-set 命令后面的相同语句会突然重置cmd.exe自身稍后报告给0的退出代码,而不是保留批处理文件执行后%ERRORLEVEL%值。


[1]在情境中,你可以逃脱cmd /c <batch-file> ... `& exit,即不将/c参数包含在单个整体字符串中,但如果批处理文件路径需要双引号并且至少存在一个双引号传递参数,则会中断

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

第一:ERRORLEVEL 不是 %ERRORLEVEL%。

其次,错误级别与进程退出代码不同。

尝试按如下方式更改 cmd 脚本(请注意添加"exit/b 1"):

@echo off
setlocal
cmd /c dir aaa
IF %ERRORLEVEL% NEQ 0 GOTO fail
GOTO end
:fail
echo - Script failed
exit /b 1
:end
endlocal

我不知道为什么,但我遇到了同样的问题。 $LastExitCode始终为 0,即使批处理文件以错误代码 1 退出也是如此。

我需要$LastExitCode,以便在出现错误时,我必须提示错误退出 ps1 脚本,所以我所做的是将批处理执行命令包装在 if 中,如下所示:

if( !(Start-Process -FilePath PATH/TO/BATCH -NoNewWindow -Wait -ErrorAction -Stop -ArgumentList arguments_if_needed) ){

Write-Error "There was an error, exiting"
exit
}

这对我有用。

最新更新