Detect if python program is executed via Windows GUI (double-click) vs command prompt

Question:

Background
I have a Python 3.5 console program compiled into a Windows executable via pyinstaller.

Question

  • When executed via a command prompt, I’d like my program to run with whatever arguments were supplied (possibly none).
  • When executed via the operating system’s GUI (i.e. by double-clicking the .exe in Windows Explorer on Windows, etc) I’d like my program to prompt the user for input. I also need my program to pause before exiting so the user can read the output.

How do I detect these different scenarios?

Constraints

  1. The executable must be able to run on a bare-bones (i.e. fresh install) Windows/RedHat machine.
  2. The compiled executable must be a single file and may not rely on other files not packaged inside the compiled executable (pyinstaller allows files to be packaged inside the compiled executable).
  3. The program may depend on 3rd party python packages.

Things I’ve Tried

Asked By: ErikusMaximus

||

Answers:

Turns out there is a simple and concise way to determine this on Windows.
https://stackoverflow.com/a/14394730/3508142

The PROMPT environment variable defines the prompt text in a command prompt.
https://ss64.com/nt/prompt.html

# If the program was started via the GUI (i.e. by double-clicking the executable),
# then prevent the console window from closing automatically.
if os.name == 'nt' and 'PROMPT' not in os.environ:
    input('Press ENTER to continue...')
Answered By: ErikusMaximus

Count processes attached to the console

Windows API documentation for GetConsoleProcessList

import ctypes

# Load kernel32.dll
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
# Create an array to store the processes in.  This doesn't actually need to
# be large enough to store the whole process list since GetConsoleProcessList()
# just returns the number of processes if the array is too small.
process_array = (ctypes.c_uint * 1)()
num_processes = kernel32.GetConsoleProcessList(process_array, 1)
# num_processes may be 1 if your compiled program doesn't have a launcher/wrapper.
if num_processes == 2:
    input('Press ENTER to continue...')
Answered By: ErikusMaximus

I’m not sure if "does my process have a terminal" was strictly what the OP was asking (he asked about "was I launched by cmd or a GUI", which is independant of whether you have a console window), but for anyone else who searches their way to this question:

The current accepted answer (GetConsoleProcessList) isn’t quite right, since it doesn’t work with virtual terminals. See the warning at https://learn.microsoft.com/en-us/windows/console/getconsoleprocesslist: "This API is not recommended and does not have a virtual terminal equivalent."

If you want to know if you have a console, you can do:

def has_console():
    try:
        with open('CONIN$'):
            return True
    except:
        return False

If the process has a console, it can be accessed with CONIN$ and CONOUT$. That works for both traditional consoles and virtual terminals. If there’s no console, it’ll throw WinError with ERROR_INVALID_HANDLE.

You could also see if kernel32.GetStdHandle(win32api.STD_INPUT_HANDLE) is nonzero. I think that’s a bit different and would be nonzero if the process was started with file redirection, where CONIN$ specifically checks for a console. For most applications the difference probably doesn’t matter.

Answered By: Glenn Maynard