问题
Please, observe:
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
Now if I run it in the command prompt:
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>
Now I am running it from 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>
From what I know the exit code is supposed to propagate correctly. So, what is the problem?
回答1:
First: ERRORLEVEL is not %ERRORLEVEL%.
Second, the errorlevel is not the same as a process exit code.
Try altering your cmd script as follows (note the addition of "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
回答2:
Note: This answer was substantially rewritten after new information came to light.
To complement jazzdelightsme's effective solution with some general guidance and background information:
When calling a batch file from outside
cmd.exe
, such as from PowerShell, invoke it ascmd /c call file.cmd
to ensure that it behaves as it would when called from insidecmd.exe
with respect to its exit code (error level), i.e. to ensure that the batch file's exit code also becomescmd.exe
's process exit code.Without using
call
, you'll only get the desired behavior if you ensure that all code paths exit in one of the following ways - and missing a code path is an easy mistake to make:exit
with no arguments, which correctly relays the most recently executed command's exit code.- Caveat:
exit
without/b
instantly exits thecmd.exe
instance as a whole, so it isn't suitable for batch files that may be run interactively or must be callable from other batch files and return control to those batch files.
- Caveat:
exit /b <code>
orexit <code>
, where<code>
represents the desired exit code, i.e. specifying an explicit exit code, as in jazzdelightsme's solution.- Caveat:
exit /b
without an explicit<code>
argument does not pass the most recently executed command's exit code through withoutcall
- see this answer.
- Caveat:
Additional background information, in the context of your code:
Bizarrely, with an outside invocation of a batch file without call
, statements such as if
, goto
, echo
, and endlocal
, and even REM
(but, curiously, not ::
) reset the exit code that cmd.exe
later reports to 0
- even though inside that cmd.exe
session %ERRORLEVEL%
is set as it usually is, meaning that such statements have no impact on the current %ERRORLEVEL%
value.
Therefore:
When your batch file is run from inside
cmd.exe
, the specific statements that follow the command that sets%ERRORLEVEL%
to1
(cmd /c dir aaa
), i.e. theif
,goto
,echo
andendlocal
statements, preserve this value, and%ERRORLEVEL%
ends up with value1
after exiting the batch file.When your batch file is run from outside
cmd.exe
, and isn't invoked withcmd /c call
, the same statements that follow the%ERRORLEVEL%
-setting command suddenly reset the exit code thatcmd.exe
itself later reports to0
, instead of preserving the post-batch-file-execution%ERRORLEVEL%
value.
来源:https://stackoverflow.com/questions/55289348/exit-code-from-a-batch-file-is-not-propagated-to-the-parent-powershell-script