List of IDispatch errors and/or message texts

8.8k views Asked by At

I am calling methods on WMI/WBEM interfaces that return HRESULTS. I want to display meaningful error messages for these error codes to the user. However, when I look up the HRESULT's error message I only get strings like "IDispatch error #3598".

Were can I find a list of these IDispatch error codes that explains their meaning?

Example code where errors may occur:

IWbemLocator *pLocator = NULL;
IWbemServices *pNamespace = NULL;
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLocator);
if (FAILED(hr))
   return hr;

hr = pLocator->ConnectServer(wPath, NULL, NULL, NULL, 0, NULL, NULL, &pNamespace);
if(FAILED(hr))
   return hr;

Error lookup:

CString sMessage = _com_error(nError).ErrorMessage();

// sMessage now contains a string like "IDispatch error #3598"

Note: This does not help - it does not contain the HRESULTS I get. Neither are they contained in winerror.h.

2

There are 2 answers

2
Hans Passant On BEST ANSWER

COM servers can generate their own HRESULT error codes. The IErrorInfo interface helps a client to get a description of the error. You are not giving the _com_error class a chance to do that job, you don't pass the IErrorInfo interface pointer to the constructor.

First QI the interface for ISupportErrorInfo and call its InterfaceSupportsErrorInfo() method to verify that error reporting is supported. Next call GetErrorInfo() to obtain the IErrorInfo interface pointer. MSDN docs are here.

0
OwnageIsMagic On

That WMI implementation doesn't support common convention with ISupportErrorInfo. Check error facility, if it is interface error (FACILITY_ITF) then call GetErrorInfo and QI That IErrorInfo to IWbemClassObject. From that object you can Get() some detailed info, but it probably won't describe error reason.

  string Description;
  string Operation;
  string ParameterInfo;
  string ProviderName;
  uint32 StatusCode;

https://learn.microsoft.com/en-us/windows/win32/wmisdk/retrieving-an-error-code#handling-an-error-using-c

To get meaningful message you can call FormatMessage and specify C:\Windows\System32\wbem\wmiutils.dll as the message module. https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-error-constants

OR

Use IWbemStatusCodeText::GetErrorCodeText/GetFacilityCodeText. That interface should return the same message but I don't know for sure.

Here is the code

void CheckErrorWMI(HRESULT orig_hres/*, IUnknown* unk, REFIID iid*/)
{
    if (SUCCEEDED(orig_hres))
        return;

    if (HRESULT_FACILITY(orig_hres) != FACILITY_ITF)
    {
        // Common error handling

        // does the magic ISupportErrorInfo dance for us and raise _com_error
        //_com_issue_errorex(orig_hres, unk, iid);
        // but it's not interface error so, it won't help much
        // -OR-
        // use stdlib (msvc STL will pass orig_hres to FormatMessage)
        throw std::system_error(orig_hres, std::system_category(), "some text");
    }
    else
    {
        // WMI specific error handling

        std::string str = "HRESULT = ";
        str += std::to_string(orig_hres); // TODO: as hex
        str += '\n';

        HRESULT hres;

        if (IErrorInfoPtr errInfo; SUCCEEDED(hres = GetErrorInfo(0, &errInfo))
            && errInfo && reinterpret_cast<intptr_t>(errInfo.GetInterfacePtr()) != -1)
            if (IWbemClassObjectPtr errObj; SUCCEEDED(errInfo.QueryInterface(IID_IWbemClassObject, &errObj)))
            {
                SAFEARRAY* sfArray{};

                hres = errObj->GetNames(nullptr, WBEM_FLAG_NONSYSTEM_ONLY/*WBEM_FLAG_ALWAYS*/, nullptr, &sfArray);
                if (SUCCEEDED(hres))
                {
                    DEFER{ SafeArrayDestroy(sfArray); };

                    LONG lstart, lend;
                    SafeArrayGetLBound(sfArray, 1, &lstart);
                    SafeArrayGetUBound(sfArray, 1, &lend);

                    BSTR* pbstr{};
                    hres = SafeArrayAccessData(sfArray, reinterpret_cast<void**>(&pbstr));
                    if (SUCCEEDED(hres))
                    {
                        DEFER{ SafeArrayUnaccessData(sfArray); };

                        VARIANT var;

                        for (LONG nIdx = lstart; nIdx <= lend; nIdx++)
                        {
                            hres = errObj->Get(pbstr[nIdx], 0, &var, nullptr, nullptr);

                            if (FAILED(hres))
                                continue;

                            DEFER{ VariantClear(&var); };

                            if (!str.empty())
                                str += '\n';
                            str += utf8_encode(pbstr[nIdx]);
                            str += " = ";
                            if (V_VT(&var) == VT_NULL)
                            {
                                str += "<null>";
                                continue;
                            }

                            hres = V_VT(&var) == VT_BSTR ? S_OK : VariantChangeType(&var, &var, VARIANT_ALPHABOOL, VT_BSTR);
                            if (hres != S_OK)
                                continue;

                            if (BSTR ss = V_BSTR(&var))
                                str += utf8_encode(ss);
                        }
                    }
                }
            }

        if (IWbemStatusCodeTextPtr sct; SUCCEEDED(sct.CreateInstance(CLSID_WbemStatusCodeText, nullptr, CLSCTX_INPROC_SERVER)))
        {
            _bstr_t msg;
            // https://referencesource.microsoft.com/#System.Management/managementexception.cs,747 GetMessage(ManagementStatus errorCode)
            hres = sct->GetErrorCodeText(orig_hres, 0, 1, msg.GetAddress()); // pass 1 to lFlags to not append \r\n (from .Net interop impl)
            if (hres != WBEM_S_NO_ERROR) // Just in case it didn't like the flag=1, try it again with flag=0.
                hres = sct->GetErrorCodeText(orig_hres, 0, 0, msg.GetAddress());
            if (SUCCEEDED(hres))
            {
                if (!str.empty())
                    str += "\n\n";
                str += "Message: ";
                str += utf8_encode(msg.GetBSTR());
            }

            hres = sct->GetFacilityCodeText(orig_hres, 0, 0, msg.GetAddress());
            if (SUCCEEDED(hres))
            {
                str += "Facility: ";
                str += utf8_encode(msg.GetBSTR());
            }
        }
        if (str.size() <= 22) // just "HRESULT = 123", probably not WMI error
            _com_raise_error(orig_hres); // raise _com_error

        throw std::runtime_error(str);
    }
}

DEFER is just ScopeGuard macros (like Boost.ScopeExit, etc)

Output:

HRESULT = -2147217385

Description = <null>
Operation = ExecQuery
ParameterInfo = SELECT 1+8 FROM Win32_PnPAllocatedResource
ProviderName = WinMgmt
StatusCode = <null>

Message: Query was not syntactically valid. Facility: WMI