gh-85028: Honor startupinfo.wShowWindow with shell=True (GH-20975)

serhiy-storchaka <[email protected]> Mon, 10 Aug 2026 04:42:14 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/b2c7b3483ca4f5787c7785e1d5e09c51ef27ea8f
commit: b2c7b3483ca4f5787c7785e1d5e09c51ef27ea8f
branch: main
author: Zackery Spytz <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-10T08:40:21Z
summary:

gh-85028: Honor startupinfo.wShowWindow with shell=True (GH-20975)

On Windows, subprocess.Popen() with shell=True no longer overrides
wShowWindow of the startupinfo argument, so the console window of the
started program can be shown.

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/Windows/2020-06-19-00-14-52.bpo-40851.0-3EJP.rst
M Lib/subprocess.py
M Lib/test/test_subprocess.py

diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index a14fede00c391c..d38cc756ec479f 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1688,8 +1688,9 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
                     close_fds = False
 
             if shell:
-                startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
-                startupinfo.wShowWindow = _winapi.SW_HIDE
+                if not startupinfo.dwFlags & _winapi.STARTF_USESHOWWINDOW:
+                    startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
+                    startupinfo.wShowWindow = _winapi.SW_HIDE
                 if not executable:
                     # gh-101283: without a fully-qualified path, before Windows
                     # checks the system directories, it first looks in the
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index d1840e97d0f2f7..4cea07b3d2c774 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -3777,6 +3777,34 @@ def test_startupinfo_copy(self):
             self.assertEqual(startupinfo.wShowWindow, subprocess.SW_HIDE)
             self.assertEqual(startupinfo.lpAttributeList, {"handle_list": []})
 
+    def test_startupinfo_shell_show_window(self):
+        # gh-85028: shell=True must not override wShowWindow set by the caller
+        import _winapi
+        SW_MAXIMIZE = 3
+        used = []
+        create_process = _winapi.CreateProcess
+
+        def spy(*args):
+            # The startup info is the last argument of CreateProcess()
+            used.append(args[-1])
+            return create_process(*args)
+
+        startupinfo = subprocess.STARTUPINFO(
+            dwFlags=subprocess.STARTF_USESHOWWINDOW,
+            wShowWindow=SW_MAXIMIZE)
+        with mock.patch.object(_winapi, 'CreateProcess', spy):
+            rc = subprocess.call(ZERO_RETURN_CMD, shell=True,
+                                 startupinfo=startupinfo)
+            self.assertEqual(rc, 0)
+            rc = subprocess.call(ZERO_RETURN_CMD, shell=True)
+            self.assertEqual(rc, 0)
+
+        requested, default = used
+        self.assertEqual(requested.wShowWindow, SW_MAXIMIZE)
+        # Without STARTF_USESHOWWINDOW the shell window is still hidden.
+        self.assertTrue(default.dwFlags & subprocess.STARTF_USESHOWWINDOW)
+        self.assertEqual(default.wShowWindow, subprocess.SW_HIDE)
+
     # CREATE_NEW_CONSOLE creates a "popup" window.
     @support.requires_resource('gui')
     def test_creationflags(self):
diff --git a/Misc/NEWS.d/next/Windows/2020-06-19-00-14-52.bpo-40851.0-3EJP.rst b/Misc/NEWS.d/next/Windows/2020-06-19-00-14-52.bpo-40851.0-3EJP.rst
new file mode 100644
index 00000000000000..70f5efad398b03
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2020-06-19-00-14-52.bpo-40851.0-3EJP.rst
@@ -0,0 +1,3 @@
+:class:`subprocess.Popen` with ``shell=True`` on Windows now honors
+``wShowWindow`` of the *startupinfo* argument, so the console window of the
+started program can be shown.  Previously it was always hidden.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]