From 5e7bf1719b6e172993472dffaa1242909804f10c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 18 May 2026 17:29:35 -0500 Subject: [PATCH 1/4] 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/4] 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/4] 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) From 78e6db48d98269f8a1abd2b14e0d645097e0e532 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 22 May 2026 18:33:41 -0500 Subject: [PATCH 4/4] Use RtlGetVersion build number; combine with registry UBR Per Windows-team guidance on issue #1407: the RtlGetVersion() API is the authoritative source for the running OS build number, and the registry CurrentBuildNumber / CurrentBuild values are only useful for offline reads of another Windows installation. For the running OS the API and registry are always in sync, so prefer the API. UBR is registry-only and is always safe to combine with the kernel-provided build number. Drop the registry CurrentBuildNumber / CurrentBuild precedence chain and the now-unused getCurrentVersionStringValue helper. Format the OS full version directly from rtlOsvi.dwBuildNumber plus the optional UBR. Update PalTests to cover the simplified formatter (UBR present, UBR missing, zero UBR present), replacing the obsolete source-precedence tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../WindowsDesktopSystemInformationImpl.cpp | 76 +++---------------- tests/unittests/PalTests.cpp | 53 ++----------- 2 files changed, 19 insertions(+), 110 deletions(-) diff --git a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp index f5b41e445..8bc6b1147 100644 --- a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp +++ b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp @@ -142,26 +142,6 @@ namespace PAL_NS_BEGIN { const PCSTR c_currentVersion_Key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; - bool getCurrentVersionStringValue(PCSTR valueName, std::string& value) - { - char buff[MAX_PATH] = { 0 }; - DWORD size = sizeof(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; @@ -185,11 +165,13 @@ namespace PAL_NS_BEGIN { std::string formatWindowsOsFullVersion( unsigned long majorVersion, unsigned long minorVersion, - std::string const& buildNumber, + unsigned long buildNumber, uint32_t updateBuildRevision, bool hasUpdateBuildRevision) { - std::string version = std::to_string(majorVersion) + "." + std::to_string(minorVersion) + "." + buildNumber; + std::string version = std::to_string(majorVersion) + "." + + std::to_string(minorVersion) + "." + + std::to_string(static_cast(buildNumber)); if (hasUpdateBuildRevision) { version += "." + std::to_string(updateBuildRevision); @@ -198,39 +180,6 @@ 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 */ @@ -264,20 +213,19 @@ namespace PAL_NS_BEGIN { { osMajorVersion = std::to_string((long long)rtlOsvi.dwMajorVersion) + "." + std::to_string((long long)rtlOsvi.dwMinorVersion); - std::string currentBuildNumber; - bool hasCurrentBuildNumber = getCurrentVersionStringValue("CurrentBuildNumber", currentBuildNumber); - std::string currentBuild; - bool hasCurrentBuild = hasCurrentBuildNumber ? false : getCurrentVersionStringValue("CurrentBuild", currentBuild); + // 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 = getWindowsOsFullVersionFromSources( + osFullVersion = formatWindowsOsFullVersion( rtlOsvi.dwMajorVersion, rtlOsvi.dwMinorVersion, rtlOsvi.dwBuildNumber, - currentBuildNumber, - hasCurrentBuildNumber, - currentBuild, - hasCurrentBuild, updateBuildRevision, hasUpdateBuildRevision); } diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index f608e269d..0bf8a06a3 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -32,17 +32,7 @@ namespace PAL_NS_BEGIN { std::string formatWindowsOsFullVersion( unsigned long majorVersion, unsigned long minorVersion, - 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, + unsigned long buildNumber, uint32_t updateBuildRevision, bool hasUpdateBuildRevision); } PAL_NS_END @@ -120,53 +110,24 @@ TEST_F(PalTests, FormatUtcTimestampMsAsISO8601) } #if defined(_WIN32) || defined(_WIN64) -TEST_F(PalTests, WindowsOsFullVersionUsesServicingBuildValues) +TEST_F(PalTests, WindowsOsFullVersionIncludesUbrWhenPresent) { EXPECT_THAT( - PAL::formatWindowsOsFullVersion(10, 0, "26200", 1234, true), + 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")); -} - -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) +TEST_F(PalTests, WindowsOsFullVersionOmitsUbrWhenMissing) { EXPECT_THAT( - PAL::getWindowsOsFullVersionFromSources( - 10, 0, 19041, "26200", true, "22631", true, 0, false), + PAL::formatWindowsOsFullVersion(10, 0, 26200, 0, false), Eq("10.0.26200")); } -TEST_F(PalTests, WindowsOsFullVersionIncludesZeroServicingBuildValueWhenPresent) +TEST_F(PalTests, WindowsOsFullVersionIncludesZeroUbrWhenPresent) { EXPECT_THAT( - PAL::getWindowsOsFullVersionFromSources( - 10, 0, 19041, "26200", true, "22631", true, 0, true), + PAL::formatWindowsOsFullVersion(10, 0, 26200, 0, true), Eq("10.0.26200.0")); } #endif