29

Is there a way for a batch file (in this case, running on Windows XP) to determine whether it was launched from a command line (i.e. inside a console window) or launched via the shell (e.g. by double-clicking)?

I have a script which I'd like to have pause at certain points when run via the shell, but not when run at a command line. I've seen a similar question on SO, but am unable to use the same solution for two reasons: first, whether or not it pauses needs to be dependent on multiple factors, only one of which is whether it was double-clicked. Second, I'll be distributing this script to others on my team and I can't realistically ask all of them to make registry changes which will affect all scripts.

Is this possible?

Community
  • 1
  • 1
Ben Blank
  • 54,908
  • 28
  • 127
  • 156
  • The other answer didn't suggest a registry change. Only changing the command line that gets executed on a shortcut. Granted, that won't help you much if you double-click the actual batch file. – Joey Aug 23 '10 at 23:58
  • @Joey — You're right; I was conflating the answer and its first comment. ;-) – Ben Blank Aug 24 '10 at 00:51
  • Related: https://stackoverflow.com/questions/5859854/detect-if-bat-file-is-running-via-double-click-or-from-cmd-window – riQQ May 12 '21 at 19:28

4 Answers4

38

Found one :-) – After desperately thinking of what cmd might do when run interactively but not when launching a batch file directly ... I finally found one.

The pseudo-variable %cmdcmdline% contains the command line that was used to launch cmd. In case cmd was started normally this contains something akin to the following:

"C:\Windows\System32\cmd.exe"

However, when launching a batch file it looks like this:

cmd /c ""C:\Users\Me\test.cmd" "

Small demo:

@echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
if defined DOUBLECLICKED pause

This way of checking might not be the most robust, though, but /c should only be present as an argument if a batch file was launched directly.

Works on my machine

Tested here on Windows 7 x64. It may or may not work, break, do something weird, eat children (might be a good thing) or bite you in the nose.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Joey
  • 344,408
  • 85
  • 689
  • 683
  • Bah. I was just playing around with something similar, but was trying to determine if `%CMDCMDLINE%` contained `%0`. Checking for `/c` is *so* much easier, though, and I can't think of any edge cases where my approach would have worked but this wouldn't. Thanks! – Ben Blank Aug 24 '10 at 00:17
  • 2
    @Ben: I thought of the `%0` thing as well but sadly it breaks. When I run `%cmdcmdline%` through `for` the path and name of the batch gets broken up into two tokens since my home directory contains spaces and the command line has double double quotes as can be seen above which throws `for` off. Another option might be to use `echo.%cmdcmdline%|findstr /i /c:"%~0" >nul 2>&1 && set DOUBLECLICKED=1` (or maybe `||`, I always make mistakes with `findstr`), which would actually search for `%0` instead of relying on tokenizing. Still, it would fail if the batch was called by another batch. – Joey Aug 24 '10 at 00:24
  • — I was actually using substring tricks rather than `FOR`, but batch is not well-suited for string manipulation. Your `/c` trick is much simpler and easier and should work unless the user is doing something *really* weird. – Ben Blank Aug 24 '10 at 01:00
  • I was also thinking about checking tasklist.exe output (it has some nice comparison operators) - the window title in particular. i thought that it might contain some hints, like the path of the batch file when double clicked, but alas, no. – x0n Aug 24 '10 at 01:19
  • 4
    On my copy of Windows 7, running a batch file as administrator makes explorer pass a capital `/C` as the argument. This will do a case-insensitive compare: `for %%x in (%CMDCMDLINE%) do if /I [%%~x] equ [/c] pause` – indiv Jan 04 '13 at 23:15
  • indiv: Fixed it, thanks. Also fixed a quoting problem in the original code which may manifest itself with certain arguments. – Joey Jan 05 '13 at 14:18
  • 1
    Not bad, but insufficient IMO as even invoking from PowerShell uses `cmd /c` – Ruben Bartelink Feb 06 '16 at 13:51
  • @Ruben, invoking from *any* other program that is not `cmd` will use `cmd /c` (well, has to, anyway). That's unavoidable because, after all, `cmd` is probably the only program that can interpret batch files without delegating to another program. – Joey Feb 06 '16 at 14:45
  • @Joey Yes, I know; however the title of the question remains "from a console window", rather than "from a CMD command prompt". So for my use case (PS is my primary shell), the solution doesnt meet that goal. I +1'd in acknowledgement of this being a solution, but I personally wouldn't have bothered had I known that "A Console Window" had * on it meaning *Except PowerShell ones of course obviously – Ruben Bartelink Feb 06 '16 at 15:42
