I have a simple batch file in which I want to do things if an operation failed. In the condition, it seems only the first lines executes for some reason...
call "%local_path%\unins000.exe" /verysilent
IF ERRORLEVEL 1 (
echo ERROR: uninstallation failed
REM Installation failed, deletes the folder
rmdir /s /q "%local_path%"
set batcherrorlevel=1
)
IF %batcherrorlevel% neq 0 exit /b %batcherrorlevel%
If the uninstall fails, the echo works and displays, but my exit code at the end is 0. However, if I place the line "set batcherrorlevel=1" to be the first line in the condition, the exit code is 1 but the echo does not print.
EDIT: I never found the real cause of the issue, but it seems it solved iself... Bothers me a little, but as long as it works, I guess it's ok...
Sometimes batch file crashes when you put remarks inside a block statement
I think you may need to wrap it in percents, and add an == Operator.
I string-ify my comparisons, but that may just be me.
If "%ERRORLEVEL%"=="1"
Or you could look at if using a line continuation character is needed.
See http://blogs.msdn.com/b/oldnewthing/archive/2008/08/06/8835317.aspx
Related
I have a menu in a batch file that is powered by the choice command:
choice /C PNQTF /N /M "Choice: "
echo Errorlevel is %errorlevel%
if %errorlevel%==5 goto ViewFTP
if %errorlevel%==4 goto AutoTransferToggle
if %errorlevel%==3 goto TheEnd
if %errorlevel%==2 goto InternalLink
if %errorlevel%==1 goto NewSub
goto begin
19 times out of 20, there are no problems. The menu works fine. But a small percentage of the time, when I push Q to quit, the code seems to randomly skip all the way to :NewSub even though the Errorlevel is %errorlevel% statement echoes to the screen that Errorlevel is 3... and yet it still follows the instructions as if errorlevel were 1!
I thought of maybe using !errorlevel! just to be safe, but it doesn't matter because this set of choices is not in any block of code - it's not enclosed in any if statements or in any functions. I will say this issue never happens if I just run the batch file and immediately quit... there is something in the dark depths of the batch file that is somehow managing to linger and haunt the menu when the execution returns back to the beginning via goto begin.
I am missing half of the hair on my head because this issue has caused me to pull so much of it out. Chest hair might be next. The only thing that would seem more random than this
It turns out the solution is something I had already checked for before, but not careful enough. I traced my code and found a scenario where the user is returned to the main menu (goto begin) while in the middle of a called function - so the function was never concluded with a goto :eof statement.
Symptoms only occur when the user tries to quit from the main menu. When the user selects Q (which brings them to the very bottom of the file to a label called :TheEnd), it skips right back up to the line after the goto begin statement that interrupted the unconcluded function. The user doesn't see any output other than "after I hit Q, suddenly it's executing this other code."
So, as it turns out, errorlevel was working just fine; the returning of execution back to that line of code only made it appear that errorlevel was messing up, when in fact the batch file execution was treating the non-completed function like a bucket list item before finally dying.
I am trying to understand why the first line of my batch file fails to execute. My code is as follows:
if exist reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion" goto OptionOne
exit
:OptionOne
some code
goto:eof
It never goes to the OptionOne subroutine. Instead, it just exits.
I do have a solution to this problem written differently (so I don't want examples to make it work) but I want to understand why this one line fails to execute.
Is the syntax improper? Google says it is correct.
Poorly designed code? I know this registry key exists so this is not the case.
Is it something with the return value and its correct syntax, but needs to be further written out on the else statements?
The code you have doesn't work because if exist is used only to check if folders or files exist. Its syntax is:
if exist "C:\foldername\" (do something) else (optionally do something else)
for folders and:
if exist "C:\filename" (do something) else (optionally do something else)
for files.
My suggested solution (as mentioned in comments) is the following:
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion" >nul 2>&1
if %errorlevel% EQU 0 (goto :OptionOne) else (echo Registry key not found. & pause>nul & exit /b 1)
:OptionOne
some code
goto :eof
which checks if the command returned errorlevel different than equal to 1 or bigger (the registry key exists) or 1 or bigger (it doesn't exist).
REG QUERY only returns 0 for success or 1 for failure. Note that no results is still a successful query operation and will return 0.
Ref: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/reg-query
And as commentors noted, IF EXIST is only for files and folders, not for commands.
Just launch reg query and check %errorlevel%, as you can see here:
Prompt>reg query "HKCU\..." (put something which exists)
<some successful answers>
Prompt>echo %errorlevel%
0
Prompt>reg query "blabla"
ERROR: Invalid key name.
Type "REG QUERY /?" for usage.
Prompt>echo %errorlevel%
1
You can check %errorlevel% in your batch script.
I have a little program and when i run "if exist" it closes, can somebody tell me why? I have checked for syntax errors and none (i think), i will paste my code here and see what you can do.
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
set /p YesNoIn="[y/n]: "
if "%YesNoIn%"=="y" goto:yes
goto:menu
:yes
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done!
set /p yesnol="Would you like to launch MCEdit now? [y/n]: "
if "%yesnol%"="y" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
)
Right after you type the drive it closes, any help?
The issue that causes the script to exit early is a syntax error in the last if-statement of your else branch. If you run the script from the command line (not by double clicking on it) you'll get the error message:
="y" was unexpected at this time.
Indeed your last if-statement is:
if "%yesnol%"="y" (
but the cmd parser expects a double "=" sign (==) for comparisons, so you should have:
if "%yesnol%"=="y" (
The reason why it will see it even if it won't take the else branch is because an if-block (actually, all block of codes delimited by ( ... )) is parsed as if it was one single command written on one line (with each "subcommand" in your block separated with &&). As the parser will process that whole "line" in one go, it will detect whatever syntax error present inside the whole block.
Besides that syntax error, there are some little mistakes you've made in your script. The first one is that it is actually not good to declare labels inside a block of code. You've declared the :yes label inside the if-block in the else branch. A goto inside an if-block will "destroy" the if-context. You maybe won't notice in your code, but consider this example:
#echo off
set var=Let's brake the if statement
IF DEFINED var (
echo The variable var exists
IF "%var%"=="Let's brake the if statement" goto :expected
echo The variable var is not what I would expect though.
echo You have to change it to something else...
goto :outside
:expected
echo I'm happy with its value. It is at least something I would expect
echo This is the end of the if-branch
) ELSE (
echo Strange ... the variable var is not defined.
echo Indeed: var="%var%"
)
:outside
rem just to get outside
You would expect an output like
The variable var exists
I'm happy with its value. It is at least something I would expect
This is the end of the if-branch
but the output will be
The variable var exists
I'm happy with its value. It is at least something I would expect
This is the end of the if-branch
Strange ... the variable var is not defined.
Indeed: var="Let's brake the if statement"
The goto destroyed the if-context. As said earlier, the cmd parser will parse the whole if-block as one command. See it this way: you're asking the parser to abandon the command it was processing (the whole if block with the condition it just checked) and jump to somewhere else. That somewhere else is after the if condition, so it won't even evaluate that one again. So once you're inside a block (espacially if-blocks and for-loops) don't use goto to ignore some piece of code inside that block, use if-statements and put the code to ignore inside the if-block. A goto to jump outside a block of code is not a problem but from the moment the label is inside an if block for example, it can result in unexpected results.
The second mistake is about the variables YesNoIn and yesnol. As I said the whole if block is parsed in one go as one single command. It is not possible to give a variable a new value, and read that new value in the same command with the simple variable expansion %YesNoIn% or %yesnol%. A solution to this problem is delayed expansion (the link also has an example). As I was writing this answer, I saw #Josefz already posted an answer with delayed expansion so I won't repeat it here. But what I would recommend is that you take a look at the choice command. Using the choice command, you won't need delayed expansion. It just sets the errorlevel and there exists a way to check the errorlevel without worrying about delayed expansion: IF ERRORLEVEL n will check if the errorlevel is greater or equal to n. On top of that, choice automatically verifies if the user entered a correct value!
Your script with choice instead of set /p will look like this:
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
choice /c YN
REM Y ==> errorlevel = 1 ; N ==> errorlevel = 2
if ERRORLEVEL 2 goto:menu
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done!
choice /c YN /m "Would you like to launch MCEdit now? "
if NOT ERRORLEVEL 2 (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
)
PS: No Enter key needs to be pressed when choice is used.
EDIT: A third mistake is the choice of the variable APPDATA, it is already used by your windows OS in a user's context as you can see here.
You have simple typo in comparison operator in the latter if. Correct to if "%yesnol%"=="y"
I'm not sure whether changing APPDATA variable is a good idea.
Missing :menu label in your code.
Important: read and apply http://ss64.com/nt/delayedexpansion.html.
Never use :label nor :: label-like comment inside a command block enclosed in () parentheses
SETLOCAL EnableExtensions EnableDelayedExpansion
:::
:menu
:::
echo What drive would you like to launch MCEdit on?
set /p DRIVE="Drive: "
if exist "%DRIVE%:\MC\DATA\mcedit\mcedit.exe" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
) else (
echo You do not have MCEdit installed on this drive, would you like to install it?
set /p YesNoIn="[y/n]: "
if /I "!YesNoIn!"=="y" (
echo Choose the "mcedit.exe" file from a current installation on your pc.
pause
call "%DRIVE%\MC\DATA\FileViewer.bat"
xcopy "%OutCD%" "%DRIVE%:\MC\DATA\mcedit" /E
echo Done^!
set /p yesnol="Would you like to launch MCEdit now? [y/n]: "
if /I "!yesnol!"="y" (
set APPDATA=%DRIVE%:\MC\DATA
start %DRIVE%:\MC\DATA\mcedit\mcedit.exe
)
) else goto:menu
)
A frequent method to handling errors within Windows batch scripts is to use things like
if errorlevel 1 ... or if %errorlevel% neq 0 .... Often times one wants the error handling code to preserve the ERRORLEVEL.
I believe all external commands will always result in ERRORLEVEL being set to some value, so the error handling code must preserve the ERRORLEVEL in an environment variable prior to executing an external command.
But what about internal commands? The problem is, some internal commands clear the ERRORLEVEL to 0 when they succeed, and some do not. And I can't find any documentation specifying which commands do what.
So the question is, which internal commands clear the ERRORLEVEL to 0 upon success? This is not a general question about returned ERRORLEVEL codes, but strictly about success results.
There are posts like What is the easiest way to reset ERRORLEVEL to zero? and Windows batch files: .bat vs .cmd? that give partial answers. But I have never seen a comprehensive list.
Note: I've been curious about this for years. So I finally decided to run a bunch of experiments and come up with a definitive answer. I'm posting this Q&A to share what I have found.
This answer is based on experiments I ran under Windows 10. I doubt there are differences with earlier Windows versions that use cmd.exe, but it is possible.
Also note - This answer does not attempt to document the ERRORLEVEL result when an internal command encounters an error (except for a wee bit concerning DEL and ERASE)
Not only are there difference between commands, but a single command can behave differently depending on whether it was run from the command line, or within a batch script with a .bat extension, or from within a batch script with a .cmd extension.
The following set of commands never clear the ERRORLEVEL to 0 upon success, regardless of context, but instead preserve the prior ERRORLEVEL:
BREAK
CLS
ECHO
ENDLOCAL
FOR : Obviously, commands in the DO clause may set the ERRORLEVEL, but a successful FOR with at least one iteration does not set the ERRORLEVEL to 0 on its own.
GOTO
IF : Obviously, commands executed by IF may set the ERRORLEVEL, but a successful IF does not set ERRORLEVEL to 0 on its own.
KEYS
PAUSE
POPD
RD
REM
RMDIR
SHIFT
START
TITLE
The next set of commands always clear the ERRORLEVEL to 0 upon success, regardless of context:
CD
CHDIR
COLOR
COPY
DATE
DEL : Always clears ERRORLEVEL, even if the DEL fails (except when run without any file argument).
DIR
ERASE : Always clears ERRORLEVEL, even if ERASE fails. (except when run without any file argument).
MD
MKDIR
MKLINK
MOVE
PUSHD
REN
RENAME
SETLOCAL
TIME
TYPE
VER
VERIFY
VOL
Then there are these commands that do not clear ERRORLEVEL upon success if issued from the command line or within a script with a .bat extension, but do clear the ERRORLEVEL to 0 if issued from a script with a .cmd extension. See https://stackoverflow.com/a/148991/1012053 and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J for more info.
ASSOC
DPATH
FTYPE
PATH
PROMPT
SET
Lastly, there are these commands that do not fit neatly into any of the prior categories:
CALL : If a :routine or batch script is CALLed, then ERRORLEVEL is exclusively controlled by the CALLed script or :routine. But any other type of successful CALL to a command will always clear ERRORLEVEL to 0 if the CALLed command does not otherwise set it.
Example: call echo OK.
EXIT : If used without /B, then the cmd.exe session terminates and there is no more ERRORLEVEL, just the cmd.exe return code. Obviously EXIT /B 0 clears the ERRORLEVEL to 0, but EXIT /B without a value preserves the prior ERRORLEVEL.
I believe that accounts for all internal commands, unless there is an undocumented command that I missed.
Your description of CALL command is incomplete:
CALL : Clears ERRORLEVEL if the CALLed command does not otherwise set it.
Example: call echo OK.
Check this small example:
#echo off
call :setTwo
echo Set two: %errorlevel%
call :preserve
echo Preserve: %errorlevel%
call echo Reset
echo Reset: %errorlevel%
call :subNotExists 2> NUL
echo Sub not exist: %errorlevel%
goto :EOF
:setTwo
exit /B 2
:preserve
echo Preserve
exit /B
Output:
Set two: 2
Preserve
Preserve: 2
Reset
Reset: 0
Sub not exist: 1
CALL description should say something like this:
CALL : Clears ERRORLEVEL if the CALLed command does not otherwise set it. Example: call echo OK, but if the called command is a subroutine it preserves the prior ERRORLEVEL. If the called subroutine does not exist, it sets the ERRORLEVEL to 1.
Basically, let's say that I have a batch file that calls myapp1.exe and myapp1.exe exits with Exit Code 1. Can the batch file capture this information and either force the batch file to exit with that same exit code or perform some other logic?
#echo off
rem ...
set errorlevel=
MyApp1.exe
exit /b %errorlevel%
would be the explicit variant.
The accepted answer is correct, but if you are using call to call another batch script, and that second batch script is using SetLocal, you may need to use a parsing trick to accomplish this. If you are running into this, add the following code before your exit b:
ENDLOCAL&set myvariable=%myvariable%
Now the value of myvariable is made available to the calling context and you can see the value in the other script.
References:
https://stackoverflow.com/a/16167938/89590
http://www.borngeek.com/2008/05/22/exiting-batch-file-contexts/
You could try using errorlevels. Some more info here.
%ERRORLEVEL% stores the return value of last executed command
call program.exe
echo program.exe returns "%ERRORLEVEL%"
IF %ERRORLEVEL% NEQ 0 (
echo FAILED
)