How to make windows batch file pause when double-clicked? - windows

I've written a batch file to automate some tasks. I can run it from a command window and it runs and displays results. If I double click it from explorer though, it runs and terminates immediately so I can't see the results.
Is there a way I can make batch file window stay open until I dismiss it if I started it by double-clicking the icon?
I don't want to have to pass a /nopause parameter or something when I call a batch file from the command line. I'd like a solution where I can use the batch file without having to do anything too special?
Thanks.
NOTE I don't want it to pause when running from the command line!! I may call this batch file from another batch file to carry out a load of operations. In that case I can't be sitting there to keep hitting enter.
Ideally it would be best if I can put some code in the batch file so it can work out where it was started from, and then pause or not as appropriate.

Use:
cmd /K myBatch.bat
as your shortcut.

My problem was similar - I wanted a pause at the very end if called from Windows Explorer, but no pause if in a command window. I came up with this.
At the top of every batch file, put this:
if "%parent%"=="" set parent=%~0
if "%console_mode%"=="" (set console_mode=1& for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set console_mode=0)
and then at end
if "%parent%"=="%~0" ( if "%console_mode%"=="0" pause )
It handles nested batch calls, where you only want to pause at the end of the original batch file, not in nested batch files. In a nested batch file, %parent% is already set to original caller so it won't equal the nested %~0. If you have bat1 which calls bat2, it leaves open the option of double clicking bat2 in Explorer - in that context bat2 will pause at end, whereas if bat1 calls bat2, then bat2 won't pause at the end (only bat1 will pause).
The & statement separator helps avoid visual clutter for something which is secondary to the main function. If you don't like the look of it, just put each statement on a new line.
This approach looks for /C as one of the params in the launch command (%cmdcmdline%). It assumes your batch files don't use the /C option. If you use /C yourself, then you need to instead check if %COMSPEC%appears within %cmdcmdline% (use FINDSTR). When Explorer launches a bat file, its %cmdcmdline% includes %COMSPEC% eg C:\Windows\System32\cmd.exe /C double_clicked_batch_file_name. In a command window, %cmdcmdline% just has cmd.exe in it (no path). I use CALL mybat rather than cmd.exe /C mybat, but you may have to work with existing batch files.

