/*++ Copyright (c) 1998-2000 Microsoft Corporation Module Name: utils.cpp Abstract: General purpose utilities Author: ??? Revision History: Mohit Srivastava 18-Dec-00 --*/ #include "iisprov.h" #include "iiswmimsg.h" extern CDynSchema* g_pDynSch; extern HMODULE g_hModule; BSTR CUtils::ExtractBstrFromVt( const VARIANT* i_pvt, LPCWSTR i_wszVtName) // default(NULL) /*++ Synopsis: This is different from VARAINT::ChangeType in that it handles conversion from VT_NULL also. Arguments: [i_pvt] - Return Value: BSTR: If non-null, points to i_pvt->bstrVal --*/ { DBG_ASSERT(i_pvt != NULL); switch(i_pvt->vt) { case VT_BSTR: return i_pvt->bstrVal; case VT_NULL: return NULL; default: CIIsProvException e; e.SetHR(DISP_E_TYPEMISMATCH, i_wszVtName); throw e; } return NULL; } LONG CUtils::ExtractLongFromVt( const VARIANT* i_pvt, LPCWSTR i_wszVtName) //default(NULL) { DBG_ASSERT(i_pvt); try { _variant_t svt; svt = *i_pvt; return (long)svt; } catch(_com_error ce) { CIIsProvException e; e.SetHR(ce.Error(), i_wszVtName); throw e; } } bool CUtils::CompareKeyType( LPCWSTR i_wszKeyFromMb, METABASE_KEYTYPE* i_pktKeyCompare) { DBG_ASSERT(i_wszKeyFromMb); DBG_ASSERT(i_pktKeyCompare); if(!i_pktKeyCompare->m_pszName) { return false; } if(_wcsicmp(i_wszKeyFromMb, i_pktKeyCompare->m_pszName) == 0) { return true; } // // If i_wszKeyFromMb is not in our hashtable and i_wszKeyCompare is // IIsObject, treat as a match. // METABASE_KEYTYPE* pKt = NULL; HRESULT hr = g_pDynSch->GetHashKeyTypes()->Wmi_GetByKey(i_wszKeyFromMb, &pKt); if( FAILED(hr) && i_pktKeyCompare == &METABASE_KEYTYPE_DATA::s_IIsObject ) { return true; } return false; } bool CUtils::CompareMultiSz( WCHAR* i_msz1, WCHAR* i_msz2 ) { if(i_msz1 == NULL && i_msz2 == NULL) return true; else if(i_msz1 == NULL || i_msz2 == NULL) return false; // compare the two multisz buffers. for ( ; (*i_msz1 && *i_msz2); ) { if (_wcsicmp(i_msz1, i_msz2) != NULL) return false; i_msz1 += wcslen(i_msz1) + 1; i_msz2 += wcslen(i_msz2) + 1; } if (!*i_msz1 && !*i_msz2) { return true; } return false; } HRESULT CUtils::LoadSafeArrayFromByteArray( LPBYTE i_aBytes, DWORD i_iBytes, _variant_t& io_vt ) { DBG_ASSERT(i_aBytes != NULL); HRESULT hr = S_OK; SAFEARRAY* pSafeArray = NULL; SAFEARRAYBOUND safeArrayBounds[1]; safeArrayBounds[0].lLbound = 0; safeArrayBounds[0].cElements = i_iBytes; pSafeArray = SafeArrayCreate(VT_UI1, 1, safeArrayBounds); if(pSafeArray == NULL) { hr = WBEM_E_OUT_OF_MEMORY; goto exit; } for(ULONG idx = 0; idx < i_iBytes; idx++) { hr = SafeArrayPutElement(pSafeArray, (LONG *)&idx, &i_aBytes[idx]); if(FAILED(hr)) { goto exit; } } // // If everything succeeded, set out parameters. // io_vt.vt = VT_UI1 | VT_ARRAY; io_vt.parray = pSafeArray; exit: if(FAILED(hr)) { if(pSafeArray != NULL) { SafeArrayDestroy(pSafeArray); } } return hr; } // // CreateByteArrayFromSafeArray // HRESULT CUtils::CreateByteArrayFromSafeArray( _variant_t& i_vt, LPBYTE* o_paBytes, DWORD* io_pdw ) { DBG_ASSERT(i_vt.vt == (VT_ARRAY | VT_UI1)); DBG_ASSERT(o_paBytes != NULL); DBG_ASSERT(io_pdw != NULL); if(i_vt.parray == NULL) { *o_paBytes = NULL; *io_pdw = 0; } HRESULT hr = S_OK; LONG iLo = 0; LONG iUp = 0; LPBYTE aBytes = NULL; hr = SafeArrayGetLBound(i_vt.parray,1,&iLo); if(FAILED(hr)) { goto exit; } hr = SafeArrayGetUBound(i_vt.parray,1,&iUp); if(FAILED(hr)) { goto exit; } aBytes = new BYTE[iUp-iLo+1]; if(aBytes == NULL) { hr = WBEM_E_OUT_OF_MEMORY; goto exit; } for(LONG i = iLo; i <= iUp; i++) { hr = SafeArrayGetElement(i_vt.parray, &i, &aBytes[i-iLo]); if(FAILED(hr)) { goto exit; } } // // If everything succeeded, set out parameters. // *o_paBytes = aBytes; *io_pdw = iUp-iLo+1; exit: if(FAILED(hr)) { delete [] aBytes; } return hr; } bool CUtils::CompareByteArray( LPBYTE i_aBytes1, ULONG i_iBytes1, LPBYTE i_aBytes2, ULONG i_iBytes2 ) { if(i_aBytes1 == NULL && i_aBytes2 == NULL) { return true; } if(i_aBytes1 == NULL || i_aBytes2 == NULL) { return false; } if(i_iBytes1 != i_iBytes2) { return false; } for(ULONG i = 0; i < i_iBytes1; i++) { if(i_aBytes1[i] != i_aBytes2[i]) { return false; } } return true; } KeyRef* CUtils::GetKey( ParsedObjectPath* i_pParsedObjectPath, WCHAR* i_wsz ) /*++ Synopsis: Return the KeyRef pointer from the ParsedObjectPath for the given string. Arguments: [i_pParsedObjectPath] - [i_wsz] - Return Value: --*/ { KeyRef* pkr; DWORD numkeys = i_pParsedObjectPath->m_dwNumKeys; DWORD c; if(numkeys == 1) { pkr = *(i_pParsedObjectPath->m_paKeys); if(pkr->m_pName == NULL) { return pkr; } } for ( c=0; numkeys; numkeys--,c++ ) { pkr = *(i_pParsedObjectPath->m_paKeys + c); if (!_wcsicmp(pkr->m_pName,i_wsz)) return pkr; } CIIsProvException e; e.SetMC(WBEM_E_INVALID_OBJECT, IISWMI_NO_PRIMARY_KEY, i_wsz); throw e; } bool CUtils::GetAssociation( LPCWSTR i_wszAssocName, WMI_ASSOCIATION** o_ppAssoc ) /*++ Synopsis: Association i_wszAssocName is returned in o_ppAssoc if found. Arguments: [i_wszAssocName] - [o_ppAssoc] - Return Value: true if found false otherwise --*/ { DBG_ASSERT(o_ppAssoc != NULL); HRESULT hr; hr = g_pDynSch->GetHashAssociations()->Wmi_GetByKey( (LPWSTR)i_wszAssocName, o_ppAssoc); if(SUCCEEDED(hr)) { return true; } else { return false; } } bool CUtils::GetClass( LPCWSTR i_wszClassName, WMI_CLASS** o_ppClass ) /*++ Synopsis: Class i_wszClassName is returned in o_ppClass if found. Arguments: [i_wszClassName] - [o_ppClass] - Return Value: true if found false otherwise --*/ { DBG_ASSERT(o_ppClass != NULL); HRESULT hr; hr = g_pDynSch->GetHashClasses()->Wmi_GetByKey( (LPWSTR)i_wszClassName, o_ppClass); if(SUCCEEDED(hr)) { return true; } else { return false; } } bool CUtils::GetMethod( LPCWSTR i_wszMethod, WMI_METHOD** i_apMethodList, WMI_METHOD** o_ppMethod ) /*++ Synopsis: The Method descriptor for i_wszMethod is returned via o_ppMethod if found Arguments: [i_wszMethod] - [i_apMethodList] - [o_ppMethod] - Return Value: true if found. false otherwise. --*/ { DBG_ASSERT(i_wszMethod != NULL); DBG_ASSERT(o_ppMethod != NULL); WMI_METHOD** ppmethod; if(i_apMethodList == NULL) { return false; } for (ppmethod = i_apMethodList; *ppmethod != NULL;ppmethod++) { if (_wcsicmp(i_wszMethod,(*ppmethod)->pszMethodName) ==0) { *o_ppMethod = *ppmethod; return true; } } return false; } HRESULT CUtils::ConstructObjectPath( LPCWSTR i_wszMbPath, const WMI_CLASS* i_pClass, BSTR* o_pbstrPath) { DBG_ASSERT(i_wszMbPath != NULL); DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(o_pbstrPath != NULL); DBG_ASSERT(*o_pbstrPath == NULL); CComBSTR sbstrPath; ULONG cchPrefix = wcslen(i_pClass->pszMetabaseKey); DBG_ASSERT(cchPrefix <= wcslen(i_wszMbPath)); LPCWSTR wszSuffix = &i_wszMbPath[cchPrefix]; if(wszSuffix[0] == L'/') { wszSuffix++; } sbstrPath = i_pClass->pszClassName; if(sbstrPath.m_str == NULL) { return WBEM_E_OUT_OF_MEMORY; } sbstrPath += L"='"; if(sbstrPath.m_str == NULL) { return WBEM_E_OUT_OF_MEMORY; } sbstrPath += wszSuffix; if(sbstrPath.m_str == NULL) { return WBEM_E_OUT_OF_MEMORY; } sbstrPath += L"'"; if(sbstrPath.m_str == NULL) { return WBEM_E_OUT_OF_MEMORY; } *o_pbstrPath = sbstrPath.Detach(); return S_OK; } void CUtils::GetMetabasePath( IWbemClassObject* io_pObj, ParsedObjectPath* i_pParsedObjectPath, WMI_CLASS* i_pClass, _bstr_t& io_bstrPath) /*++ Synopsis: Populates io_bstrPath and sets the key field in IWbemClassObject Arguments: [io_pObj] - [i_pParsedObjectPath] - [i_pClass] - [io_bstrPath] - --*/ { KeyRef* pkr; LPWSTR wszWmiKey = i_pClass->pszKeyName; DBG_ASSERT(i_pParsedObjectPath != NULL); DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(wszWmiKey != NULL); DBG_ASSERT(i_pClass->pszMetabaseKey != NULL); pkr = GetKey(i_pParsedObjectPath, wszWmiKey); DBG_ASSERT(pkr != NULL); if (io_pObj) { _bstr_t bstr; if(pkr->m_pName == NULL) { bstr = wszWmiKey; } else { bstr = pkr->m_pName; } HRESULT hr = io_pObj->Put(bstr, 0, &pkr->m_vValue, 0); THROW_ON_ERROR(hr); } io_bstrPath = i_pClass->pszMetabaseKey; switch ((pkr)->m_vValue.vt) { case VT_I4: { WCHAR wszBuf[32] = {0}; io_bstrPath += L"/"; io_bstrPath += _itow(pkr->m_vValue.lVal, wszBuf, 10); break; } case VT_BSTR: { io_bstrPath += L"/"; io_bstrPath += pkr->m_vValue.bstrVal; break; } } return; } HRESULT CUtils::GetParentMetabasePath( LPCWSTR i_wszChildPath, LPWSTR io_wszParentPath) /*++ Synopsis: Eg. /LM/w3svc/1 => /LM/w3svc/ / => E_FAIL Arguments: [i_wszChildPath] - [io_wszParentPath] - Should be allocated by caller to at least same size as i_wszChildPath. Return Value: E_FAIL S_OK --*/ { DBG_ASSERT(i_wszChildPath != NULL); DBG_ASSERT(io_wszParentPath != NULL); ULONG cchChildPath = wcslen(i_wszChildPath); BOOL bParentFound = false; // // This should trim all the ending L'/' // while(cchChildPath > 0 && i_wszChildPath[cchChildPath-1] == L'/') { cchChildPath--; } if(cchChildPath <= 1) { // // does not have a parent // return E_FAIL; } for(LONG i = cchChildPath-1; i >= 0; i--) { if(i_wszChildPath[i] == L'/') { bParentFound = true; break; } } if(!bParentFound) { return E_FAIL; } memcpy(io_wszParentPath, i_wszChildPath, (i+1)*sizeof(WCHAR)); io_wszParentPath[i+1] = L'\0'; return S_OK; } void CUtils::Throw_Exception( HRESULT a_hr, METABASE_PROPERTY* a_pmbp ) { CIIsProvException t_e; t_e.SetHR(a_hr, a_pmbp->pszPropName); throw(t_e); } // // io_wszDateTime should be allocated outside with 30 elements // void CUtils::FileTimeToWchar(FILETIME *i_pFileTime, LPWSTR io_wszDateTime) { DBG_ASSERT(i_pFileTime != NULL); DBG_ASSERT(io_wszDateTime != NULL); SYSTEMTIME systime; if(FileTimeToSystemTime(i_pFileTime, &systime) == 0) { THROW_ON_ERROR(HRESULT_FROM_WIN32(GetLastError())); } swprintf( io_wszDateTime, L"%04d%02d%02d%02d%02d%02d.%06d+000", systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond, systime.wMilliseconds ); } // // Below this line, added by Mohit // HRESULT CUtils::CreateEmptyMethodInstance( CWbemServices* i_pNamespace, IWbemContext* i_pCtx, LPCWSTR i_wszClassName, LPCWSTR i_wszMethodName, IWbemClassObject** o_ppMethodInstance) /*++ Synopsis: Generally used when executing a WMI method that has out parameters. Arguments: --*/ { DBG_ASSERT(i_pNamespace != NULL); DBG_ASSERT(i_pCtx != NULL); DBG_ASSERT(i_wszClassName != NULL); DBG_ASSERT(i_wszMethodName != NULL); DBG_ASSERT(o_ppMethodInstance != NULL); CComPtr spClass; CComPtr spMethodClass; CComPtr spMethodInstance; HRESULT hr = S_OK; hr = i_pNamespace->GetObject(_bstr_t(i_wszClassName), 0, i_pCtx, &spClass, NULL); if(FAILED(hr)) { goto exit; } hr = spClass->GetMethod(i_wszMethodName, 0, NULL, &spMethodClass); if(FAILED(hr)) { goto exit; } hr = spMethodClass->SpawnInstance(0, &spMethodInstance); if(FAILED(hr)) { goto exit; } // // If everything succeeded, set out parameters // *o_ppMethodInstance = spMethodInstance; (*o_ppMethodInstance)->AddRef(); exit: return hr; } HRESULT CUtils::GetQualifiers( IWbemClassObject* i_pClass, LPCWSTR* i_awszQualNames, VARIANT* io_aQualValues, ULONG i_NrQuals ) /*++ Synopsis: Gets Qualifiers. Arguments: [i_pClass] - [i_awszQualNames] - An array of size i_NrQuals with names of quals. [io_aQualValues] - An array of size i_NrQuals with empty variants. Will be populated on success. [i_NrQuals] - --*/ { DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(i_awszQualNames != NULL); DBG_ASSERT(io_aQualValues != NULL); HRESULT hr = S_OK; ULONG i = 0; CComPtr spQualSet = NULL; hr = i_pClass->GetQualifierSet(&spQualSet); if(FAILED(hr)) { goto exit; } // Looking for qualifiers for(i = 0; i < i_NrQuals; i++) { DBG_ASSERT(i_awszQualNames[i] != NULL); hr = spQualSet->Get(i_awszQualNames[i], 0, &io_aQualValues[i], NULL); if(FAILED(hr) && hr != WBEM_E_NOT_FOUND) { break; } hr = WBEM_S_NO_ERROR; } if(FAILED(hr)) { for(i = 0; i < i_NrQuals; i++) { VariantClear(&io_aQualValues[i]); } if(FAILED(hr)) { goto exit; } } exit: return hr; } HRESULT CUtils::GetPropertyQualifiers( IWbemClassObject* i_pClass, LPCWSTR i_wszPropName, DWORD* io_pdwQuals) /*++ Synopsis: Unlike SetPropertyQualifiers, this method is specific to this provider. Arguments: --*/ { DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(i_wszPropName != NULL); DBG_ASSERT(io_pdwQuals != NULL); HRESULT hr = S_OK; CComPtr spQualSet = NULL; BSTR bstrQualName = NULL; VARIANT varQualValue; VariantInit(&varQualValue); DWORD dwQuals = 0; bool bSeenForcePropertyOverwrite = false; bool bSeenIsDefault = false; bool bSeenIsInherit = false; hr = i_pClass->GetPropertyQualifierSet(i_wszPropName, &spQualSet); if(FAILED(hr)) { goto exit; } // Looking for qualifiers spQualSet->BeginEnumeration(WBEM_FLAG_LOCAL_ONLY); while(!bSeenForcePropertyOverwrite || !bSeenIsDefault || !bSeenIsInherit) { hr = spQualSet->Next(0, &bstrQualName, &varQualValue, NULL); if(hr == WBEM_S_NO_MORE_DATA || FAILED(hr)) { // No more qualifiers. // We don't need to worry about cleanup - nothing was allocated. break; } if(!bSeenForcePropertyOverwrite && _wcsicmp(bstrQualName, g_wszForcePropertyOverwrite) == 0) { bSeenForcePropertyOverwrite = true; if(varQualValue.vt == VT_BOOL && varQualValue.boolVal) { dwQuals |= g_fForcePropertyOverwrite; } } else if(!bSeenIsDefault && _wcsicmp(bstrQualName, g_wszIsDefault) == 0) { bSeenIsDefault = true; if(varQualValue.vt == VT_BOOL && varQualValue.boolVal) { dwQuals |= g_fIsDefault; } } else if(!bSeenIsInherit && _wcsicmp(bstrQualName, g_wszIsInherit) == 0) { bSeenIsInherit = true; if(varQualValue.vt == VT_BOOL && varQualValue.boolVal) { dwQuals |= g_fIsInherit; } } SysFreeString(bstrQualName); VariantClear(&varQualValue); } spQualSet->EndEnumeration(); if(FAILED(hr)) { goto exit; } *io_pdwQuals = dwQuals; exit: if(hr == WBEM_S_NO_MORE_DATA) { hr = WBEM_S_NO_ERROR; } return hr; } HRESULT CUtils::SetQualifiers( IWbemClassObject* i_pClass, LPCWSTR* i_awszQualNames, VARIANT* i_avtQualValues, ULONG i_iNrQuals, ULONG i_iFlags) { DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(i_awszQualNames != NULL); DBG_ASSERT(i_avtQualValues != NULL); HRESULT hr = S_OK; CComPtr spQualSet = NULL; hr = i_pClass->GetQualifierSet(&spQualSet); if(FAILED(hr)) { goto exit; } for(ULONG i = 0; i < i_iNrQuals; i++) { DBG_ASSERT(i_awszQualNames[i] != NULL); hr = spQualSet->Put(i_awszQualNames[i], &i_avtQualValues[i], i_iFlags); if(FAILED(hr)) { goto exit; } } exit: return hr; } HRESULT CUtils::SetMethodQualifiers( IWbemClassObject* i_pClass, LPCWSTR i_wszMethName, LPCWSTR* i_awszQualNames, VARIANT* i_avtQualValues, ULONG i_iNrQuals) { DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(i_wszMethName != NULL); DBG_ASSERT(i_awszQualNames != NULL); DBG_ASSERT(i_avtQualValues != NULL); HRESULT hr = WBEM_S_NO_ERROR; CComPtr spQualSet; hr = i_pClass->GetMethodQualifierSet(i_wszMethName, &spQualSet); if(FAILED(hr)) { goto exit; } for(ULONG i = 0; i < i_iNrQuals; i++) { DBG_ASSERT(i_awszQualNames[i] != NULL); hr = spQualSet->Put(i_awszQualNames[i], &i_avtQualValues[i], WBEM_FLAVOR_FLAG_PROPAGATE_TO_INSTANCE); if(FAILED(hr)) { goto exit; } } exit: return hr; } HRESULT CUtils::SetPropertyQualifiers( IWbemClassObject* i_pClass, LPCWSTR i_wszPropName, LPCWSTR* i_awszQualNames, VARIANT* i_avtQualValues, ULONG i_iNrQuals) /*++ Synopsis: Arguments: [i_pClass] - [i_wszPropName] - [i_awszQualNames] - [i_avtQualValues] - [i_iNrQuals] - --*/ { DBG_ASSERT(i_pClass != NULL); DBG_ASSERT(i_wszPropName != NULL); DBG_ASSERT(i_awszQualNames != NULL); DBG_ASSERT(i_avtQualValues != NULL); HRESULT hr = S_OK; CComPtr spQualSet = NULL; hr = i_pClass->GetPropertyQualifierSet(i_wszPropName, &spQualSet); if(FAILED(hr)) { goto exit; } for(ULONG i = 0; i < i_iNrQuals; i++) { DBG_ASSERT(i_awszQualNames[i] != NULL); hr = spQualSet->Put(i_awszQualNames[i], &i_avtQualValues[i], WBEM_FLAVOR_FLAG_PROPAGATE_TO_INSTANCE); if(FAILED(hr)) { goto exit; } } exit: return hr; } HRESULT CUtils::CreateEmptyInstance( LPWSTR i_wszClass, CWbemServices* i_pNamespace, IWbemClassObject** o_ppInstance) /*++ Synopsis: Creates an IWbemClassObject populated with default values. Arguments: [i_wszClass] - [i_pNamespace] - [o_ppInstance] - Must Release() if this function succeeds. --*/ { DBG_ASSERT(i_wszClass != NULL); DBG_ASSERT(i_pNamespace != NULL); DBG_ASSERT(o_ppInstance != NULL); HRESULT hr = S_OK; CComPtr spClass; CComPtr spInstance; hr = i_pNamespace->GetObject( i_wszClass, 0, NULL, &spClass, NULL); if(FAILED(hr)) { goto exit; } hr = spClass->SpawnInstance(0, &spInstance); if(FAILED(hr)) { goto exit; } *o_ppInstance = spInstance; (*o_ppInstance)->AddRef(); exit: return hr; } void CUtils::MessageCodeToText( DWORD i_dwMC, va_list* i_pArgs, BSTR* o_pbstrText) /*++ Synopsis: Arguments: [i_dwMC] - [i_pArgs] - Can be NULL [o_pbstrText] - Needs to be freed by caller --*/ { DBG_ASSERT(o_pbstrText != NULL); *o_pbstrText = NULL; LPVOID lpMsgBuf = NULL; DWORD dwRet = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, g_hModule, i_dwMC, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPWSTR) &lpMsgBuf, 0, i_pArgs); if(dwRet == 0) { DBG_ASSERT(lpMsgBuf == NULL); } CComBSTR sbstrOut; if(lpMsgBuf != NULL) { // // If out of memory, sbstrOut will be NULL. This is okay. // sbstrOut = (LPWSTR)lpMsgBuf; // // Free the buffer. // LocalFree( lpMsgBuf ); } // // Set out parameter // *o_pbstrText = sbstrOut.Detach(); } void CUtils::HRToText( HRESULT i_hr, BSTR* o_pbstrText) { DBG_ASSERT(o_pbstrText != NULL); CComPtr spStatus; *o_pbstrText = NULL; i_hr = HRESULT_FROM_WIN32(i_hr); if(HRESULT_FACILITY(i_hr) == FACILITY_INTERNET) { MessageCodeToText(i_hr, NULL, o_pbstrText); return; } HRESULT hr = CoCreateInstance( CLSID_WbemStatusCodeText, 0, CLSCTX_INPROC_SERVER, IID_IWbemStatusCodeText, (LPVOID *) &spStatus); CComBSTR sbstrError = NULL; CComBSTR sbstrFacility = NULL; if(SUCCEEDED(hr)) { spStatus->GetErrorCodeText(i_hr, 0, 0, &sbstrError); // ignore hr spStatus->GetFacilityCodeText(i_hr, 0, 0, &sbstrFacility); // ignore hr } CComBSTR sbstrFullError = NULL; if(sbstrError != NULL && sbstrFacility != NULL) { sbstrFullError = sbstrFacility; sbstrFullError += L": "; sbstrFullError += sbstrError; // sbstrFullError may be NULL in low mem -- okay } else if(sbstrError != NULL) { sbstrFullError = sbstrError; // sbstrFullError may be NULL in low mem -- okay } else if(sbstrFacility != NULL) { sbstrFullError = sbstrFacility; // sbstrFullError may be NULL in low mem -- okay } *o_pbstrText = sbstrFullError.Detach(); }