10

A consolidated answer, derived from much of the information found on this page (and some other stack overflow pages with similar questions). This one does not rely on detecting /c, but actually checks for the name of the script in the command line. As a result this solution will not pause if you double-clicked on another batch and then called this one; you had to double-click on this particular batch file.

:pauseIfDoubleClicked
setlocal enabledelayedexpansion
set testl=%cmdcmdline:"=%
set testr=!testl:%~nx0=!
if not "%testl%" == "%testr%" pause
  1. The variable "testl" gets the full line of the cmd processor call, stripping out all of the pesky double quotes.
  2. 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).
  3. 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 (or called from another batch file), go on.

Edit: The same can be done in a single line:

echo %cmdcmdline% | findstr /i /c:"%~nx0" && set standalone=1

In plain English, this

  • pipes the value of %cmdcmdline% to findstr, which then searches for the current script name
  • %0 contains the current script name, of course only if shift has not been called beforehand
  • %~nx0 extracts file name and extension from %0
  • >NUL 2>&1 mutes findstr by redirecting any output to NUL
  • findstr sets a non-zero errorlevel if it can't find the substring in question
  • && only executes if the preceding command returned without error
  • as a consequence, standalone will not be defined if the script was started from the command line

Later in the script we can do:

if defined standalone pause
Tomalak
  • 332,285
  • 67
  • 532
  • 628
DocOc
  • 635
  • 7
  • 13
  • 2
    Nice, I like the one-liner! This works on Windows 7. If you don't use "shift" you can even directly call `ECHO %cmdcmdline% | findstr /i /c:"%~nx0" >NUL 2>&1 && PAUSE` at the end of the script - no need for a variable. – EM0 May 17 '17 at 10:00
2

One approach might be to create an autoexec.nt file in the root of c:\ that looks something like:

@set nested=%nested%Z

In your batch file, check if %nested% is "Z" - if it is "Z" then you've been double-clicked, so pause. If it's not "Z" - its going to be "ZZ" or "ZZZ" etc as CMD inherits the environment block of the parent process.

-Oisin

x0n
  • 51,312
  • 7
  • 89
  • 111
  • Interesting idea, but it doesn't seem to work. `AUTOEXEC.NT` seems to be ignored completely, `AUTOEXEC.BAT` gets executed only once (at system startup), and the AutoRun registry key gets executed once per `CMD.EXE` session regardless of how it was launched. – Ben Blank Aug 23 '10 at 22:34
  • 1
    Batch files don't get started in a new `cmd` process. So if I just fire up a `cmd` instance and start the batch file from there, `nested` will be `Z`. Otherwise a nice idea for detecting nested shells, but not suitable for batch files. – Joey Aug 23 '10 at 23:56
  • 1
    Another note: `autoexec.nt` never resided in the root directory of the drive but instead in `%systemroot%\system32`. As far as I know it only affects `command.com`, not `cmd`, though. – Joey Aug 24 '10 at 00:15
  • 1
    oh well. sometimes it's just worth posting your thoughts in the hope that it might trigger someone else's brainwave. – x0n Aug 24 '10 at 01:16
0

A little more information...

I start with a batch-file (test.cmd) that contains:

@echo %cmdcmdline%

If I double-click the "test.cmd" batch-file from within Windows Explorer, the display of echo %cmdcmdline% is:

cmd /c ""D:\Path\test.cmd" "

When executing the "test.cmd" batch-file from within a Command Prompt window, the display of
echo %cmdcmdline% depends on how the command window was started...

If I start "cmd.exe" by clicking the "Start-Orb" and "Command Prompt" or if I click "Start-Orb" and execute "cmd.exe" from the search/run box. Then I execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is:

"C:\Windows\system32\cmd.exe"

Also, for me, if I click "Command Prompt" from the desktop shortcut, then execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is also:

"C:\Windows\system32\cmd.exe"

But, if I "Right-Click" inside a Windows Explorer window and select "Open Command Prompt Here", then execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is:

"C:\Windows\System32\cmd.exe" /k ver

So, just be careful, if you start "cmd.exe" from a shortcut that contains a "/c" in the "Target" field (unlikely), then the test in the previous example will fail to test this case properly.

Kevin Fegan
  • 1,292
  • 1
  • 16
  • 29