Batch file %errorlevel% malfunction with `choice` command - batch-file

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.

Related

How to exit a batch function?

So I wanted to do something like this:
#echo off
echo Before
call :test
echo After
pause
:test
echo I'm in test!
:end
echo I shouldn't be here...
Such that it would call test after saying Before and before saying After. However, it keeps just saying I shouldn't be here before saying After! Now, it's not necessarily a problem in this example, but when I have a whole bunch of functions, it can be quite an issue. Can anyone please help me? Thanks! :)
Batch has no concept of "sections", "functions", "procedures" or "paragraphs". A label is simply a reference point. Execution does not stop when a label is reached, it simply continues through, line by line, until it reaches end-of-file, a CALL , a GOTO or an EXIT. This is why you get the message you mention - and why the I'm in test! and the "rogue" message appear after your pause (which you wouldn't see if you are executing the batch by "click"ing it)
You need to goto :eof after completing the mainline, otherwise execution will continue through the subroutine code. :EOF is a predefined label understood by CMD to mean end of file. The colon is required.
You can use goto :eof to terminate a subroutine (reaching end-of-file terminates the routine and returns to the statement after the call) OR you can use exit or exit /b [n] as explained in the documentation (se exit /? from the prompt.
The downside of using exit is that it will terminate the cmd instance if executed when the routine hasn't been called - ie. it's part of the main routine - consequently, goto :eof is more popular.

I'm trying to make a batch file what is wrong with my code?

I'm trying to make a code where I can go to websites without going to my browser I have atop search function which I will change manually I'm fairly new to coding so any advice is helpful here is the code.
#echo off
echo Top searches
echo 1. Faceit
set /p name =
if %name% EQU "1" goto F
if %name% NEQ "1" then goto custom
:F
start "" https://www.faceit.com/en
:custom
echo What website would you like to go to?
set /p x =
start "" https://www.%x%
There's quite a bit going on in your code that is keeping it from working in any meaningful sort of way. Just at an initial glance, I see ten separate things that are either completely wrong or simply violate what could be considered "good programming practices" in batch.
1/2. Whitespace in variable names is significant
For some reason, Microsoft decided to allow whitespace in variable names, so %this is a valid variable name%. Seriously. As a result of this, both of your set /p statements are creating variables that you never use.
Instead of set /p name = and set /p x =, use set /p name= and set /p x=
3/4. Put quotes around set statements
This one is just good programming practice and is arguably not "wrong," but it's a good habit to form early.
Use quotes to avoid the user entering things like & or > and having those break the flow of the script. You can put the quotes to the around the prompt (like set /p variable="Enter text: "), but if you do that with a regular set statement, the quotes will become part of the value. To avoid this, put the first quote to the left of the variable name, like this: set /p "variable=Enter text: "
This also prevents any hidden spaces from getting tacked on at the end of the value by accident.
5. Then is not a keyword in batch
The then in your second if statement if going to give you a syntax error because it's not a valid keyword in batch. Just get rid of it.
if "%name%" NEQ "1" goto custom
6/7. Quotes in comparisons are significant
When you put quotes around one side of a comparison, you need to put quotes on the other side as well. This has the added effect of keeping characters like & and > from breaking the flow of the script.
if "%name%" EQU "1" goto F
if "%name%" NEQ "1" goto custom
8. A missing goto (or exit) will cause :custom to run immediately after :F
Batch scripts run from top to bottom unless acted upon by a goto, call, if, or some other flow control command. In this case, after start "" https://www.faceit.com/en is called, the very next non-whitespace line is :custom.
To avoid :custom from running, kill the script after the first start with exit /b or goto :eof - both of these will stop the script but keep the command prompt open if you ran the script from the command line instead of double-clicking it. Note that if you use goto :eof, you do not need to make a :eof label, since it's built into the command prompt.
9/10. Put colons in front of labels in goto commands
Again, not necessary, just good programming practice. You have to include the colons when you use call to run subroutines anyway, so you might as well be consistent everywhere.
Other notes
When the script is first run, all you see is
Top searches
1. Faceit
and that's it. Nothing to tell the user what to do or to indicate that they can enter another site by typing something other than 1. Unless you plan on being the only person to use the script, I'd recommend putting something somewhat more descriptive in that section.
If you're going to automatically tack on https://www. to the start of a custom URL, put that on the screen so that the user doesn't accidentally end up going to https://www.https://www.google.com or something.
You may want to look into the choice command for future versions of the script to replace the initial set /p command, depending on how many options you want to give the user.
Putting comments in your code wouldn't hurt.
Ultimately, it will look something like this
#echo off
echo Top searches
echo 1. Faceit
echo Enter anything else to go to a different site
set /p "name=Your selection: "
if "%name%" EQU "1" goto :F
if "%name%" NEQ "1" goto :custom
:F
start "" https://www.faceit.com/en
exit /b
:custom
echo What website would you like to go to?
set /p "x=https://www."
start "" https://www.%x%

goto different labels depending of value of variable that may be empty or undefined

I worked with 4DOS a lot decades ago, and bash more recently, but don't have experience with plain Windows batch. I'm trying to make something to conveniently kill the firefox.exe processes that sometimes misfire and never show Firefox but persist and eat my resources.
Trying to make a query-to-user that defaults to kill the processes.
The first problem is the "if %REPLy%==SOMETHING (goto SOMEWHERE)" statements.
The explicit ones work fine but I want to kill the firefoxes if none of them are true. I thought I'd just put the code after the "if...goto"s but that didn't work. So I tried an additional if based on the variable REPLy equaling nothing. That didn't work. So I thought maybe a variable equaling nothing ("") might not be the same as being undeclared and maybe the reply stuff was simply removing the variable rather than giving the value "" and added an if for that. That didn't work either. So I thought maybe I had to put the kill code under a label and send execution there with a goto like the I did in the statements that work, but that doesn't work either. If I enter SOMETHING other than the explicit variations of NO or no, full or truncated, it works, kinda. The taskkill command reports success but it still fails in reality. But I'll work on that bridge when I get there. The immediate problem is how to get NO entry (in other words, just hit the enter key) to goto the kill code just like a non-no string does. What am I doing wrong here?
#echo off
REM All this stuff with the path is because I can't reboot this system right now (long story) and I can not seem to make the amended path stick. So for now, I set it each time. I presume I just need to reboot to make the path setting I changed under computer properties, etc, stick.
echo "This is the path:"
path
PATH=%PATH%;C:\Program Files\GnuWin32\bin
echo "This is the path now:"
path
echo "All these path setting and testing commands and remarks can be cleaned up after I figure out if the new path becomes permanent after reboot."
REM Here ends the stuff I expect to delete after I can reboot.
REM Here begins the part I do not expect to change and that works fine.
tasklist | findstr /B firefox.exe | wc -l > kill_firefox.bat_var.tmp
set /p NUMBER_OF_PROCESSEs=<kill_firefox.bat_var.tmp
del kill_firefox.bat_var.tmp
IF NOT DEFINED NUMBER_OF_PROCESSEs (goto ERROR - NUMBER_OF_PROCESSEs not set)
if %NUMBER_OF_PROCESSEs%==0 (goto NO_PROCESSES)
REM Since the contrary conditions lead to gotos, if processing gets to here, there are 1 or more firefox.exe processes.
echo The number of firefox.exe processes running is:
echo .
echo %NUMBER_OF_PROCESSEs%
echo .
tasklist | findstr /B firefox.exe
set /p REPLy= "Kill these? Y/n"
echo "REPLy is %REPLy%"
pause
if %REPLy%==n (goto USER DECLINED TO KILL)
if %REPLy%==N (goto USER DECLINED TO KILL)
if %REPLy%==no (goto USER DECLINED TO KILL)
if %REPLy%==NO (goto USER DECLINED TO KILL)
REM Here is where the problems start. By my reasoning, I shouldn't need any if/then here, nor even a goto, just the code that is now in the part labeled KILL. The ifs and the goto and putting the code in the labeled section are the result of many attempts to get that code to run with various constructions.
if [%REPLy%] == [] goto KILL
IF NOT DEFINED REPLy (goto KILL)
goto KILL
:NO_PROCESSES
echo There are no firefox.exe processes running.
pause
exit
:ERROR - NUMBER_OF_PROCESSEs not set
echo Logic error - The variable is not defined. This script must be repaired.
pause
exit
:USER DECLINED TO KILL
echo User declined to kill processes.
pause
exit
:KILL
REM I am not sure if ANY of this is running because the pause command is not working and the terminal disappears to fast to see. What am I doing wrong here?
echo killing . . .
taskkill /IM firefox.exe
pause
exit
Added by edit:
OK, I musta confused my smart pills with my dumb pills. Here is how I fixed the part that I was stuck on:
I changed
set /p REPLy= "Kill these? Y/n"
to
set /p REPLy= "Kill these? Y/n" || set REPLy=Y
and that did the trick. I see why that works, but I don't quite see why the ways I tried before don't. Apparently Batch treats variables set (unset? cleared? nulled?) by "set /p somemessage" with just a plain enter key as a response in some way I don't understand. But anyway, I don't have to understand it, just accept it. The construction with the "||" above works. Anyway this was the part of the problem I asked about and it's solved. If I can't get the rest of it working I'll post again after cleaning this batch file up a bit.
use setx to set a permanent variable (see setx/?, the syntax is different from set).
set /p leaves the variable unchanged, if input is empty. So you can predefine a variable:
set "REPLy=Y"
set /p "REPLy=Kill these? Y/n"
echo %REPLy%
echo first letter of REPLy is %REPLy:~0,1%
but instead of set /p, I would use choice.
There is no "empty" variable. If it has no value, the variable is not defined.
if has a /i switch to ignore capitalization.
To get the number of processes I would use (no need for an external utility):
for /f %%i in ('tasklist ^| find /c "firefox.exe"') do set NUMBER_OF_PROCESSEs=%%i
and as SomethingDark already mentioned:
run the script from the command prompt instead of double clicking it (and use exit /b instead of exit)
don't use spaces in labels.

How Can I Make A Command Prompt Hang?

Yes, I know you are probably going to complain saying it's a bad thing to do, but I want to do it anyway!
I am creating a batch program and at the end, I need it to hang and not accept user input. I know one method is just creating an infinite loop of:
:pause
pause > nul
goto pause
but I don't think that's a great choice. Although I need it to hang, I need to to be able to be closed via the red 'X' close button at the top of the window.
Any ideas?
This works for me. It redirects < NUL into self to prevent Ctrl+C from breaking, and uses start /b /wait to suppress the "Terminate batch job (Y/N)?" prompts.
#echo off
setlocal
>NUL (echo(%* | findstr "\<hang\>" && waitfor redX)
rem // *** PUT YOUR MAIN SCRIPT HERE ***
echo End of line.
rem // ******* END MAIN SCRIPT *********
call :hang
goto :EOF
:hang
start /b /wait "" "%~f0" hang ^<NUL
On the initial launch of the script, the echo(%* | findstr "\<hang\>" >NUL line looks for a script argument of "hang". If found, the script executes the waitfor command.
Normally, waitfor can be broken with Ctrl+C. But since the usual behavior of Ctrl+C is defeated by start /b and <NUL, the hanging effect is achieved unless a user does Ctrl+Break or sends the answering waitfor signal.
The red X still works, though.

Why is only the first line of the condition executed?

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

Resources