Переглянути джерело

Switch detection of Windows version to access WMI (#16967)

* Switch detection of Windows version to access WMI

Previously the Windows was queried using GetVersionEx which only returns
Windows 8 for any version of Windows 8 or newer.

The new method uses a WQL query to access Win32_OperatingSystem class
for the current version information of the OS

The output now looks like the following when run on a Windows 10 OS
"Microsoft Windows 10 Enterprise|C:\Windows|\Device\Harddisk0\Partition3 10.0.19045"

The previous output for a Windows 10 machine was
"Windows 8 6.2 (C:\Windows)"

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Added error handling to the querying of the Windows version using WMI

As a fallback, the `GetVersionEx` function is used if querying the
Windows version information fails throug using WQL.

Signed-off-by: lumberyard-employee-dm <[email protected]>

* Fixed formatting and typo issues in LogFile.cpp

Signed-off-by: lumberyard-employee-dm <[email protected]>

---------

Signed-off-by: lumberyard-employee-dm <[email protected]>
lumberyard-employee-dm 1 рік тому
батько
коміт
b6f50c7abd
1 змінених файлів з 214 додано та 81 видалено
  1. 214 81
      Code/Editor/LogFile.cpp

+ 214 - 81
Code/Editor/LogFile.cpp

@@ -32,6 +32,9 @@
 #if !defined(AZ_PLATFORM_WINDOWS)
 #if !defined(AZ_PLATFORM_WINDOWS)
 #include <time.h>
 #include <time.h>
 #include <QTime>
 #include <QTime>
+#else
+#include <Wbemidl.h>
+#pragma comment(lib, "wbemuuid.lib")
 #endif
 #endif
 
 
 #if defined(AZ_PLATFORM_MAC)
 #if defined(AZ_PLATFORM_MAC)
@@ -178,6 +181,215 @@ void CLogFile::FormatLineV(const char * format, va_list argList)
     CryLog("%s", szBuffer);
     CryLog("%s", szBuffer);
 }
 }
 
 