Here is a solution that should work well and take into consideration the possibility that a batch-file might call another batch-file ("Nested").
You could use Find, to look for "/c" which should not be present if the batch-file is run from a "Command Prompt":
echo %cmdcmdline% | find /i "/c"
But, you could do a more "robust" test by using Find to search for a longer string, or the batch-file name.
The "Find" command will not work properly if the search string has (") double-quotes within it. To work around that, you can use environment variable substitution to "adjust" the string so it plays nice with Find:
set newcmdcmdline=%cmdcmdline:"=-%
This will typically return:
if the batch-file is run from a "Command Prompt"
newcmdcmdline=-C:\Windows\system32\cmd.exe-
if the batch-file is run by clicking on the the batch-file
(or the batch-file shortcut) from "Windows Explorer"
newcmdcmdline=cmd /c --D:\Path\test.cmd- -
Then you can use "Find" to test like:
echo %newcmdcmdline% | find /i "cmd /c --"
or
echo %newcmdcmdline% | find /i "cmd /c --%~dpf0%-"
Next, you need to decide if you want a "Nested" batch-file to behave as if you executed it in the same way as the calling batch-file, or if you want nested batch-files to always behave as if they were executed from a "Command Prompt".
Consider that if you are running in a nested batch-file, then testing for this:
echo %newcmdcmdline% | find /i "cmd /c --%~dpf0%-"
will always fail (no match) because %newcmdcmdline% contains the name of the outermost batch-file, not the nested batch-file.
So the first solution will behave the same for the calling batch-file, and all nested batch-files. Also perfect if you don't call any batch-files:
In all batch-files (calling and nested) that you care to make this test, add these lines, typically near the top of the batch-files (you may exclude the echo-statements if you wish):
if not defined withincmd call :testshell
if %withincmd% EQU 0 echo This batch-file: %~dpf0 was executed directly (from Windows Explorer, ...).
if %withincmd% EQU 1 echo This batch-file: %~dpf0 was executed from within a Command Prompt
rem if %withincmd% EQU 0 pause
Then, somewhere within each batch-file, add the testshell sub-function:
goto :EOF
:testshell
rem "Nested" batch-files won't see this because withincmd is already defined
if not defined newcmdcmdline set newcmdcmdline=%cmdcmdline:"=-%
set withincmd=1
echo %newcmdcmdline% | find /i "cmd /c --%~dpf0%-"
if %errorlevel% EQU 0 set withincmd=0
goto :EOF
You only make the conditional call to "testshell" one time, at the top of the outermost batch-file.
In some situations, you may want to have only the "outermost" batch-file behave differently if it is executed from a "Command Prompt" versus if it is run by clicking on the the batch-file (or the batch-file shortcut) from "Windows Explorer". So, batch-files called from the "outermost" batch-file will always behave the same regardless of how they are run.
For this to work, you have a few choices.
1) Save the value of "withincmd" before you call another batch-file, and restore the previous value of "withincmd" after the called batch-file returns. This is a little involved for most cases.
2) Use a "globally-unique" variable name for "withincmd" in each batch-file.
3) Execute the "Find" command each time you need to know how the current batch-file was run.
4) Increment a variable on entry to a batch-file and decrement it on batch-file exit, then only test how batch-file was run if count-variable=1
Method 3 is the easiest, but has the downside that if the outermost batch-file is called from itself (as in recursion) or another batch-file, the test variable (withincmd) will not be properly set.
Here's how to do it using method 3:
In all batch-files (calling and nested) that you care to make this test, add these lines, typically near the top of the batch-files (you may exclude the echo-statements if you wish):
call :testshell
if %withincmd% EQU 0 echo This batch-file: %~dpf0 was executed directly (from Windows Explorer, ...).
if %withincmd% EQU 1 echo This batch-file: %~dpf0 was executed from (or Nested) within a Command Prompt
rem if %withincmd% EQU 0 pause
Then, somewhere within each batch-file, add the testshell sub-function:
goto :EOF
:testshell
if not defined newcmdcmdline set newcmdcmdline=%cmdcmdline:"=-%
set withincmd=1
echo %newcmdcmdline% | find /i "cmd /c --%~dpf0%-"
if %errorlevel% EQU 0 set withincmd=0
goto :EOF
In this case, you have to call "testshell" once, at the top of the EACH batch-file, then again after you have returned from calling another batch-file (or call "testshell" each time you need to know how the current batch-file was run).
Here's how to do it using method 4:
In all batch-files (calling and nested) that you care to make this test, add these lines, typically near the top of the batch-files (you may exclude the echo-statements if you wish):
if not defined nestinglevel set nestinglevel=0
set /A nestinglevel=nestinglevel+1
call :testshell
if %withincmd% EQU 0 echo This batch-file: %~dpf0 was executed directly (from Windows Explorer, ...).
if %withincmd% EQU 1 echo This batch-file: %~dpf0 was executed from (or Nested) within a Command Prompt
rem if %withincmd% EQU 0 pause
Then, somewhere within each batch-file, add the testshell sub-function:
goto :EOF
:testshell
if not defined newcmdcmdline set newcmdcmdline=%cmdcmdline:"=-%
set withincmd=1
if %nestinglevel% GEQ 2 goto :EOF
echo %newcmdcmdline% | find /i "cmd /c --%~dpf0%-"
if %errorlevel% EQU 0 set withincmd=0
goto :EOF
Also, remember to decrement the variable when you exit one batch-file to return to the calling batch-file:
set /A nestinglevel=nestinglevel-1
In this case, you have to call "testshell" once, at the top of the EACH batch-file, then again after you have returned from calling another batch-file (or call "testshell" each time you need to know how the current batch-file was run).
In all cases, test %withincmd% to determine how the current batch-file was run, like this:
if %withincmd% EQU 0 pause
if %withincmd% EQU 1 goto :EOF

at the end of file print
pause
it will wait for anykey input

Add this at the end of your batch:
echo %CMDCMDLINE% | findstr /C:"/c">nul && pause
This will pause if run from Windows Explorer and do nothing if run from command line.
Explanation:
CMDCMDLINE contains "/c" when run from Windows Explorer.
echo %CMDCMDLINE% | will pipe contents of the CMDCMDLINE into findstr
findstr /C:"/c" checks if CMDCMDLINE contains "/c"
">nul" will discard findstr console output
&& pause will run only if findstr finds something

you can just add params to the batch call and handle conditionally pause statement in your batch. So when started from command line or dblclick the batch can pause, when called from others batches with a /nopause param don't pause.

