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
- The executable must be able to run on a bare-bones (i.e. fresh install) Windows/RedHat machine.
- 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).
- The program may depend on 3rd party python packages.
Things I’ve Tried
-
sys.stdin.isatty()
https://stackoverflow.com/a/3818551/3508142
os.isatty(sys.stdout.fileno())
https://stackoverflow.com/a/6108504/3508142
These always return True
on Windows.
-
Searching StackOverflow / the internet:
How to determine if Python script was run via command line?
How can I check to see if a Python script was started interactively?
As far as I understand, a program is running interactively if the user started it regardless of whether it was started from a command prompt or the GUI.
-
I also considered checking to see if the parent process is cmd.exe
or explorer.exe
. However, starting the program via the Windows run command will make explorer.exe
the parent process. Starting the program via Task Manager will make Task Manager the parent process. These are edge cases that I could live with, but obviously I’d prefer a more robust solution.
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...')
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...')
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.
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
- The executable must be able to run on a bare-bones (i.e. fresh install) Windows/RedHat machine.
- 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).
- The program may depend on 3rd party python packages.
Things I’ve Tried
-
sys.stdin.isatty()
https://stackoverflow.com/a/3818551/3508142
os.isatty(sys.stdout.fileno())
https://stackoverflow.com/a/6108504/3508142
These always returnTrue
on Windows. -
Searching StackOverflow / the internet:
How to determine if Python script was run via command line?
How can I check to see if a Python script was started interactively?
As far as I understand, a program is running interactively if the user started it regardless of whether it was started from a command prompt or the GUI. -
I also considered checking to see if the parent process is
cmd.exe
orexplorer.exe
. However, starting the program via the Windows run command will makeexplorer.exe
the parent process. Starting the program via Task Manager will make Task Manager the parent process. These are edge cases that I could live with, but obviously I’d prefer a more robust solution.
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...')
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...')
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.