Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-129294: use winapi to determine win version before invoking cmd.exe #129295

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 70 additions & 28 deletions Lib/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"""

__version__ = '1.0.9'
__version__ = '1.0.10'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be updated?

Suggested change
__version__ = '1.0.10'
__version__ = '1.0.9'

It was updated a couple of months ago in #122547, but the previous change was 10 years ago (b9f4fea), and there have been lots of updates in the past decade that didn't change it:

https://github.com/python/cpython/commits/main/Lib/platform.py

Copy link
Member

@terryjreedy terryjreedy Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the module __version__ attribute really still needed? I thought that these were obsolete. If kept, is the use defined anywhere in a manner that would say when to bump it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it just wasn't kept properly. Each change should increase the version (path, minor, major as needed).


import collections
import os
Expand Down Expand Up @@ -384,6 +384,49 @@ def win32_edition():

return None

def _format_sp_and_product_type(spmajor, spminor, product_type):
csd = f"SP{spmajor}.{spminor}" if spminor else f"SP{spmajor}"
is_client = (int(product_type) == 1)
return csd, is_client

def _get_version_using_rtlgetversion():
import ctypes
class OSVERSIONINFOEXW(ctypes.Structure):
_fields_ = [
("dwOSVersionInfoSize", ctypes.c_uint32),
("dwMajorVersion", ctypes.c_uint32),
("dwMinorVersion", ctypes.c_uint32),
("dwBuildNumber", ctypes.c_uint32),
("dwPlatformId", ctypes.c_uint32),
("szCSDVersion", ctypes.c_wchar * 128), # Service Pack name
("wServicePackMajor", ctypes.c_uint16), # Major Service Pack version
("wServicePackMinor", ctypes.c_uint16), # Minor Service Pack version
("wSuiteMask", ctypes.c_uint16),
("wProductType", ctypes.c_uint8), # Type of product (Workstation, etc.)
("wReserved", ctypes.c_uint8) # Reserved
]

# Initialize OSVERSIONINFOEXW structure
os_version = OSVERSIONINFOEXW()
os_version.dwOSVersionInfoSize = ctypes.sizeof(OSVERSIONINFOEXW)

# Load ntdll.dll and call RtlGetVersion
ntdll = ctypes.WinDLL("ntdll")
rtl_get_version = ntdll.RtlGetVersion
rtl_get_version(ctypes.byref(os_version))

# Extract the version details
major = os_version.dwMajorVersion
minor = os_version.dwMinorVersion
build = os_version.dwBuildNumber
spmajor = os_version.wServicePackMajor
spminor = os_version.wServicePackMinor
product_type = os_version.wProductType

version = f"{major}.{minor}.{build}"
csd, is_client = _format_sp_and_product_type(spmajor, spminor, product_type)
return version, csd, is_client

def _win32_ver(version, csd, ptype):
# Try using WMI first, as this is the canonical source of data
try:
Expand All @@ -395,40 +438,39 @@ def _win32_ver(version, csd, ptype):
'ServicePackMajorVersion',
'ServicePackMinorVersion',
)
is_client = (int(product_type) == 1)
if spminor and spminor != '0':
csd = f'SP{spmajor}.{spminor}'
else:
csd = f'SP{spmajor}'
csd, is_client = _format_sp_and_product_type(spmajor, spminor, product_type)
return version, csd, ptype, is_client
except OSError:
pass

# Fall back to a combination of sys.getwindowsversion and "ver"
try:
from sys import getwindowsversion
except ImportError:
return version, csd, ptype, True

winver = getwindowsversion()
is_client = (getattr(winver, 'product_type', 1) == 1)
# Fall back to RtlGetVersion using ntdll
try:
version = _syscmd_ver()[2]
major, minor, build = map(int, version.split('.'))
except ValueError:
major, minor, build = winver.platform_version or winver[:3]
version = '{0}.{1}.{2}'.format(major, minor, build)

# getwindowsversion() reflect the compatibility mode Python is
# running under, and so the service pack value is only going to be
# valid if the versions match.
if winver[:2] == (major, minor):
version, csd, is_client = _get_version_using_rtlgetversion()
except Exception:
# Fall back to a combination of sys.getwindowsversion and "ver"
try:
csd = 'SP{}'.format(winver.service_pack_major)
except AttributeError:
if csd[:13] == 'Service Pack ':
csd = 'SP' + csd[13:]
from sys import getwindowsversion
except ImportError:
return version, csd, ptype, True

winver = getwindowsversion()
is_client = (getattr(winver, 'product_type', 1) == 1)
try:
version = _syscmd_ver()[2]
major, minor, build = map(int, version.split('.'))
except ValueError:
major, minor, build = winver.platform_version or winver[:3]
version = '{0}.{1}.{2}'.format(major, minor, build)

# getwindowsversion() reflect the compatibility mode Python is
# running under, and so the service pack value is only going to be
# valid if the versions match.
if winver[:2] == (major, minor):
try:
csd = 'SP{}'.format(winver.service_pack_major)
except AttributeError:
if csd[:13] == 'Service Pack ':
csd = 'SP' + csd[13:]
try:
import winreg
except ImportError:
Expand Down
Loading