use "pause" in the batch file at the end, and it will wait for the user input
HTH

Would the pause command work?
Microsoft Documentation on pause

Related

%errorlevel% returning 0 in loop command

Ok, I've installed Dropbox but it didn't corresponded to what I was looking for so I uninstalled it with Revo Pro.
But, when i open the taskmanager there are still processes related to it running in my computer so I decided to make a batch to look out and delete all files that are related to it.
#echo off
cd c:\
:a
set /p a=Phrase that might be realted to it
for /r %%d IN (*.*) DO (
(
findstr /i /m /c:%a% "%%d"
if "%errorlevel%"=="0" del "%%d"
echo %errorlevel%
)
)
pause
The problem is: when I run findstr using loop even when there is no match for my variable "%a%" in an analized file %errorlevel% returns as 0. But when I use findstr alone and there isn't a match %ERRORLEVEL% returns as 1 and 0 for a match.
If I use it, I'll delete all my PC files haha. What's wrong with the code?
Within a parenthesised series of statements, any %var% is replaced by the value of that variable at the time the verb controlling that statement-sequence (or block) is encountered.
Here, the block is the entire sequence of statements controlled by the for. %errorlevel% is replaced by the status of errorlevel at the time the for is encountered, so probably 0.
If you use
findstr /i /m /c:%a% "%%d"
if not errorlevel 1 del "%%d"
echo %errorlevel%
then the run-time value of errorlevel is used (ie. as it changes through the operation of the loop) and the command means "if errorlevel is not (1 or greater than 1) do this..."
The findstr will set errorlevel to 0 on found, 1 on not found and 2 for file not found(IIRC) so NOT (1 or greater than 1) selects 0 only. Note that in certain esoteric circumstances, errorlevel may become negative, but after a findstr I believe 0..2 is the allowed range.
Not sure what's wrong with the code, but you can probably skip it using the && operand.
findstr /i /m /c:%a% "%%d" && del "%%d" echo %errorlevel%
Thanks to Stephan for correcting the example.
Whenever Windows command interpreter encounters ( being interpreted as begin of a command block, it parses the entire command block up to matching ) marking end of the command block and replaces all %variable% by current value of the variable.
This means in this case that before command FOR is the first time executed, everything from ( after DO up to last ) is processed already with replacing all %variable% references by current value of the appropriate variable. Then the already preprocessed block is executed one (on command IF) or more times (on command FOR).
This behavior can be seen by debugging the batch file. For debugging a batch file first #echo off must be removed or commented out with command REM or changed to #echo on. Then a command prompt window must be opened and the batch file is executed from within this command prompt window by typing its name with full path enclosed in double quotes if path or name contains a space character. The Windows command interpreter shows now all command lines and command blocks after preprocessing before executing and of course the standard messages and the error messages output by the commands or by Windows command interpreter itself in case of a syntax error in batch file.
Opening a command prompt window means running cmd.exe with option /K to Keep window open after execution of a command or a batch script. Double clicking on a batch file starts also cmd.exe for processing the batch file, but with parameter /C to Close the window automatically after batch processing terminated independent on cause - successful finished or an error occurred.
The command prompt window opened before running the batch file remains open after batch processing finished successfully or with an error except the batch file contains command EXIT without parameter /B. So experts in batch code writing test batch files always by running them from within a command prompt window instead of double clicking on them.
Delayed variable expansion is needed for variables set or modified and referenced within same command block as explained by help of command SET output on running in a command prompt window set /?.
#echo off
setlocal EnableDelayedExpansion
cd /D C:\
:a
set /P "a=Phrase that might be realted to it: "
for /r %%d in (*) do (
%SystemRoot%\System32\findstr.exe /i /m /c:"%a%" "%%d"
if "!errorlevel!" == "0" del "%%d" >nul
)
endlocal
But for checking the exit code of a previous command there is also if errorlevel syntax as explained by Microsoft in support article Testing for a Specific Error Level in Batch Files.
#echo off
setlocal EnableDelayedExpansion
cd /D C:\
:a
set /P "a=Phrase that might be realted to it: "
for /r %%d in (*) do (
%SystemRoot%\System32\findstr.exe /i /m /c:"%a%" "%%d" >nul
if not errorlevel 1 del "%%d" >nul
)
endlocal
if errorlevel X tests if exit code of previous command or application when it modifies the errorlevel variable at all is greater or equal X. By using if not errorlevel X the check is if last exit code is lower than X which is here a test if exit code is 0.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
cd /?
del /?
echo /?
for /?
if /?
set /?
And see also
Microsoft's command-line reference
SS64.com - A-Z index of the Windows CMD command line
Microsoft article about Using command redirection operators
Answer on question Single line with multiple commands using Windows batch file
How to set environment variables with spaces?

