39

I have to create a console application which needs certain parameters. If they are missing or wrong I print out an error message.

Now the problem: If someone starts the program from the explorer by double-clicking the console window disappears immediately. (But the application is not entirely useless from the explorer, you could drag files onto it and it would work)

I could always wait for a keypress, but I don't want that if the user did start it from the command line.

Is there some way to distinguish between these situations?

Daniel Rikowski
  • 71,375
  • 57
  • 251
  • 329

6 Answers6

33

See http://support.microsoft.com/kb/99115, "INFO: Preventing the Console Window from Disappearing".

The idea is to use GetConsoleScreenBufferInfo to determine that the cursor has not moved from the initial 0,0 position.

Code sample from @tomlogic, based on the referenced Knowledge Base article:

// call in main() before printing to stdout
// returns TRUE if program is in its own console (cursor at 0,0) or
// FALSE if it was launched from an existing console.
// See http://support.microsoft.com/kb/99115
#include <stdio.h>
#include <windows.h>
int separate_console( void)
{
    CONSOLE_SCREEN_BUFFER_INFO csbi;

    if (!GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE), &csbi))
    {
        printf( "GetConsoleScreenBufferInfo failed: %lu\n", GetLastError());
        return FALSE;
    }

    // if cursor position is (0,0) then we were launched in a separate console
    return ((!csbi.dwCursorPosition.X) && (!csbi.dwCursorPosition.Y));
}
tomlogic
  • 11,489
  • 3
  • 33
  • 59
Per Mildner
  • 10,469
  • 23
  • 30
  • 1
    This quite original :) Although this is a hack it does exactly what I need and it's far more simple then getting the PPID. – Daniel Rikowski Feb 05 '09 at 07:07
  • you should only use this hack when there are no arguments passed to the app so you can still do "cls&app filename" from cmd.exe – Anders Feb 16 '09 at 02:31
  • Just implemented this solution, and it works great for a MinGW/MSYS-compiled command-line app. And, @Anders, `cls && appname.exe` works just fine. I'm going to edit this answer to include the code I'm using, for others to reference. – tomlogic Mar 22 '12 at 19:44
7

GetConsoleTitle()

I've seen code which performs

if (!GetConsoleTitle(NULL, 0) && GetLastError() == ERROR_SUCCESS) {
    // Console
} else {
    // GUI
}

BUT... I've found that AttachConsole() is more helpful

In C++ (off the top of my head, and I'm no C++ programmer)

if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
    // GUI
} else {
    // Console, and you have a handle to the console that already exists.
}

Is more effective. Additionally, if you find yourself in a GUI environment and would like to stay there as long as you can, but later find something catastrophic has happened that could really use a dump to a console window (you can't be arsed writing an edit box window to lot it to or attach to the NT System log and throw up a MessageBox()) well then you can AllocConsole() later on in the process, when GUI methods have failed.

tomlogic
  • 11,489
  • 3
  • 33
  • 59
bobsobol
  • 79
  • 1
  • 1
  • 2
    Some feedback: I'm looking to do this in a command-line app compiled with MinGW/MSYS. Neither method worked for me -- I could not differentiate between dragging a file onto the app in Windows Explorer, and executing it on the command-line inside a bash shell. – tomlogic Mar 22 '12 at 19:25
6

I've found a much better solution using GetConsoleProcessList to get the attached process count to the current console. If this process is the only one attached it will be closed when the process exists.

I found it in a post https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 But it had a bug (at least in windows 10) since the documentation forbids invoking this function with null.

My solution was:

DWORD procId;
DWORD count = GetConsoleProcessList(&procId, 1);
if (count < 2) ...
3

I believe cmd.exe sets the CMDCMDLINE and CMDEXTVERSION environemntal variables when it starts. So if these are set your program was most probably started from a shell.

This isn't foolproof but it's something.

It's also possible to determine your parent PID in a few convoluted and possibly unreliable ways, or so I gather. You may want to look into that.

Artelius
  • 48,337
  • 13
  • 89
  • 105
  • 2
    I tried the environment variables, and they don’t exist, neither when run from cmd.exe nor when run outside it. However, typing `echo %cmdcmdline%` does produce something, so the variable is apparently only valid in cmd.exe itself, not its child processes. – Timwi Aug 20 '10 at 02:10
1

Here is the excellent answer from @DanielBenSassoon adapted for C#. Tested in Visual Studio 2019 and Windows 10.

// Gets a list of the process IDs attached to this console
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetConsoleProcessList(uint[] processList, uint processCount);

public static bool IsFinalProcess()
{
    // See: https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922
    uint[] procIDs = new uint[64];
    uint processCount = GetConsoleProcessList(procIDs, 64);
    return (processCount < 2);
}

This approach allows you to distinguish between four scenarios:

  • Debugging from the IDE (F5) [process count = 1]
  • Running from the IDE, but not debugging (Ctrl + F5) [process count = 2]
  • Double-clicking in Explorer [process count = 1]
  • Running from a command-prompt window [process count = 2]

When IsFinalProcess is true, you can use Console.ReadKey(false); to prevent the console window from disappearing after your application exits.

AlainD
  • 5,413
  • 6
  • 45
  • 99
0

EDIT: I've been using this .bat/.cmd wrapper successfully for a couple of years now:

@ECHO OFF

REM Determine if script was executed from an interactive shell
(echo %CMDCMDLINE% | find /i "%~0" >NUL 2>&1) && (set IS_INTERACTIVE=false) || (set IS_INTERACTIVE=true)

<call console application here>

REM Wait for keystroke
if "%IS_INTERACTIVE%" == "false" (
        echo.
        echo Hit any key to close.
        pause >NUL 2>&1
)

The advantage here is that this will work for all console applications, be it your own or someone else's (for which you might not have sources you could modify). The downside is, well, that you have a separate wrapper.


My original answer back in 2014 was this:

This works like a charm:

@echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" goto nonconsole

:console
<do something>
goto exit

:nonconsole
<do something>
pause

:exit

Copied from this thread. I also tried evaluating %cmdcmdline% myself, however there's an issue regarding quote characters ("), which prevents something like if "%cmdcmdline%" == "%ComSpec%" goto [target] from working.

Fonic
  • 2,625
  • 23
  • 20