From 5e7bf1719b6e172993472dffaa1242909804f10c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 18 May 2026 17:29:35 -0500 Subject: [PATCH 1/3] Fix Windows OS build version reporting Use servicing-aware registry values so desktop telemetry reports the current Windows build instead of stale BuildLabEx data after OS updates. Files changed: - lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp - tests/unittests/PalTests.cpp Fixes #1407 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../WindowsDesktopSystemInformationImpl.cpp | 82 ++++++++++++++++--- tests/unittests/PalTests.cpp | 26 ++++++ 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp index d910f99e7..c37844a3d 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,62 @@ 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 getCurrentVersionStringValue(PCSTR valueName, std::string& 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; + if (RegGetValueA( + HKEY_LOCAL_MACHINE, + c_currentVersion_Key, + valueName, + RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, + NULL, + static_cast(buff), + &size) != ERROR_SUCCESS) + { + return false; + } + + value = buff; + return !value.empty(); + } + + bool getCurrentVersionDwordValue(PCSTR valueName, uint32_t& value) + { + 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, + std::string const& buildNumber, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision) + { + std::string version = std::to_string(majorVersion) + "." + std::to_string(minorVersion) + "." + buildNumber; + if (hasUpdateBuildRevision) + { + version += "." + std::to_string(updateBuildRevision); + } + + return version; } /** @@ -175,8 +221,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 +230,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; + + std::string buildNumber; + if (!getCurrentVersionStringValue("CurrentBuildNumber", buildNumber) && + !getCurrentVersionStringValue("CurrentBuild", buildNumber)) + { + buildNumber = std::to_string((long long)rtlOsvi.dwBuildNumber); + } + + uint32_t updateBuildRevision = 0; + bool hasUpdateBuildRevision = getCurrentVersionDwordValue("UBR", updateBuildRevision); + osFullVersion = formatWindowsOsFullVersion( + rtlOsvi.dwMajorVersion, + rtlOsvi.dwMinorVersion, + buildNumber, + updateBuildRevision, + hasUpdateBuildRevision); } } diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index 15bb98e5e..4cd9dcc39 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, + std::string const& buildNumber, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision); +} PAL_NS_END +#endif + class PalTests : public Test {}; TEST_F(PalTests, UuidGeneration) @@ -95,6 +109,18 @@ TEST_F(PalTests, FormatUtcTimestampMsAsISO8601) EXPECT_THAT(PAL::formatUtcTimestampMsAsISO8601(2147483647999ll), Eq("2038-01-19T03:14:07.999Z")); } +#if defined(_WIN32) || defined(_WIN64) +TEST_F(PalTests, WindowsOsFullVersionUsesServicingBuildValues) +{ + EXPECT_THAT( + PAL::formatWindowsOsFullVersion(10, 0, "26200", 1234, true), + Eq("10.0.26200.1234")); + EXPECT_THAT( + PAL::formatWindowsOsFullVersion(10, 0, "26200", 0, false), + Eq("10.0.26200")); +} +#endif + TEST_F(PalTests, MonotonicTime) { int64_t t0 = PAL::getMonotonicTimeMs(); From 05ce748365e05a8ff9e1f365122e73502bcd51e5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 20 May 2026 18:43:29 -0500 Subject: [PATCH 2/3] Add deterministic Windows OS version source tests Refactor the Windows OS full-version composition into a testable helper so registry and Rtl build source precedence can be covered without depending on the host machine state. The new PAL tests cover CurrentBuildNumber precedence, CurrentBuild fallback, Rtl build fallback, and missing UBR formatting so future key or fallback regressions are caught before merge. Files changed: - lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp - tests/unittests/PalTests.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../WindowsDesktopSystemInformationImpl.cpp | 52 +++++++++++++++---- tests/unittests/PalTests.cpp | 42 +++++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp index c37844a3d..f5b41e445 100644 --- a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp +++ b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp @@ -198,6 +198,39 @@ namespace PAL_NS_BEGIN { return version; } + std::string getWindowsOsFullVersionFromSources( + unsigned long majorVersion, + unsigned long minorVersion, + unsigned long rtlBuildNumber, + std::string const& currentBuildNumber, + bool hasCurrentBuildNumber, + std::string const& currentBuild, + bool hasCurrentBuild, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision) + { + std::string buildNumber; + if (hasCurrentBuildNumber && !currentBuildNumber.empty()) + { + buildNumber = currentBuildNumber; + } + else if (hasCurrentBuild && !currentBuild.empty()) + { + buildNumber = currentBuild; + } + else + { + buildNumber = std::to_string((long long)rtlBuildNumber); + } + + return formatWindowsOsFullVersion( + majorVersion, + minorVersion, + buildNumber, + updateBuildRevision, + hasUpdateBuildRevision); + } + /** * Get CommercialId from registry */ @@ -231,19 +264,20 @@ namespace PAL_NS_BEGIN { { osMajorVersion = std::to_string((long long)rtlOsvi.dwMajorVersion) + "." + std::to_string((long long)rtlOsvi.dwMinorVersion); - std::string buildNumber; - if (!getCurrentVersionStringValue("CurrentBuildNumber", buildNumber) && - !getCurrentVersionStringValue("CurrentBuild", buildNumber)) - { - buildNumber = std::to_string((long long)rtlOsvi.dwBuildNumber); - } - + std::string currentBuildNumber; + bool hasCurrentBuildNumber = getCurrentVersionStringValue("CurrentBuildNumber", currentBuildNumber); + std::string currentBuild; + bool hasCurrentBuild = hasCurrentBuildNumber ? false : getCurrentVersionStringValue("CurrentBuild", currentBuild); uint32_t updateBuildRevision = 0; bool hasUpdateBuildRevision = getCurrentVersionDwordValue("UBR", updateBuildRevision); - osFullVersion = formatWindowsOsFullVersion( + osFullVersion = getWindowsOsFullVersionFromSources( rtlOsvi.dwMajorVersion, rtlOsvi.dwMinorVersion, - buildNumber, + rtlOsvi.dwBuildNumber, + currentBuildNumber, + hasCurrentBuildNumber, + currentBuild, + hasCurrentBuild, updateBuildRevision, hasUpdateBuildRevision); } diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index 4cd9dcc39..b5b1339a3 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -35,6 +35,16 @@ namespace PAL_NS_BEGIN { std::string const& buildNumber, uint32_t updateBuildRevision, bool hasUpdateBuildRevision); + std::string getWindowsOsFullVersionFromSources( + unsigned long majorVersion, + unsigned long minorVersion, + unsigned long rtlBuildNumber, + std::string const& currentBuildNumber, + bool hasCurrentBuildNumber, + std::string const& currentBuild, + bool hasCurrentBuild, + uint32_t updateBuildRevision, + bool hasUpdateBuildRevision); } PAL_NS_END #endif @@ -119,6 +129,38 @@ TEST_F(PalTests, WindowsOsFullVersionUsesServicingBuildValues) PAL::formatWindowsOsFullVersion(10, 0, "26200", 0, false), Eq("10.0.26200")); } + +TEST_F(PalTests, WindowsOsFullVersionPrefersCurrentBuildNumber) +{ + EXPECT_THAT( + PAL::getWindowsOsFullVersionFromSources( + 10, 0, 19041, "26200", true, "22631", true, 1234, true), + Eq("10.0.26200.1234")); +} + +TEST_F(PalTests, WindowsOsFullVersionFallsBackToCurrentBuild) +{ + EXPECT_THAT( + PAL::getWindowsOsFullVersionFromSources( + 10, 0, 19041, "", false, "22631", true, 1234, true), + Eq("10.0.22631.1234")); +} + +TEST_F(PalTests, WindowsOsFullVersionFallsBackToRtlBuild) +{ + EXPECT_THAT( + PAL::getWindowsOsFullVersionFromSources( + 10, 0, 19041, "", false, "", false, 1234, true), + Eq("10.0.19041.1234")); +} + +TEST_F(PalTests, WindowsOsFullVersionOmitsMissingServicingBuildValue) +{ + EXPECT_THAT( + PAL::getWindowsOsFullVersionFromSources( + 10, 0, 19041, "26200", true, "22631", true, 0, false), + Eq("10.0.26200")); +} #endif TEST_F(PalTests, MonotonicTime) From ce4306031dca121d0e90aa05dd05aa08b169368c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 20 May 2026 18:50:46 -0500 Subject: [PATCH 3/3] Cover zero Windows servicing build revision Add a deterministic PAL test proving that UBR value 0 is still included when the source reports it as present. This keeps zero-value handling separate from the missing-UBR fallback. Files changed: - tests/unittests/PalTests.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unittests/PalTests.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index b5b1339a3..f608e269d 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -161,6 +161,14 @@ TEST_F(PalTests, WindowsOsFullVersionOmitsMissingServicingBuildValue) 10, 0, 19041, "26200", true, "22631", true, 0, false), Eq("10.0.26200")); } + +TEST_F(PalTests, WindowsOsFullVersionIncludesZeroServicingBuildValueWhenPresent) +{ + EXPECT_THAT( + PAL::getWindowsOsFullVersionFromSources( + 10, 0, 19041, "26200", true, "22631", true, 0, true), + Eq("10.0.26200.0")); +} #endif TEST_F(PalTests, MonotonicTime)