I'd like to understand how subprocess.Popen works on windows when shell=False. In particular, how is the environment or variable expansion taken into consideration by the underlying algorithm? Consider the below simple test: import os import subprocess import textwrap def truncate(string, width): if len(string) > width: string = string[:width - 3] + '...' return string if __name__ == '__main__': index = 0 commands = [ ["where", "find.exe"], "where find.exe", [r"c:\windows\system32\where.exe", "find.exe"], r"c:\windows\system32\where.exe find.exe", ["find.exe", "--version"], "find.exe --version", r"D:\sources\personal\sublimemerge\Git\usr\bin\find.exe --version", ] def run(target, shell): global index label = ( f'{index:<2}) subprocess.run(' f'{repr(target)}, shell={shell}, ' f'stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ' f'universal_newlines=True)' ) label = f"{label:<160}" try: x = subprocess.run(target, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) print(f"{label} - returncode={x.returncode} - {repr(truncate(x.stdout, 40))}") except Exception as e: print(f"{label} - exception={repr(truncate(str(e), 90))}") index += 1 for c in commands: run(c, shell=True) run(c, shell=False) Where the output looks like this: 0 ) subprocess.run(['where', 'find.exe'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 1 ) subprocess.run(['where', 'find.exe'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 2 ) subprocess.run('where find.exe', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 3 ) subprocess.run('where find.exe', shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 4 ) subprocess.run(['c:\\windows\\system32\\where.exe', 'find.exe'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 5 ) subprocess.run(['c:\\windows\\system32\\where.exe', 'find.exe'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 6 ) subprocess.run('c:\\windows\\system32\\where.exe find.exe', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 7 ) subprocess.run('c:\\windows\\system32\\where.exe find.exe', shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'D:\\sources\\personal\\sublimemerge\\Git\\...' 8 ) subprocess.run(['find.exe', '--version'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'find (GNU findutils) 4.6.0\nCopyright ...' 9 ) subprocess.run(['find.exe', '--version'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=2 - 'FIND: Parameter format not correct\n' 10) subprocess.run('find.exe --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'find (GNU findutils) 4.6.0\nCopyright ...' 11) subprocess.run('find.exe --version', shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=2 - 'FIND: Parameter format not correct\n' 12) subprocess.run('D:\\sources\\personal\\sublimemerge\\Git\\usr\\bin\\find.exe --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'find (GNU findutils) 4.6.0\nCopyright ...' 13) subprocess.run('D:\\sources\\personal\\sublimemerge\\Git\\usr\\bin\\find.exe --version', shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - returncode=0 - 'find (GNU findutils) 4.6.0\nCopyright ...' The first question that comes to my mind would be, why do I need to specify the full executable path when shell=False? Why is env not considered the same way than when shell=True is used? Actually... look at cases 5,7, by just looking at those one would think env is being taken into consideration. I guess the whole matter boils down to understand how env is given to CreateProcess call in cpython's _winapi.c. Continue reading...