diff --git a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp index d910f99e7..8bc6b1147 100644 --- a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp +++ b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp @@ -29,6 +29,7 @@ #include "WindowsEnvironmentInfo.hpp" +#include #include // This define is only available for TH1+ @@ -139,17 +140,44 @@ namespace PAL_NS_BEGIN { std::to_string(static_cast(LOWORD(pffi->dwProductVersionLS))); } - /** - * Get OS BuildLabEx string - */ - std::string getOsBuildLabEx() + const PCSTR c_currentVersion_Key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + + bool getCurrentVersionDwordValue(PCSTR valueName, uint32_t& value) { - char buff[MAX_PATH] = { 0 }; - const PCSTR c_currentVersion_Key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; - const PCSTR c_buildLabEx_ValueName = "BuildLabEx"; - DWORD size = sizeof(buff); - RegGetValueA(HKEY_LOCAL_MACHINE, c_currentVersion_Key, c_buildLabEx_ValueName, RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, NULL, (char*)buff, &size); - return buff; + DWORD regValue = 0; + DWORD size = sizeof(regValue); + if (RegGetValueA( + HKEY_LOCAL_MACHINE, + c_currentVersion_Key, + valueName, + RRF_RT_REG_DWORD | RRF_SUBKEY_WOW6464KEY, + NULL, + ®Value, + &size) != ERROR_SUCCESS) + { + return false; + } + + value = regValue; + return true; + } + + std::string formatWindowsOsFullVersion( + unsigned long majorVersion, + unsigned long minorVersion, + unsigned long buildNumber, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision) + { + std::string version = std::to_string(majorVersion) + "." + + std::to_string(minorVersion) + "." + + std::to_string(static_cast(buildNumber)); + if (hasUpdateBuildRevision) + { + version += "." + std::to_string(updateBuildRevision); + } + + return version; } /** @@ -175,8 +203,6 @@ namespace PAL_NS_BEGIN { */ void getOsVersion(std::string& osMajorVersion, std::string& osFullVersion) { - std::string buildLabEx = getOsBuildLabEx(); - HMODULE hNtDll = ::GetModuleHandle(TEXT("ntdll.dll")); typedef HRESULT NTSTATUS; typedef NTSTATUS(__stdcall * RtlGetVersion_t)(PRTL_OSVERSIONINFOW); @@ -186,8 +212,22 @@ namespace PAL_NS_BEGIN { if (pRtlGetVersion && SUCCEEDED(pRtlGetVersion(&rtlOsvi))) { osMajorVersion = std::to_string((long long)rtlOsvi.dwMajorVersion) + "." + std::to_string((long long)rtlOsvi.dwMinorVersion); - osFullVersion = osMajorVersion + "." + std::to_string((long long)rtlOsvi.dwBuildNumber); - osFullVersion = osMajorVersion + "." + buildLabEx; + + // Use the kernel's authoritative dwBuildNumber from RtlGetVersion + // (see Windows team guidance on issue #1407). The registry + // CurrentBuildNumber / CurrentBuild values are only useful for + // offline reads of another Windows installation; for the running + // OS they are always in sync with the kernel build, so prefer the + // API. UBR is registry-only and is always safe to combine with the + // kernel build number. + uint32_t updateBuildRevision = 0; + bool hasUpdateBuildRevision = getCurrentVersionDwordValue("UBR", updateBuildRevision); + osFullVersion = formatWindowsOsFullVersion( + rtlOsvi.dwMajorVersion, + rtlOsvi.dwMinorVersion, + rtlOsvi.dwBuildNumber, + updateBuildRevision, + hasUpdateBuildRevision); } } diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index 15bb98e5e..0bf8a06a3 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -7,6 +7,9 @@ #include "pal/PseudoRandomGenerator.hpp" #include "Version.hpp" +#include +#include + #ifdef HAVE_MAT_LOGGING #include "pal/PAL.hpp" #include @@ -24,6 +27,17 @@ using namespace PAL::detail; using namespace testing; +#if defined(_WIN32) || defined(_WIN64) +namespace PAL_NS_BEGIN { + std::string formatWindowsOsFullVersion( + unsigned long majorVersion, + unsigned long minorVersion, + unsigned long buildNumber, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision); +} PAL_NS_END +#endif + class PalTests : public Test {}; TEST_F(PalTests, UuidGeneration) @@ -95,6 +109,29 @@ TEST_F(PalTests, FormatUtcTimestampMsAsISO8601) EXPECT_THAT(PAL::formatUtcTimestampMsAsISO8601(2147483647999ll), Eq("2038-01-19T03:14:07.999Z")); } +#if defined(_WIN32) || defined(_WIN64) +TEST_F(PalTests, WindowsOsFullVersionIncludesUbrWhenPresent) +{ + EXPECT_THAT( + PAL::formatWindowsOsFullVersion(10, 0, 26200, 1234, true), + Eq("10.0.26200.1234")); +} + +TEST_F(PalTests, WindowsOsFullVersionOmitsUbrWhenMissing) +{ + EXPECT_THAT( + PAL::formatWindowsOsFullVersion(10, 0, 26200, 0, false), + Eq("10.0.26200")); +} + +TEST_F(PalTests, WindowsOsFullVersionIncludesZeroUbrWhenPresent) +{ + EXPECT_THAT( + PAL::formatWindowsOsFullVersion(10, 0, 26200, 0, true), + Eq("10.0.26200.0")); +} +#endif + TEST_F(PalTests, MonotonicTime) { int64_t t0 = PAL::getMonotonicTimeMs();