How do I detect if script was CALL'ed or invoked directly, in Windows CMD.EXE shell?

I need to distinguish these two situations inside script.cmd:
C:\> call script.cmd
C:\> script.cmd
How can I determine if my script.cmd was invoked directly, or invoked in the context of using a CALL?
If it matters, this is on Windows 7.
#echo off
set invoked=0
rem ---magic goes here---
if %invoked%==0 echo Script invoked directly.
if %invoked%==1 echo Script invoked by a CALL.
Anyone know the "magic goes here" which would detect having been CALL'ed and set invoked=1?
At this moment, I see no way to detect it, but as a workaround you can always force the use of the sentinel.
#echo off
setlocal enableextensions
rem If "flag" is not present, use CALL command
if not "%~1"=="_flag_" goto :useCall
rem Discard "flag"
shift /1
rem Here the main code
set /a "randomExitCode=%random% %% 2"
echo [%~1] exit with code %randomExitCode%
exit /b %randomExitCode%
goto :eof
rem Retrieve a correct full reference to the current batch file
:getBatchReference returnVar
set "%~1=%~f0" & goto :eof
rem Execute
:useCall
setlocal enableextensions disabledelayedexpansion
call :getBatchReference _f0
endlocal & call "%_f0%" _flag_ %*
This will allow you to use the indicated syntax
script.cmd first && script.cmd second && script.cmd third
The posted code ends the script with a random exit code for testing. Execution will continue when the exit code is 0
NOTE: For it to work, at least in XP, it seems the call to the batch file MUST be the last code in the batch file
Check if the script's path is in the CMDCMDLINE variable. If not, then it was probably called.
In this example I use %CMDCMDLINE:"=/% to turn the quotes into forward-slashes (the FIND command can't search for quotes) and I echo it with <NUL SET/P="" so that certain characters in the file path (like ampersands) don't break the script.
<NUL SET/P="%CMDCMDLINE:"=/%" | FIND "/%~0/">NUL || (
REM Commands to perform if script was called
GOTO:EOF
)
::AND/OR
<NUL SET/P="%CMDCMDLINE:"=/%" | FIND "/%~0/">NUL && (
REM Commands to perform if script was NOT called
GOTO:EOF
)

Detect if bat file is running via double click or from cmd window