+namespace LogFileInternal
+{
+#if defined(AZ_PLATFORM_WINDOWS)
+    // Stores information about the OS queried from WMI
+    struct OSInfo
+    {
+        // Stores the Name property from Win32_OperatingSystem
+        AZStd::string m_name;
+        // Stores the Version property from Win32_OperatingSystem
+        AZStd::string m_version;
+    };
+
+    // This uses the GetVersionEx API which was deprecated in Windows 10
+    // as a fall back to query the version information for Windows
+    // On Windows 10 and after, this always returns the version as 6.2
+    OSInfo QueryOSInfoUsingGetVersionEx()
+    {
+        OSInfo osInfo;
+
+        OSVERSIONINFO osVersionInfo;
+        osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+
+        AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option")
+            GetVersionEx(&osVersionInfo);
+        AZ_POP_DISABLE_WARNING;
+
+        // Default the name of the operating system to just Windows as the version information
+        // is based on the manifest at the time application was built, which probably
+        // does not match the current version of Windows that is running
+        osInfo.m_name = "Windows";
+        osInfo.m_version = AZStd::string::format("%ld.%ld", osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion);
+        return osInfo;
+    }
+
+    // This specifically avoids using the GetVersion/GetVersionEx WinAPI
+    // functions because those will only return the version of Windows
+    // that was manifested at the time the application was built
+    // and not the version of Windows that is currently running
+    // https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversion
+    AZ::Outcome<OSInfo, AZStd::string> QueryOSInfoUsingWMI()
+    {
+        // Get the thread Id as an numeric value
+        static_assert(sizeof(AZStd::thread_id) <= sizeof(uintptr_t), "Thread Id should less than a size of a pointer");
+        // Using curly braces to zero initalize all bytes the uintptr_t
+        // If the thread Id is < sizeof(uintptr_t), the high bytes would be zeroed out
+        uintptr_t numericThreadId{};
+        *reinterpret_cast<AZStd::thread_id*>(&numericThreadId) = AZStd::this_thread::get_id();
+
+        OSInfo osInfo;
+        // Query the Windows version using WMI
+        HRESULT hResult = CoInitialize(nullptr);
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(AZStd::string::format(
+                "Failed to initialize the Com library on thread %#" PRIxPTR ": %lu", numericThreadId, static_cast<unsigned long>(hResult)));
+        }
+
+        // Obtain the initial locator to Windows Management on a particular host computer.
+        IWbemLocator* locator = nullptr;
+        hResult = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&locator);
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(
+                AZStd::string::format("Failed to create the Windows Management COM class: %lu", static_cast<unsigned long>(hResult)));
+        }
+        // Connect to the root\cimv2 namespace with the current user and obtain pointer pSvc to make IWbemServices calls.
+        IWbemServices* services = nullptr;
+        auto serverPath = SysAllocString(TEXT(R"(ROOT\CIMV2)"));
+        hResult = locator->ConnectServer(serverPath, nullptr, nullptr, 0, 0, nullptr, nullptr, &services);
+        SysFreeString(serverPath);
+
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(
+                AZStd::string::format("Could not connect the WMI on the local machine: %lu", static_cast<unsigned long>(hResult)));
+        }
+
+        // Set the IWbemServices proxy so that impersonation of the user (client) occurs.
+        hResult = CoSetProxyBlanket(
+            services,
+            RPC_C_AUTHN_WINNT,
+            RPC_C_AUTHZ_NONE,
+            nullptr,
+            RPC_C_AUTHN_LEVEL_CALL,
+            RPC_C_IMP_LEVEL_IMPERSONATE,
+            nullptr,
+            EOAC_NONE);
+
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(
+                AZStd::string::format("Cannot impersonate current user for proxy call: %lu", static_cast<unsigned long>(hResult)));
+        }
+
+        IEnumWbemClassObject* classObjectEnumerator = nullptr;
+
+        // query the Name and Version property from the Win32_OperatingSystem class
+        // https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem
+        AZStd::wstring propertyQuery{ L"SELECT Name,Version FROM Win32_OperatingSystem" };
+
+        // ExecQuery expects BSTR types
+        auto query = SysAllocString(propertyQuery.c_str());
+        auto language = SysAllocString(L"WQL");
+        hResult =
+            services->ExecQuery(language, query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, &classObjectEnumerator);
+        SysFreeString(language);
+        SysFreeString(query);
+
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(AZStd::string::format(
+                "WQL query from Win32_OperatingSystem WMI class has failed: %lu", static_cast<unsigned long>(hResult)));
+        }
+
+        // prepare to enumerate the object, blocking until the objects are ready
+        IWbemClassObject* classObject = nullptr;
+        ULONG numResults = 0;
+        hResult = classObjectEnumerator->Next(WBEM_INFINITE, 1, &classObject, &numResults);
+
+        if (FAILED(hResult))
+        {
+            return AZ::Failure(AZStd::string::format(
+                "Enumerating the CIM objects has failed with result code: %lu", static_cast<unsigned long>(hResult)));
+        }
+        else if (classObject == nullptr || numResults == 0)
+        {
+            return AZ::Failure(AZStd::string(
+                "There are no CIM objects found when querying the Win32_OperatingSystem class"));
+        }
+
+        constexpr const wchar_t* namePropertyName{ L"Name" };
+        constexpr const wchar_t* versionPropertyName{ L"Version" };
+        // get the class object's property value
+        VARIANT propertyValue;
+        // Query the Name field
+        if (FAILED(classObject->Get(namePropertyName, 0, &propertyValue, nullptr, nullptr)))
+        {
+            return AZ::Failure(AZStd::string::format(
+                R"(Could not retrieve the ")" AZ_TRAIT_FORMAT_STRING_PRINTF_WSTRING R"(" property from the CIM object: %lu)", namePropertyName, static_cast<unsigned long>(hResult)));
+        }
+
+        // If the name is a binary string copy it over
+        if ((propertyValue.vt & VT_BSTR) == VT_BSTR)
+        {
+            AZStd::to_string(osInfo.m_name, V_BSTR(&propertyValue));
+        }
+        // VariantClear must be called on the variant retrieved from `IWbemClassObject::Get`
+        // according to the documentation:
+        // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-get
+        VariantClear(&propertyValue);
+
+        // Query the Version field
+        if (FAILED(classObject->Get(versionPropertyName, 0, &propertyValue, nullptr, nullptr)))
+        {
+            {
+                return AZ::Failure(AZStd::string::format(
+                    R"(Could not retrieve the ")" AZ_TRAIT_FORMAT_STRING_PRINTF_WSTRING R"(" property from the CIM object: %lu)", versionPropertyName, static_cast<unsigned long>(hResult)));
+            }
+        }
+
+        // If the name is a binary string copy it over
+        if ((propertyValue.vt & VT_BSTR) == VT_BSTR)
+        {
+            AZStd::to_string(osInfo.m_version, V_BSTR(&propertyValue));
+        }
+        // VariantClear must be called on the variant retrieved from `IWbemClassObject::Get`
+        // according to the documentation:
+        // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-get
+        VariantClear(&propertyValue);
+
+        if (classObject)
+        {
+            classObject->Release();
+        }
+
+        if (classObjectEnumerator)
+        {
+            classObjectEnumerator->Release();
+        }
+
+        if (services)
+        {
+            services->Release();
+        }
+
+        if (locator)
+        {
+            locator->Release();
+        }
+
+        CoUninitialize();
+
+        return osInfo;
+    }
+
+    OSInfo QueryOSInfo()
+    {
+        auto queryOSInfoOutcome = QueryOSInfoUsingWMI();
+        if (!queryOSInfoOutcome)
+        {
+            CryLog("Failed to query Windows version info using WMI with error: %s.\n"
+                "Falling back to using GetVersionEx", queryOSInfoOutcome.GetError().c_str());
+            return QueryOSInfoUsingGetVersionEx();
+        }
+        return queryOSInfoOutcome.GetValue();
+    }
+
+#endif
+}
 
 
 void CLogFile::AboutSystem()
 void CLogFile::AboutSystem()
 {
 {
@@ -193,8 +405,6 @@ void CLogFile::AboutSystem()
 #if defined(AZ_PLATFORM_WINDOWS)
 #if defined(AZ_PLATFORM_WINDOWS)
     wchar_t szLanguageBufferW[64];
     wchar_t szLanguageBufferW[64];
     DEVMODE DisplayConfig;
     DEVMODE DisplayConfig;
-    OSVERSIONINFO OSVerInfo;
-    OSVerInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
 
 
     //////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////
     // Display editor and Windows version
     // Display editor and Windows version
@@ -217,90 +427,13 @@ void CLogFile::AboutSystem()
 
 
 #if defined(AZ_PLATFORM_WINDOWS)
 #if defined(AZ_PLATFORM_WINDOWS)
     // Format and send OS version line
     // Format and send OS version line
-    QString str = "Windows ";
-
-AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option")
-    GetVersionEx(&OSVerInfo);
-AZ_POP_DISABLE_WARNING
-
-    if (OSVerInfo.dwMajorVersion == 4)
-    {
-        if (OSVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
-        {
-            if (OSVerInfo.dwMinorVersion > 0)
-            {
-                // Windows 98
-                str += "98";
-            }
-            else
-            {
-                // Windows 95
-                str += "95";
-            }
-        }
-        else
-        {
-            // Windows NT
-            str += "NT";
-        }
-    }
-    else if (OSVerInfo.dwMajorVersion == 5)
-    {
-        if (OSVerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
-        {
-            // Windows Millennium
-            str += "ME";
-        }
-        else
-        {
-            if (OSVerInfo.dwMinorVersion > 0)
-            {
-                // Windows XP
-                str += "XP";
-            }
-            else
-            {
-                // Windows 2000
-                str += "2000";
-            }
-        }
-    }
-    else if (OSVerInfo.dwMajorVersion == 6)
-    {
-        if (OSVerInfo.dwMinorVersion == 0)
-        {
-            // Windows Vista
-            str += "Vista";
-        }
-        else if (OSVerInfo.dwMinorVersion == 1)
-        {
-            // Windows 7
-            str += "7";
-        }
-        else if (OSVerInfo.dwMinorVersion == 2)
-        {
-            // Windows 8
-            str += "8";
-        }
-        else
-        {
-            // Windows unknown (newer than Win8)
-            str += "Version Unknown";
-        }
-    }
-    azsnprintf(szBuffer, MAX_LOGBUFFER_SIZE, " %ld.%ld", OSVerInfo.dwMajorVersion, OSVerInfo.dwMinorVersion);
-    str += szBuffer;
+    auto osInfo = LogFileInternal::QueryOSInfo();
+    QString str = QString("%1 %2").arg(osInfo.m_name.c_str()).arg(osInfo.m_version.c_str());
 
 
     //////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////
     // Show Windows directory
     // Show Windows directory
     //////////////////////////////////////////////////////////////////////
     //////////////////////////////////////////////////////////////////////
 
 
-    str += " (";
-    wchar_t szBufferW[MAX_LOGBUFFER_SIZE];
-    GetWindowsDirectoryW(szBufferW, sizeof(szBufferW));
-    AZStd::to_string(szBuffer, MAX_LOGBUFFER_SIZE, szBufferW);
-    str += szBuffer;
-    str += ")";
     CryLog("%s", str.toUtf8().data());
     CryLog("%s", str.toUtf8().data());
 #elif defined(AZ_PLATFORM_LINUX)
 #elif defined(AZ_PLATFORM_LINUX)
     // TODO: Add more detail about the current Linux Distro
     // TODO: Add more detail about the current Linux Distro