2

How can I create a GUI application that also provides a CLI without having a popup shell using PyInstaller?

For example, if I create the following application with pyinstaller argparse_gui.py --noconsole, stdout isn't displayed in the shell:

C:\projects\argparse_gui\dist\argparse_gui>argparse_gui.exe -V

C:\projects\argparse_gui\dist\argparse_gui>

I can redirect stdout/stderr to a file with argparse_gui.exe -V > log.txt 2>&1, but that's not exactly user-friendly. I can see stdout if built without --noconsole, but then there's a nagging separate shell window.

# argparse_gui.py
import sys
import argparse
from PyQt5 import QtCore, QtWidgets, QtGui


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.init_widgets()
        self.init_layout()

    def init_widgets(self):
        self.label = QtWidgets.QLabel('Hello, world!')

    def init_layout(self):
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.label)

        centralWidget = QtWidgets.QWidget()
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)


if __name__ == '__main__':

    parser = argparse.ArgumentParser()

    parser.add_argument("-V", "--version", help="display application information", action='store_true')

    args = parser.parse_args()

    if args.version:
        print('Version 123', flush=True)
    else:
        app = QtWidgets.QApplication(sys.argv)
        main_window = MainWindow()
        main_window.show()
        sys.exit(app.exec_())

Lorem Ipsum
  • 4,020
  • 4
  • 41
  • 67
  • 1
    hey, I am having the same problem, did you find a way out of this? When I did similarly in C++ I added a command line argument that when passed forced the app to attach to console (something like https://stackoverflow.com/a/46050762) – eri0o Jan 26 '22 at 23:34
  • No, I couldn't figure anything out beyond what's in the responses here. Thank you for your comment and suggestion. – Lorem Ipsum Jan 27 '22 at 14:22
  • 1
    There is an extended discussion on the PyInstaller issue tracker about this. There may be a work around: https://github.com/pyinstaller/pyinstaller/issues/6244#issuecomment-1076666456 – Lorem Ipsum Jun 02 '22 at 18:49

1 Answers1

2

EDIT: I found a roundabout way to do it, but the console window appears until the GUI loads up. I placed the following code at the top of my main script and ran Pyinstaller without --windowed. This hides the console window if the script isn't run from an existing console.

import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
process_array = (ctypes.c_uint8 * 1)()
num_processes = kernel32.GetConsoleProcessList(process_array, 1)
if num_processes < 3: ctypes.WinDLL('user32').ShowWindow(kernel32.GetConsoleWindow(), 0)

Otherwise, I'm going to conclude that there isn't a way using PyInstaller. The EXE is bundled with pythonw if --noconsole/--windowed. PythonW doesn't have a console attached, even if launched from the console.

Command line arguments are still passed, however. You can still use sys.argv or argparser to access them.

When running in --noconsole, sys.stdout is a NullWriter object and sys.__stdout__ is None. Using open() on 1 raises an exception, CON and CONOUT$ fail to do anything. Redirecting the console to >&1 throws an error.

Note: Under some conditions stdin, stdout and stderr as well as the original values stdin, stdout and stderr can be None. It > is usually the case for Windows GUI apps that aren’t connected to a console and Python apps started with pythonw. https://docs.python.org/3/library/sys.html#sys.__stderr__

Pyinstaller explicitly uses PythonW.exe if the EXE is built with --noconsole. I wasn't able to find a way around this, such as loading the console bootloader if called from the cli but pythonw otherwise.

  • 1
    Thank you for taking the time to respond. Unfortunately, I am not making the connection between your answer and the problem I'm trying to solve. Maybe I have been unclear? My use of `2>&1` is only a workaround (which does work for me). I'm not sure how to adapt your answer into a working solution. – Lorem Ipsum May 25 '21 at 19:48