I have a bat file that does a bunch of things and closes the cmd window which is fine when user double clicks the bat file from explorer. But if I run the bat file from a already open cmd window as in cmd>c:\myfile.bat then I do not want the bat file to close the cmd window (END) since I need to do other things. I need bat dos command code that will do something like
if (initiated_from_explorer) then
else
endif
Is this possible ? thanks
mousio's solution is nice but I did not manage to make it work in an "IF" statement because of the double quotes in the value of %cmdcmdline% (with or without double quotes around %cmdcmdline%).
In constrast, the solution using %0 works fine. I used the following block statement and it works like a charm:
IF %0 == "%~0" pause
The following solution, which expands %~0 to a fully qualified path, might also work if the previous does not (cf. Alex Essilfie's comment):
IF %0 EQU "%~dpnx0" PAUSE
However, note that this solution with %~dpnx0 fails when
the .bat file is located somewhere in the %USERPROFILE% directory, and
your %USERNAME% contains one or more uppercase characters
because... wait for it... the d in %~dpnx0 forces your %USERPROFILE% username to lowercase, while plain %0 does not. So they're never equal if your username contains an uppercase character. ¯\_(ツ)_/¯
[Edit 18 June 2021 - thanks to JasonXA]
You can solve this lowercase issue with case-insensitive comparison (magic /I):
IF /I %0 EQU "%~dpnx0" PAUSE
This might be the best solution of all!
%cmdcmdline% gives the exact command line used to start the current Cmd.exe.
When launched from a command console, this var is "%SystemRoot%\system32\cmd.exe".
When launched from explorer this var is cmd /c ""{full_path_to_the_bat_file}" ";
this implicates that you might also check the %0 variable in your bat file, for in this case it is always the full path to the bat file, and always enclosed in double quotes.
Personally, I would go for the %cmdcmdline% approach (not %O), but be aware that both start commands can be overridden in the registry…
A consolidated answer, derived from much of the information found on this page:
:pauseIfDoubleClicked
setlocal enabledelayedexpansion
set testl=%cmdcmdline:"=%
set testr=!testl:%~nx0=!
if not "%testl%" == "%testr%" pause
The variable "testl" gets the full line of the cmd processor call (as per mousio), stripping out all of the pesky double quotes.
The variable "testr" takes "testl" and further strips outs the name of the current batch file name if present (which it will be if the batch file was invoked with a double-click).
The if statement sees if "testl" and "testr" are different. If yes, batch was double-clicked, so pause; if no, batch was typed in on command line, go on.
Naturally, if you want to do something else if you detect a double-click, you can change the pause.
Thanks everyone.
Less code, more robust:
Build upon the other answers, I find the most robust approach to:
Replace quotes with x to enable text comparison without breaking the IF statement.
Recreate the expected cmdcmdline exactly (well, with '"' replaced by x).
Test for case-insensitive equality.
The result is:
set "dclickcmdx=%comspec% /c xx%~0x x"
set "actualcmdx=%cmdcmdline:"=x%"
set isdoubleclicked=0
if /I "%dclickcmdx%" EQU "%actualcmdx%" (
set isdoubleclicked=1
)
This adds more robustness against general cmd /c calls, since Explorer adds an awkward extra space before the last quote/x (in our favor). If cmdcmdline isn't found, it correctly renders isdoubleclicked=0.
Addendum:
Similarly to the above method, the following one-liner will pause a script if it was double-clicked from explorer. I add it to the end of my scripts to keep the command-line window open:
(Edit 2022-01-12, fixed quote mismatching from this discussion)
if /i "%comspec% /c ``%~0` `" equ "%cmdcmdline:"=`%" pause
if /i "%comspec% /c %~0 " equ "%cmdcmdline:"=%" pause
Use exit /b 0, not exit
The former will exit all the way if launched from Windows Explorer, but return to the console if launched from the command line.
You can add a command line parameter when running from a CMD window that won't exist when the file is double-clicked. If there is no parameter, close the window. If there is, don't close it. You can test the parameter using %1
It's not only possible, but your desired behavior is the normal behavior of batch file execution, unless you do something 'special':
when executing a batch file by double-clicking it in Explorer, the cmd window will close when it's done;
when the batch file is executed from the command line, it simply returns to the command line prompt when complete - the window is not closed;
So I think the question that needs to be answered is what are you doing in the batch file that causes the command window to close when you execute it by the command line?
Like #anishsane I too wanted a pause statement if launched from explorer, but not when launched from a command window.
Here's what worked for me, based upon #mousio's answer above:
#SET cmdcmdline|FINDSTR /b "cmdcmdline="|FINDSTR /i pushd >nul
#IF ERRORLEVEL 1 (
#echo.
#echo Press ENTER when done
#pause > nul
)
(Nothing original here, just providing a working example)
Paste this at the beginning of your BAT or CMD script and maybe change what happens in the 'if' clause:
:: To leave command window open if script run from Windows explorer.
#setlocal
#set x=%cmdcmdline:"=%
#set x=%x: =%
#set y=%x:cmd/c=%
#if "%x%" neq "%y%" cmd /k %0 %* && exit || exit
#endlocal
What this does, is if the user either double-clicks or calls this script using "cmd /c" it will re-launch with "cmd /k" which will leave the session open after the command finishes. This allows the user to EXIT or maybe do something else.
The reason for doing it this way rather than the other ways explained in this answer is because I've found situations that still even with using the quotes or other symbols, the IF statement would barf with certain situations of the QUOTES and the /c and with spaces. So the logic first removes all QUOTES and then removes all spaces.. because SOMETIMES there is an extra space after removing the quotes.
set x=%cmdcmdline:"=% <-- removes all quotes
set x=%x: =% <-- removes all spaces
set y=%x:cmd/c=% <-- removes cmd/c from the string saving it to y
The point of the && exit || exit is so that if the ERRORLEVEL before exiting is 0 (success) it then stops running, but also if it is non 0 (some failure) it also stops running.
But you can replace this part:
cmd /k %0 %* && exit || exit
with something like
set CALLED_WITH_CMD_C=YES
and then make up your own differences in the rest of your script. You would have to then move or remove the endlocal.
The '#' symbol at front just prevents the echo, which you can have if you want to test.
Do not use echo on or echo off as it changes the setting and affects all subsequent scripts that call yours.
#dlchambers was close but set didn't work since cmdcmdline isn't a defined environment variable in some cases, but this version based on his works great for me:
echo %cmdcmdline% | findstr /i pushd >nul
if errorlevel 1 pause
after reading through the suggestions, this is what I went with:
set __cmdcmdline=%cmdcmdline%
set __cmdcmdline=%__cmdcmdline:"=%
set __cmdcmdline=%__cmdcmdline: =%
set __cmdcmdline=%__cmdcmdline:~0,5%
if "%__cmdcmdline%"=="cmd/c" set CMD_INITIATED_FROM_EXPLORER=1
set __cmdcmdline=
which conditionally sets the variable: CMD_INITIATED_FROM_EXPLORER
..and can subsequently be used as needed:
if defined CMD_INITIATED_FROM_EXPLORER (
echo.
pause
)
..but the issue regarding Powershell that #Ruben Bartelink mentions isn't solved:
running ./batch.cmd from Powershell uses cmd /c under the hood
You also can check for SESSIONNAME environment variable.
As you see here that variable typically isn't set in Explorer window. When invoking from cmd it SESSIONNAME is set to Console. I can confirm this for Windows 10.
Unfortunately behaviour seems to be changeable: https://support.microsoft.com/de-de/help/2509192/clientname-and-sessionname-enviroment-variable-may-be-missing
(Partly) Contrary and in addition to the accepted answer AToW (re %cmdcmdline%) and the top answer AToW (re if /i %0 equ "%~dpnx0") in Win10 it is:
in CMD:
in a *.cmd (here _pauseIfRunFromGUI.cmd):
"C:\Windows\System32\cmd.exe"
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
.cmd is present if entered on the cmd line, which happens if you complete with Tab.
in a *.cmd (_pauseIfRunFromGUI.cmd) that's called by a *.cmd:
"C:\Windows\System32\cmd.exe"
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
Same as above.
.cmd is present if called via call _pauseIfRunFromGUI.cmd.
In any way the comparison evaluates to false which is intended.
from GUI:
(Explorer and link on Desktop)
in a *.cmd (here _pauseIfRunFromGUI.cmd) that's launched from the GUI:
C:\WINDOWS\system32\cmd.exe /c ""C:\Users\Geri\_pauseIfRunFromGUI.cmd" "
if /I "C:\Users\Geri\_pauseIfRunFromGUI.cmd" EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
This one is different to the accepted answer AToW which says just cmd /c ""..." "_!
The comparison evaluates to true which is intended.
in a *.cmd (_pauseIfRunFromGUI.cmd) that's called by a *.cmd (here calling.cmd) that's launched from the GUI:
C:\WINDOWS\system32\cmd.exe /c ""C:\Users\Geri\calling.cmd" "
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
Different to above, since calling .cmd is in cmdcmdline, of course, not the one in which it is evaluated (_pauseIfRunFromGUI.cmd).
.cmd is present if called via call _pauseIfRunFromGUI.cmd within calling.cmd.
The comparison evaluates to false which is not intended!
If the comparison is changed to:
if /i "%cmdcmdline:~0,31%"=="C:\WINDOWS\system32\cmd.exe /c " echo: & pause
everything works as expected.

Is there a way in a batch script to keep the console open only if invoked from Windows Manager?

I have a DOS batch script that invokes a java application which interacts with the user through the console UI. For the sake of argument, let's call it runapp.bat and its contents be
java com.example.myApp
If the batch script is invoked in a console, everything works fine. However, if the script is invoked from the Window Manager, the newly opened console closes as soon as the application finishes executing. What I want is for the console to stay open in all cases.
I know of the following tricks:
add a pause command at the end of the script. This is a bit ugly in case runapp.bat is invoked from the command line.
create a new shell using cmd /K java com.example.myApp This is the best solution I found so far, but leaves an extra shell environment when invoked from the command line, so that calling exit doesn't actually close the shell.
Is there a better way?
See this question: Detecting how a batch file was executed
This script will not pause if run from the command console, but will if double-clicked in Explorer:
#echo off
setlocal enableextensions
set SCRIPT=%0
set DQUOTE="
:: Detect how script was launched
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
:: Run your app
java com.example.myApp
:EXIT
if defined PAUSE_ON_CLOSE pause
I prefer using %cmdcmdline% as posted in the comment to Patrick's answer to the other question (which I didn't find although looked). That way, even if someone decides to use quotes to call the batch script, it won't trigger the false positive.
My final solution:
#echo off
java com.example.myApp %1 %2
REM "%SystemRoot%\system32.cmd.exe" when from console
REM cmd /c ""[d:\path\script.bat]" " when from windows explorer
#echo %cmdcmdline% | findstr /l "\"\"" >NUL
if %ERRORLEVEL% EQU 0 pause
cmd /K java com.example.myApp & pause & exit
will do the job. The & will execute the command one after another. If you use && you can break if one fails.
Include this line in a batch file and double click on the batch file in explorer:
cmd /k "script commands within these quotes seperated by &&"
For example
cmd /k "cd ../.. && dir && cd some_directory"
The full complement of options to cmd can be found here
I frequently use alternate shells (primarily TCC/LE from jpsoft.com) and subshells. I've found that this code works for a wider, more general case (and it doesn't require FINDSTR):
#echo off & setlocal
if "%CMDEXTVERSION%"=="" ( echo REQUIRES command extensions & exit /b 1 ) &:: REQUIRES command extensions for %cmdcmdline% and %~$PATH:1 syntax
call :_is_similar_command _FROM_CONSOLE "%COMSPEC%" %cmdcmdline%
if "%_PAUSE_NEEDED%"=="0" ( goto :_START )
if "%_PAUSE_NEEDED%"=="1" ( goto :_START )
set _PAUSE_NEEDED=0
if %_FROM_CONSOLE% equ 0 ( set _PAUSE_NEEDED=1 )
goto :_START
::
:_is_similar_command VARNAME FILENAME1 FILENAME2
:: NOTE: not _is_SAME_command; that would entail parsing PATHEXT and concatenating each EXT for any argument with a NULL extension
setlocal
set _RETVAL=0
:: more than 3 ARGS implies %cmdcmdline% has multiple parts (therefore, NOT direct console execution)
if NOT [%4]==[] ( goto :_is_similar_command_RETURN )
:: deal with NULL extensions (if both NULL, leave alone; otherwise, use the non-NULL extension for both)
set _EXT_2=%~x2
set _EXT_3=%~x3
if NOT "%_EXT_2%"=="%_EXT_3%" if "%_EXT_2%"=="" (
call :_is_similar_command _RETVAL "%~2%_EXT_3%" "%~3"
goto :_is_similar_command_RETURN
)
if NOT "%_EXT_2%"=="%_EXT_3%" if "%_EXT_3%"=="" (
call :_is_similar_command _RETVAL "%~2" "%~3%_EXT_2%"
goto :_is_similar_command_RETURN
)
::if /i "%~f2"=="%~f3" ( set _RETVAL=1 ) &:: FAILS for shells executed with non-fully qualified paths (eg, subshells called with 'cmd.exe' or 'tcc')
if /i "%~$PATH:2"=="%~$PATH:3" ( set _RETVAL=1 )
:_is_similar_command_RETURN
endlocal & set "%~1=%_RETVAL%"
goto :EOF
::
:_START
if %_FROM_CONSOLE% EQU 1 (
echo EXEC directly from command line
) else (
echo EXEC indirectly [from explorer, dopus, perl system call, cmd /c COMMAND, subshell with switches/ARGS, ...]
)
if %_PAUSE_NEEDED% EQU 1 ( pause )
Initially, I had used if /i "%~f2"=="%~f3" in the _is_similar_command subroutine. The change to if /i "%~$PATH:2"=="%~$PATH:3" and the additional code checking for NULL extensions allows the code to work for shells/subshells opened with non-fully qualified paths (eg, subshells called with just 'cmd.exe' or 'tcc').
For arguments without extensions, this code does not parse and use the extensions from %PATHEXT%. It essentially ignores the hierarchy of extensions that CMD.exe uses when searching for a command without extension (first attempting FOO.com, then FOO.exe, then FOO.bat, etc.). So, _is_similar_command checks for similarity, not equivalence, between the two arguments as shell commands. This could be a source of confusion/error, but will, in all likelyhood, never arise as a problem in practice for this application.
Edit: Initial code was an old version. The code is now updated to the most recent version which has: (1) a swapped %COMSPEC% and %cmdcmdline% in the initial call, (2) added a check for multiple %cmdcmdline% arguments, (3) echoed messages are more specific about what is detected, and (4) a new variable %_PAUSE_NEEDED% was added.
It should be noted that %_FROM_CONSOLE% is set based specifically on whether the batch file was excecuted directly from the console command line or indirectly through explorer or some other means. These "other means" can include a perl system() call or by executing a command such as cmd /c COMMAND.
The variable %_PAUSE_NEEDED% was added so that processes (such as perl) which execute the batch file indirectly can bypass pauses within the batch file. This would be important in cases in which output is not piped to the visible console (eg, perl -e "$o = qx{COMMAND}"). If a pause occurs in such a case, the "Press any key to continue . . ." pause prompt would never be displayed to the user and the process will hang waiting for unprompted user input. In instances where user interaction is either not possible or not allowed, the %_PAUSE_NEEDED% variable can be preset to "0" or "1" (false or true respectively). %_FROM_CONSOLE% is still set correctly by the code, but the value of %_PAUSE_NEEDED% is not subsequently set based upon %_FROM_CONSOLE%. It is just passed through.
And also note that the code will incorrectly detect execution as indirect (%_FROM_CONSOLE%=0) within a subshell if that subshell is opened with a command containing switches/options (eg, cmd /x). Generally this isn't a big problem as subshells are usually opened without extra switches and %_PAUSE_NEEDED% can be set to 0, when necessary.
Caveat codor.
#echo %CMDCMDLINE% | find /I " /c " >nul && pause

Detecting how a batch file was executed

Assuming Windows, is there a way I can detect from within a batch file if it was launched from an open command prompt or by double-clicking? I'd like to add a pause to the end of the batch process if and only if it was double clicked, so that the window doesn't just disappear along with any useful output it may have produced.
Any clever ways to do this? I'm looking for solutions I could rely on to work on a machine that was configured more or less with default settings.
I just ran a quick test and noticed the following, which may help you:
When run from an open command prompt, the %0 variable does not have double quotes around the path. If the script resides in the current directory, the path isn't even given, just the batch file name.
When run from explorer, the %0 variable is always enclosed in double quotes and includes the full path to the batch file.
This script will not pause if run from the command console, but will if double-clicked in Explorer:
#echo off
setlocal enableextensions
set SCRIPT=%0
set DQUOTE="
#echo do something...
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
:EXIT
if defined PAUSE_ON_CLOSE pause
EDIT:
There was also some weird behavior when running from Explorer that I can't explain. Originally, rather than
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
I tried using just an if:
if %SCRIPT:0,1% == ^" set PAUSE_ON_CLOSE=1
This would work when running from an open command prompt, but when run from Explorer it would complain that the if statement wasn't correct.
Yes. Patrick Cuff's final example almost worked, but you need to add one extra escape, '^', to make it work in all cases. This works great for me:
set zero=%0
if [^%zero:~0,1%] == [^"] pause
However, if the name of the batch file contains a space, it'll be double quoted in either case, so this solution won't work.
Don't overlook the solution of having two batch files:
abatfile.bat and abatfile-with-pause.bat
The second simply calling the first and adding a pause
Here's what I use :
rem if double clicked it will pause
for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c" pause
I use a parameter "automode" when I run my batch files from scripts.
set automode=%7
(Here automode is the seventh parameter given.)
Some code follows and when the file should pause, I do this:
if #%automode%==# pause
One easy way to do it is described here:
http://steve-jansen.github.io/guides/windows-batch-scripting/part-10-advanced-tricks.html
There is little typo in the code mentioned in the link. Here is correct code:
#ECHO OFF
SET interactive=0
ECHO %CMDCMDLINE% | FINDSTR /L /I %COMSPEC% >NUL 2>&1
IF %ERRORLEVEL%==0 SET interactive=1
ECHO do work
IF "%interactive%"==1 PAUSE
EXIT /B 0
Similar to a second batch file you could also pause if a certain parameter is not given (called via clicking).
This would mean only one batch file but having to specify a -nopause parameter or something like that when calling from the console.
crazy idea: use tasklist and parse it's results.
I've wrote in a test batch file:
tasklist > test.out
and when I double-clicked it, there was an additional "cmd.exe" process just before the tasklist process, that wasn't there when the script was run from command line (but note that might not be enough if someone opens a command line shell and then double-click the batch file)
Just add pause regardless of how it was opened? If it was opened from command prompt no harm done apart from a harmless pause. (Not a solution but just thinking whether a pause would be so harmful / annoying )

Resources