3

I have some python code using subprocess.Popen to open a console application and get stdout/stderr from it.

Launching from the interpreter works fine and as intended.

After using cx_freeze with --base-name Win32GUI option the Popen pops up in a console window now and I can't capture stdout/stderr. If I remove --base-name Win32GUI it works as intended but I now have a console behind the UI.

Here is the code (I've tried it without startupinfo and without shell=False):

startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
subprocess.Popen(['exe', 'arg1', 'arg2'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, startupinfo=startupinfo)

I'm using out, err = p.communicate() to grab stdout/stderr

David
  • 692
  • 8
  • 21
  • Try giving it `stdin=subprocess.PIPE` as well, even though you don't want to send any stdin. [This page](http://www.py2exe.org/index.cgi/Py2ExeSubprocessInteractions) suggests that you need to give it handles for all three standard streams. – Thomas K Jun 11 '14 at 16:48
  • That seemed to work but now `p.communicate()` is returning `NoneType` for `out` (err still works). – David Jun 11 '14 at 18:47
  • Weird. What happens if you give it `stderr=subprocess.STDOUT` to combine them? – Thomas K Jun 11 '14 at 18:59
  • You should put the `Popen` call in a `try` statement and log to a file if an exception is raised. – Eryk Sun Jun 11 '14 at 19:24
  • If you're running the exe from the command prompt, it's probably inheriting bad console buffer handles for the process standard handles. `Popen` will die trying to call `DuplicateHandle` on an invalid handle. Try running it as `start my.exe` instead. The `start` command sets `CREATE_NEW_CONSOLE` in the process `CreationFlags`, so the invalid handles won't be copied into the GUI process. – Eryk Sun Jun 11 '14 at 19:26
  • Thanks for the help, I found a solution below! – David Jun 11 '14 at 19:31
  • Log the value of `repr(_winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE))`. If this isn't `None`, it's probably an invalid handle that can't be duplicated. You can use ctypes to clear it: `ctypes.windll.kernel32.SetStdHandle(_winapi.STD_INPUT_HANDLE, None)`. That gets rid of the bad handle. Repeat for `STD_OUTPUT_HANDLE` and `STD_ERROR_HANDLE`. – Eryk Sun Jun 11 '14 at 19:54
  • I have the same issue I'm trying to make a exe of a python Tkinter program which calls the subprocess that runs in the background until I stop in the GUI. now if i run .py file it works perfectly but when i create exe with cx_freeze it's not calling the subprocess i also explained my problem here >>https://stackoverflow.com/questions/46537800/python-subprocess-in-exe – Kalariya_M Oct 09 '17 at 10:20

1 Answers1

4

Okay I found a solution. It looks like since its a windows GUI Application stdout handle doesn't exist and it looks like subprocess inherits that behavior. So a workaround is a simple one and a more complicated one involved with the win32api and creating a pipe for it (didn't try this method).

Here is what finally worked:

startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
stdout_file = tempfile.NamedTemporaryFile(mode='r+', delete=False)
process = subprocess.Popen(['exe', 'arg1', 'arg2'], stdin=subprocess.PIPE, stdout=stdout_file, stderr=subprocess.PIPE, shell=False, startupinfo=startupinfo)
return_code = process.wait()
stdout_file.flush()
stdout_file.seek(0) # This is required to reset position to the start of the file
out = stdout_file.read()
stdout_file.close()
David
  • 692
  • 8
  • 21
  • In my case, I had to add `encoding="UTF-16"` to the NamedTemporaryFile. This prevents default "cp1252" encoding ... which is misread with `stdout_file.read()` (every character is separated by a space). - My command was : `["wmic", "logicaldisk"]` – samb Feb 26 '15 at 14:16
  • I encountered the same problem and this seemed to work for me – Har Dec 02 '16 at 14:50
  • This worked for me as well, and I used `asyncio.create_subprocess_exec()`. – Swedgin Apr 21 '20 at 12:32