diff --git a/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopSystemInformationImpl.cpp index d910f99e7..f5b41e445 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,95 @@ 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; + } + + 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); } /** @@ -175,8 +254,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 +263,23 @@ 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 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 = getWindowsOsFullVersionFromSources( + 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 15bb98e5e..f608e269d 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,27 @@ 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); + 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 + class PalTests : public Test {}; TEST_F(PalTests, UuidGeneration) @@ -95,6 +119,58 @@ 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")); +} + +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")); +} + +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) { int64_t t0 = PAL::getMonotonicTimeMs();