Why is QProcess converting the '=' in my arguments to spaces - windows

I've run into a weird error with a Qt program running on Windows. The program uses QProcess to spawn a child process wit two arguments. The program and arguments passed to the QProcess::start() method are of the form:
"batchfile.bat" "--option1=some_value" "--option2=some_other_value\with_a\path"
For some reason by the time those options get to the batchfile for processing the equals signs have been converted to spaces and it now looks like:
"batchfile.bat" "--option1 some_value" "--option2 some_other_value\with_a\path"
because of this, the processing fails. Any ideas what could be causing the equal signs to be replaced by spaces? I'm using the mingw build of the QT 4.6.3 framework found on the Qt download page.
EDIT:
Here's the actual code. I didn't write it (I'm a complete Qt noob) but I've got to try to get it working. It's part of an automated build system that runs on two versions of RHEL (4 and 5), OS X, and Windows. And it works fine everywhere but Windows.
QProcess sconsProcess;
sconsProcess.setWorkingDirectory(build.getBuildLocation());
sconsProcess.setProcessChannelMode(QProcess::MergedChannels);
qDebug()<<"Starting scons process:"<<build.getSconsLocation()<<QString("--variant=%1-%2").arg(build.getOs()).arg(build.getVariant())<<
QString("--source-release=%1").arg(build.getSettings().getSetting("sourceReleaseLocation", QStringList()<<"BUILDLOCATION"<<"VERSION",
QStringList()<<build.getBuildLocation()<<build.getBuildPackage().getVersion()).toString());
sconsProcess.start(build.getSconsLocation(), QStringList()<<QString("--variant=%1-%2").arg(build.getOs()).arg(build.getVariant())<<
QString("--source-release=%1").arg(build.getSettings().getSetting("sourceReleaseLocation", QStringList()"BUILDLOCATION"<<"VERSION",
QStringList()<<build.getBuildLocation()<<build.getBuildPackage().getVersion()).toString()));
qDebug()<<"Source release build process started";
The actaul values that translates into in Windows (the bit that gets printed out in the first qDebug() print call) is:
DEBUG: Starting scons process: "V:\Glast_Software\Toaster\tools\Python2.5\Scripts\scons-1.3.0.bat" "--variant=Windows-i386-32bit-vc71-Debug" "--source-release=V:\Glast_Software\Toaster\ReleaseManagerBuild\Windows-i386-32bit-vc71\Debug\ScienceTools\LATEST-1-3163\ScienceTools-LATEST-1-3163-source.zip"
However inside the scons-1.3.0.bat (I had it echo all the commands executed) the passed parameters look like:
"--variant Windows-i386-32bit-vc71-Debug" "--source-release V:\Glast_Software\Toaster\ReleaseManagerBuild\Windows-i386-32bit-vc71\Debug\ScienceTools\LATEST-1-3163\ScienceTools-LATEST-1-3163-source.zip"
with the equal signs missing.
EDIT (6/29/10):
I should add that this system is designed to run on a small Windows batch farm using the LSF batch queuing system. It only fails when the process is running as a batch job. When I run this program from the command line on one of the batch machines, it works perfectly and does exactly what it is supposed to do. So maybe it is an environment problem.

There's a good chance that this is because the quotes aren't making it through (they may need to be escaped, see the docs for QProcess::start()).
cmd.exe treats equals signs in command line options that aren't quoted as a separator between arguments similar to a space or tab. Just one of very many bits of oddness in Windows cmd scripting:
C:\test>type c:\util\cmdechoargs.cmd
#echo off
setlocal
set /a i=0
echo args[*]: %*
:loop
if {%1} == {} goto :eof
echo argv[%i%]: %1
set /a i=%i% + 1
shift
goto :loop
C:\test>cmdechoargs testing=123
args[*]: testing=123
argv[0]: testing
argv[1]: 123
C:\test>cmdechoargs "testing=123"
args[*]: "testing=123"
argv[0]: "testing=123"
The best documentation I've come across for how to handle command line arguments in Windows cmd scripts is Tim Hill's "Windows NT Shell Scripting" - get one used for only a penny!
Based on the examples given in your update, I think you might want your options that have equals signs in them to have quotes embedded inside them:
"\"--variant=%1-%2\""
"\"--source-release=%1\""
Edit -- new material
The following script has a routine that will strip the quotes off of an argument passed to a cmd script. The routine returns the 'dequoted' argument in an environment variable named RET using an idiom/technique from Tim Hill's book I mentioned above. I stole some of the dequoting code from an example here: http://ss64.com/nt/syntax-esc.html, but made it a bit more robust to handle empty quotes.
#echo off
setlocal
set /a i=0
echo args[*]: %*
:loop
if {%1} == {} goto :eof
echo.
echo argv[%i%]: %1
call :dequote %1
set dequoted_arg=%RET%
echo argv[%i%] ^(dequoted^): %dequoted_arg%
set /a i=%i% + 1
shift
goto :loop
:dequote
setlocal
SET _string=###%1###
if {%_string%} == {######} goto :dequote_empty
if {%_string%} == {###""###} goto :dequote_empty
SET _string=%_string:"###=%
SET _string=%_string:###"=%
SET _string=%_string:###=%
goto :dequote_done
:dequote_empty
set _string=
:dequote_done
endlocal & (set RET=%_string%) & goto :eof
This kind of thing is why you want to avoid (in my opinion) cmd scripts except for the simplest of tasks. But, I hope this helps you pass unquoted arguments to your scons process through your batch file.

Have you tried escaping the = signs? Also, the paths in your example surely need escaping of the \ character.

Related

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.

Parenthesis in Windows cmd-script variable values not allowed?

Why gives the following Windows 7 .cmd command script:
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
if 3==3 (
set JAVA_HOME=%SUN_JAVA_HOME%
)
echo ready
The following error message instead of printing "ready"
\Java\jdk1.6.0_17 was unexpected at this time.
The error message disapears, if I remove the "(x86)" in the path name.
on the command prompt, enter the following commands
C:
CD\
dir /ogen /x
This will show you the 8 character name for Program Files (x86)
Use that name (probably "Progra~2")
The problem is the parentheses grouping after the if 3==3 part.
While parsing the set JAVA_HOME=%SUN_JAVA_HOME% command, the interpreter immediately replaces the %SUN_JAVA_HOME% variable and that causes an early match of the closing parenthesis in (386).
This can be avoided if you enable delayed expansion and replace %SUN_JAVA_HOME% with !SUN_JAVA_HOME!:
setlocal enabledelayedexpansion
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
if 3==3 (
set JAVA_HOME=!SUN_JAVA_HOME!
)
echo ready
you have to enclose the set command by double quotes
replace
set SUN_JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_17
by
set SUN_JAVA_HOME="C:\Program Files (x86)\Java\jdk1.6.0_17"
because there's a space in the path
I've written about this a while ago (slightly outdated by now).
As an alternative, if you need grouping the commands, then use a subroutine:
if 3==3 call :foo
...
goto :eof
:foo
...
goto :eof
Previous answer is ok. I just want clarify it with simple example. It's about detecting Program Files directory for 32-bit application on x86 and x64 systems. There similar problem with "(x86)".
IF DEFINED ProgramFiles(x86) (GOTO x64) ELSE (GOTO x86)
:x64
SET AppDir=%ProgramFiles(x86)%\SomeFolder
GOTO next
:x86
SET AppDir=%ProgramFiles%\SomeFolder
:next
ECHO %AppDir%

Asterisk (*) in windows batch file command line argument gets expanded

I have a batch script that takes arguments from the command line. One of the arguments has a * in it. In spite of putting the argument in quotes, the * gets expanded before the argument gets used in the batch script.
I am using the following code to parse the arguments:
set CMDLINE_ARGS=%~1
shift
:get_args
if "%~1" == "" goto execute
set CMDLINE_ARGS=%CMDLINE_ARGS% %~1
shift
goto :get_args
This works on Windows Server 2003 but not on Server 2008 for some reason.
It has been a long time since I did anything like this but take a look at
SETLOCAL ENABLEDELAYEDEXPANSION
Not sure what Windows Server 2008 defaults to.

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