I assumed that if both 3.7 and 3.8 were installed, the user would want the “python.exe” appexec link to target the 3.8 binary. If the link instead targets the 3.7 binary, then running python
via the Win+R run dialog or os.startfile
should work given the default settings (it does for me).
To reiterate, the execution of “python” via the shell API doesn’t directly consume the appexec link if a system “App Paths” subkey exists for “python.exe”. The 3.7 app sets the value of this key to the path of the 3.7 “python.exe” binary.
When the app binary gets directly executed by CreateProcessW
in the normal session security context, it does indirectly use the appexec link of the same name – assuming it exists and targets the executed binary. If the link doesn’t exist or doesn’t target the executed binary, the call fails with a permission error. Otherwise, CreateProcessW
extracts security attributes from the link that it sets in a custom access token for the child process. It also impersonates this token in the calling thread, which allows the NtCreateUserProcess
system call to succeed.
The security attributes that it sets are required by a conditional access-control entry (ACE) that grants read and execute access to the app’s installed files. For the Python 3.8 app directory, this ACE has the following SDDL definition:
(XA;OICI;0x1200a9;;;BU;
(WIN://SYSAPPID Contains
"PYTHONSOFTWAREFOUNDATION.PYTHON.3.8_QBZ5N2KFRA8P0"))
“XA” is a conditional access-allowed ACE. “OI” means object (file) inherit, and “CI” means container (directory) inherit. The granted access is 0x1200a9, i.e. FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
. The security principle is “BU”, i.e. “BUILTIN\Users”. The condition is the presence of a security attribute named “WIN://SYSAPPID” in the token that contains the given app identifier.
If the user sets the “python.exe” appexec link to run 3.8, and the 3.7 app is also installed, then running “python” via the shell API will fail. It attempts to run the 3.7 binary, but there’s no matching “python.exe” appexec link. To correct this, the system (not user) “App Paths” subkey for “python.exe” would need to be manually modified to run the 3.8 binary. This in turn requires the “python3.8.exe” appexec link to be enabled, which should be the case by default. One could also simply remove the system “App Paths” key for “python.exe”, which allows the shell API to search PATH
.
Yes, it seems this determines the registered names in “App Paths” and the target path of appexec links. And it’s self-consistent in that the “App Paths” key runs the same binary that’s the target of the appexec links.
The 3.7 app installs a “python.exe” binary. The user’s “python.exe”, “python3.exe”, and “python3.7.exe” appexec links, if enabled for 3.7, all target this binary. It also registers a system “App Paths” key named “python.exe” that directly executes this binary. The latter works as long as the user has the “python.exe” appexec link set to 3.7.
The 3.8 app installs both “python.exe” and “python3.8.exe” binaries. All of the appexec links, if enabled for 3.8, target the “python3.8.exe” binary, as does the same-named system “App Paths” key. The binary can be directly executed in a normal session context, such as via os.startfile('python3.8.exe')
, as long as the user has a “python3.8.exe” appexec link set to 3.8.
On a related note, apparently the app startup code has been modified to tell a white lie. Even though the process image is “python3.8.exe”, it pretends that sys.executable
is “python.exe”:
>>> print(os.path.basename(_winapi.GetModuleFileName(0)))
python3.8.exe
>>> print(os.path.basename(sys.executable))
python.exe
This still works because “python.exe” exists and is allowed to be executed from the app security context.