//*************************************************************************** // // COPYPASTE.CPP // // Module: HEALTHMON SERVER AGENT // // Purpose: Copying and pasting code below. // // Copyright (c)2000 Microsoft Corporation, All Rights Reserved // //*************************************************************************** #pragma warning (disable: 4786) // exceeds 255 chars in browser info #define _WIN32_DCOM #include "global.h" #include "system.h" #define ASSERT MY_ASSERT #define TRACE(x) MY_OUTPUT(x, 4) #include #include #include "SafeArray.h" #include "StringMap.h" // smart pointers for common WMI types _COM_SMARTPTR_TYPEDEF(IWbemLocator, __uuidof(IWbemLocator)); _COM_SMARTPTR_TYPEDEF(IWbemServices, __uuidof(IWbemServices)); _COM_SMARTPTR_TYPEDEF(IWbemClassObject, __uuidof(IWbemClassObject)); _COM_SMARTPTR_TYPEDEF(IWbemQualifierSet, __uuidof(IWbemQualifierSet)); _COM_SMARTPTR_TYPEDEF(IWbemObjectSink, __uuidof(IWbemObjectSink)); _COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject, __uuidof(IEnumWbemClassObject)); // a bunch of constant strings. Enables us to change class names without a lot of cut // and paste static const _bstr_t bstrLocalHealthMonNamespace(L"\\\\.\\ROOT\\CIMV2\\MicrosoftHealthMonitor"); static const _bstr_t bstrHealthMonNamespace(L"ROOT\\CIMV2\\MicrosoftHealthMonitor"); static const _bstr_t bstrBaseConfigurationPath(L"MicrosoftHM_Configuration"); static const _bstr_t bstrFilterToConsumerBindingClassPath(L"__FilterToConsumerBinding"); static const _bstr_t bstrEventConsumerClassPath(L"__EventConsumer"); static const _bstr_t bstrEventFilterClassPath(L"__EventFilter"); static const _bstr_t bstrActionAssocClassPath(L"MicrosoftHM_ConfigurationActionAssociation"); static const _bstr_t bstrActionConfigurationClassPath(L"MicrosoftHM_ActionConfiguration"); static const _bstr_t bstrTopPath(L"MicrosoftHM_SystemConfiguration.GUID=\"{@}\""); static const _bstr_t bstrTopGUID(L"{@}"); static const _bstr_t bstrConfigurationAssocClassPath(L"MicrosoftHM_ConfigurationAssociation"); static const _bstr_t bstrThresholdConfigurationClassPath(L"MicrosoftHM_ThresholdConfiguration"); static const _bstr_t bstrDataCollectorConfigurationClassPath(L"MicrosoftHM_DataCollectorConfiguration"); static const _bstr_t bstrDataGroupConfigurationClassPath(L"MicrosoftHM_DataGroupConfiguration"); static const _bstr_t bstrSystemConfigurationClassPath(L"MicrosoftHM_SystemConfiguration"); static const _bstr_t strLanguage(L"WQL"); static const _bstr_t MOFFileHeader1( L"////////////////////////////////////////////////////////\n" L"// Automatically generated Health Monitor MOF dump\n" L"// Parent Root = " ); static const _bstr_t MOFFileHeader2( L"\n" L"////////////////////////////////////////////////////////\n" L"\n" L"#pragma autorecover\n" L"#pragma namespace(\"\\\\\\\\.\\\\ROOT\\\\CIMV2\\\\MicrosoftHealthMonitor\")\n" L"\n" L"\n"); // get a BSTR-valued property static HRESULT GetStringProperty (IWbemClassObject* pObj, LPCWSTR lpszPropName, _bstr_t& bstr) { _variant_t var; CHECK_ERROR (pObj->Get(lpszPropName, 0, &var, NULL, NULL)); if (V_VT(&var) != VT_BSTR) { CHECK_ERROR (E_INVALIDARG); // bad data type. should never happen } CHECK_ERROR (SafeAssign (bstr, var)); return S_OK; } static HRESULT GetUint32Property (IWbemClassObject* pObj, LPCWSTR lpszPropName, DWORD& val) { _variant_t var; CHECK_ERROR (pObj->Get(lpszPropName, 0, &var, NULL, NULL)); if (V_VT(&var) != VT_I4) { CHECK_ERROR (E_INVALIDARG); // bad data type. should never happen } val = V_I4(&var); return S_OK; } // get a BSTR-valued property static HRESULT PutStringProperty (IWbemClassObject* pObj, LPCWSTR lpszPropName, const _bstr_t& bstr) { _variant_t var; CHECK_ERROR (SafeAssign (var, bstr)); CHECK_ERROR (pObj->Put(lpszPropName, 0, &var, NULL)); return S_OK; } static HRESULT CompareInstances (IWbemClassObject* pObj1, IWbemClassObject* pObj2, bool& bMatch) { // compare all the properties of these two instances if (pObj1 == NULL || pObj2 == NULL) return WBEM_E_INVALID_PARAMETER; HRESULT hr = S_OK; CHECK_ERROR (pObj1->BeginEnumeration (WBEM_FLAG_NONSYSTEM_ONLY)); while (TRUE) { // get property. BSTR bstrVal; hr = pObj1->Next(0, &bstrVal, NULL, NULL, NULL ); if (hr == WBEM_S_NO_MORE_DATA) break; // BREAK OUT!!!! We're done. else if (FAILED (hr)) break; _bstr_t bstrName(bstrVal, false); // false for attach and auto-free _variant_t var1, var2; CIMTYPE eType1, eType2; if (pObj1->Get(bstrName, 0, &var1, &eType1, NULL)) break; if (pObj2->Get(bstrName, 0, &var2, &eType2, NULL)) break; if (eType1 != eType2) { bMatch = false; // differnet CIM types break; } else if (var1 != var2) { bMatch = false; // differnet values break; } } if (FAILED (hr)) { pObj1->EndEnumeration (); // clean up just in case CHECK_ERROR (hr); } CHECK_ERROR (pObj1->EndEnumeration ()); bMatch = true; return WBEM_S_NO_ERROR; } // Same as Clone(), but works across machine boundaries. Clone() // only works on local instances. static HRESULT CopyInstance (IWbemServicesPtr& WMI, IWbemClassObjectPtr& pObj1, IWbemClassObjectPtr& pObj2) { // first, get the class so that we can spawn a new instance _bstr_t bstrClass; IWbemClassObjectPtr smartpClass; CHECK_ERROR (GetStringProperty(pObj1, L"__CLASS", bstrClass)); CHECK_ERROR (WMI->GetObject (bstrClass, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpClass, NULL)); CHECK_ERROR (smartpClass->SpawnInstance (0, &pObj2)); HRESULT hr = S_OK; CHECK_ERROR (pObj1->BeginEnumeration (WBEM_FLAG_NONSYSTEM_ONLY)); while (TRUE) { // get property name BSTR bstrVal; hr = pObj1->Next(0, &bstrVal, NULL, NULL, NULL ); if (hr == WBEM_S_NO_MORE_DATA) break; // BREAK OUT!!!! We're done. else if (FAILED (hr)) break; _bstr_t bstrName(bstrVal, false); // false for attach and auto-free // get the property on one and copy to the other _variant_t var; CIMTYPE eType1; if (FAILED(hr = pObj1->Get(bstrName, 0, &var, &eType1, NULL))) break; if (FAILED(hr = pObj2->Put(bstrName, 0, &var, 0))) break; } if (FAILED (hr)) { pObj1->EndEnumeration (); // clean up just in case CHECK_ERROR (hr); } CHECK_ERROR (pObj1->EndEnumeration ()); return WBEM_S_NO_ERROR; } // // escape a string so that it's OK to put in an object path // static HRESULT WmiPathEscape (LPCWSTR pszPath, _bstr_t& ResultPath) { // first, allocate a string twice as big. (escaping never exceeds 2x size) BSTR bstr = ::SysAllocStringLen (NULL, wcslen(pszPath)*2); if (bstr == NULL) CHECK_ERROR (E_OUTOFMEMORY); WCHAR *pDest = bstr; for (LPCWSTR pSrc = pszPath; *pSrc; pSrc++) { if (*pSrc == L'\"' || *pSrc == L'\\') { *pDest++ = L'\\'; } *pDest++ = *pSrc; // unescaped char } *pDest++ = 0; // null-terminate CHECK_ERROR (SafeAssign(ResultPath, bstr)); ::SysFreeString(bstr); // free this now that copy is made return S_OK; } static HRESULT GetAssociationPath (_bstr_t& strAssocPath, LPCWSTR szAssocClass, LPCWSTR szInstance1Role, LPCWSTR szInstance1Path, LPCWSTR szInstance2Role, LPCWSTR szInstance2Path) { try { // first, escape the object paths so that they can be encased in // other object paths _bstr_t bstrInstance1Path, bstrInstance2Path; CHECK_ERROR (WmiPathEscape(szInstance1Path, bstrInstance1Path)) CHECK_ERROR (WmiPathEscape(szInstance2Path, bstrInstance2Path)) // now construct the association path strAssocPath = szAssocClass; strAssocPath += L"."; strAssocPath += szInstance1Role; strAssocPath += L"=\""; strAssocPath += bstrInstance1Path; strAssocPath += L"\","; strAssocPath += szInstance2Role; strAssocPath += L"=\""; strAssocPath += bstrInstance2Path; strAssocPath += L"\""; } catch (_com_error e) { CHECK_ERROR (e.Error()); } return S_OK; } // OK, it's an action. There are four instances: // This method saves them all. // a) The ConfigurationActionAssication, between the HM object and the action // b) the action configuration itself // c) The event filter // d) the event consumer // e) the filter-to-consumer binding static HRESULT AddActionOtherInstances ( SafeArrayOneDimWbemClassObject& saInstances, int& nArrayIndex, IWbemClassObjectPtr& smartpParent, IWbemServices* WMI ) { IWbemClassObjectPtr smartpFilter, smartpConsumer, smartpFilterToConsumerBinding; HRESULT hr; ULONG nRet; // First, build paths for the filter & consumer _bstr_t bstrActionGUID, bstrConsumerPath, bstrFilterPath; CHECK_ERROR(GetStringProperty(smartpParent, L"GUID", bstrActionGUID)); CHECK_ERROR(GetStringProperty(smartpParent, L"EventConsumer", bstrConsumerPath)); try { // The Filter bstrFilterPath = L"__EventFilter.Name=\""; bstrFilterPath += bstrActionGUID; bstrFilterPath += "\""; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // ok, now that we've got the paths, let's get the objects themselves CHECK_ERROR (WMI->GetObject (bstrConsumerPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpConsumer, NULL)); CHECK_ERROR (WMI->GetObject (bstrFilterPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpFilter, NULL)); // Now query to get the Filter To Consumer Binding _bstr_t strQueryFTCB; try { strQueryFTCB = L"REFERENCES OF {"; strQueryFTCB += bstrFilterPath; strQueryFTCB += "} WHERE ResultClass = "; strQueryFTCB += bstrFilterToConsumerBindingClassPath; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // now get the Filter-to-consumer binding from WMI. // note that this must return a valid instance or it's an error! IEnumWbemClassObjectPtr pEnumFTCB; CHECK_ERROR (WMI->ExecQuery (strLanguage, strQueryFTCB, 0, NULL, &pEnumFTCB)); hr = pEnumFTCB->Next(5000, 1, &smartpFilterToConsumerBinding, &nRet); if (FAILED(hr)) CHECK_ERROR (hr); // either S_FALSE (none found) or error if (hr == WBEM_S_TIMEDOUT) CHECK_ERROR (RPC_E_TIMEOUT); // it timed out. bad! if (hr != WBEM_S_NO_ERROR) CHECK_ERROR (E_FAIL); // no associations found. bad! // now add'em to the array CHECK_ERROR (saInstances.SafePutElement(nArrayIndex++, smartpConsumer)); CHECK_ERROR (saInstances.SafePutElement(nArrayIndex++, smartpFilter)); CHECK_ERROR (saInstances.SafePutElement(nArrayIndex++, smartpFilterToConsumerBinding)); return S_OK; } static HRESULT BuildInstancesArray ( SafeArrayOneDimWbemClassObject& saInstances, int& nArrayIndex, IWbemClassObjectPtr& smartpParent, IWbemServices* WMI, bool bCopyActionsOnly = false, // only copy the actions; nothing more bool bFirstTime = true // recursion? ) { ULONG nRet; HRESULT hr; ASSERT (smartpParent != NULL); _bstr_t bstrParentPath; CHECK_ERROR (GetStringProperty (smartpParent, L"__RELPATH", bstrParentPath)); // Next, deal with children. Depending on what kind of // configuration class this is, we respond differently _bstr_t bstrClass; bool bCanHaveChildren = false; if (bCopyActionsOnly) { bCanHaveChildren = true; // get all the actions } else { CHECK_ERROR (GetStringProperty(smartpParent,L"__CLASS", bstrClass)); // first time through, we need to store the parent object before we // recurse to find kids if (bFirstTime) { // if it's an action, store the filter, binding, etc. // note that we need to store these first, as the // agent needs to have the configuration show up last. if (!_wcsicmp (bstrClass, bstrActionConfigurationClassPath)) { CHECK_ERROR (AddActionOtherInstances (saInstances, nArrayIndex, smartpParent, WMI )); } // store the top instance CHECK_ERROR (saInstances.SafePutElement (nArrayIndex++, smartpParent)); } if (!_wcsicmp (bstrClass, bstrActionConfigurationClassPath) || !_wcsicmp (bstrClass, bstrThresholdConfigurationClassPath)) { // it's a threshold or action. There are never children. return WBEM_S_NO_ERROR; } else { // it may have children. below we will recurse to handle children bCanHaveChildren = true; } } if (bCanHaveChildren) { // build the apppropriate queries to fetch the associators for // this parent object. Note that there is a weird syntax for the // ASSOCIATORS OF and REFERENCES OF queries which combines query // clauses without using AND. See the WMI SDK descripion of // REFERENCES OF for more details. _bstr_t bstrQueryChildren; try { // all the actions if (bCopyActionsOnly) { bstrQueryChildren = "SELECT * FROM "; bstrQueryChildren += bstrActionConfigurationClassPath; } else { // all the associations referencing us bstrQueryChildren = L"REFERENCES OF {"; bstrQueryChildren += bstrParentPath; bstrQueryChildren += "} WHERE ResultClass = "; bstrQueryChildren += bstrConfigurationAssocClassPath; bstrQueryChildren += " Role = ParentPath"; } } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } IEnumWbemClassObjectPtr pEnum; if (FAILED (hr = WMI->ExecQuery (strLanguage, bstrQueryChildren, 0, NULL, &pEnum))) { // TRACE (L"%s\n", (LPCTSTR) bstrQueryChildren); if (hr != WBEM_E_NOT_FOUND) // notfound is OK-- there may be no children. We're done. { CHECK_ERROR (hr); } } else // everything is OK. now enumerate { _bstr_t bstrParentPath, bstrChildPath; IWbemClassObjectPtr smartpAssocInstance, smartpInstance; for ( hr = pEnum->Next(5000, 1, &smartpAssocInstance, &nRet); SUCCEEDED(hr) && hr == WBEM_S_NO_ERROR; hr = pEnum->Next(5000, 1, &smartpAssocInstance, &nRet)) { // if we are just getting the actions, if (bCopyActionsOnly) { // if it's an action, store the filter, binding, etc. // note that we need to store these first, as the // agent needs to have the configuration show up last. CHECK_ERROR (AddActionOtherInstances (saInstances, nArrayIndex, smartpAssocInstance, WMI )); // store the instance. CHECK_ERROR (saInstances.SafePutElement (nArrayIndex++, smartpAssocInstance)); } else { // not only the actions _variant_t var; // now get the configuration instance that this association // instance actually points to CHECK_ERROR (GetStringProperty(smartpAssocInstance, L"ChildPath", bstrChildPath)); CHECK_ERROR (WMI->GetObject (bstrChildPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpInstance, NULL)); // If it's an action, store the other subinstances as well // note that we need to store the subinstances first, as the // agent needs to have the configuration show up last. CHECK_ERROR (GetStringProperty(smartpInstance,L"__CLASS", bstrClass)); if (!_wcsicmp (bstrClass, bstrActionConfigurationClassPath)) { CHECK_ERROR (AddActionOtherInstances (saInstances, nArrayIndex, smartpInstance, WMI )); } // store the instance. CHECK_ERROR (saInstances.SafePutElement (nArrayIndex++, smartpInstance)); // now put the association in the array CHECK_ERROR (saInstances.SafePutElement(nArrayIndex++, smartpAssocInstance)); // now recurse to deal with *this* object's children // note that actions and thresholds have no kids if (_wcsicmp (bstrClass, bstrActionConfigurationClassPath) != 0 && _wcsicmp (bstrClass, bstrThresholdConfigurationClassPath) != 0) { CHECK_ERROR (BuildInstancesArray (saInstances, nArrayIndex, smartpInstance, WMI, false // recursion! )); } } } if (hr == WBEM_S_TIMEDOUT) CHECK_ERROR (RPC_E_TIMEOUT); // it timed out. bad! CHECK_ERROR (hr); } } return WBEM_S_NO_ERROR; } enum EGuidType {GUID_PLAIN, GUID_PATH, GUID_QUERY}; static HRESULT ReGuidOneProperty (IWbemClassObjectPtr& pObj, LPCWSTR wszPropName, StringToStringMap& GuidMap, EGuidType eType) { // get the current value of the property from WMI _variant_t var; CIMTYPE CimType; CHECK_ERROR (pObj->Get(wszPropName, 0, &var, &CimType, NULL)); if (var.vt != VT_BSTR) { ASSERT (FALSE); return E_FAIL; // bad type of prop! } // OK, now pull out the BSTR and remove it from the variant _bstr_t bstrCurrentPropValue(var.bstrVal, false); var.Detach(); // now locate the GUID in the string LPCWSTR pGuidStr; WCHAR szGUID[39]; // enough space to fit a GUID in Unicode switch (eType) { case GUID_PLAIN: pGuidStr = bstrCurrentPropValue; ASSERT (CimType == CIM_STRING); break; case GUID_PATH: pGuidStr = wcschr ((LPWSTR)bstrCurrentPropValue, L'{'); ASSERT (CimType == CIM_REFERENCE); break; case GUID_QUERY: pGuidStr = wcschr ((LPWSTR)bstrCurrentPropValue, L'{'); ASSERT (CimType == CIM_STRING); break; } if (pGuidStr == NULL) { ASSERT (FALSE); return E_FAIL; // corrupt WMI value! } LPCWSTR pEndBracket = wcschr (pGuidStr, '}'); if (pEndBracket == NULL) { ASSERT (FALSE); return E_FAIL; // corrupt WMI value! } else if (pEndBracket - pGuidStr == 2 && pGuidStr[1] == '@') { return S_OK; // it's the system configuration instance. // No need to re-guid. } else if (pEndBracket - pGuidStr != 37 ) { ASSERT (FALSE); return E_FAIL; // corrupt WMI value! } ASSERT (pGuidStr[0] == '{'); ASSERT (pGuidStr[37] == '}'); // now move the string into temp storage wcsncpy (szGUID, pGuidStr, 38); szGUID[38] = 0; // have we seen this one? _bstr_t bstrGuidNew; bool bFound; CHECK_ERROR (GuidMap.Find (szGUID, bstrGuidNew, bFound)); if (bFound) { // OK, we found this GUID there already. Use the preset // replacement GUID wcsncpy ((LPWSTR) pGuidStr, bstrGuidNew, 38); } else { // we haven't already seen this. Generate a new GUID, add it // to the map CGuidString strNewGuid; GuidMap.Add (szGUID, strNewGuid); wcsncpy ((LPWSTR) pGuidStr, strNewGuid, 38); } // OK, now we have a BSTR with the new GUID. CHECK_ERROR (PutStringProperty (pObj, wszPropName, bstrCurrentPropValue)); return S_OK; } // walk through the array looking for GUID's. When we find a GUID, // we will check a mapping table to see if it is already mapped to another // GUID. If it is, we will replace it and move on through the list. If // it isn't in the table, we will generate a new GUID, replace the // one already there, and store the new GUID in the table static HRESULT ReGuid(SafeArrayOneDimWbemClassObject& saInstances, StringToStringMap& aGuidMap ) { HRESULT hr; for ( int i = 0, nLen = saInstances.GetSize(); i < nLen; i++) { IWbemClassObjectPtr pObj; CHECK_ERROR (saInstances.GetElement(i, &pObj)); ASSERT (pObj != NULL); // now check the class of this object. Depending on the class type, _bstr_t bstrClass, bstrPath; CHECK_ERROR (GetStringProperty (pObj, L"__RELPATH", bstrPath)) CHECK_ERROR (GetStringProperty (pObj, L"__CLASS", bstrClass)); // now re-guid the appropriate properties if (!_wcsicmp(bstrClass, bstrActionConfigurationClassPath)) { hr = ReGuidOneProperty (pObj, L"GUID", aGuidMap, GUID_PLAIN); hr = ReGuidOneProperty (pObj, L"EventConsumer", aGuidMap, GUID_PATH); } else if (pObj->InheritsFrom(bstrBaseConfigurationPath)==S_OK) { // it's a configuration class, but not action config. Use the GUID property hr = ReGuidOneProperty (pObj, L"GUID", aGuidMap, GUID_PLAIN); } else if (pObj->InheritsFrom(bstrEventConsumerClassPath)==S_OK) { hr = ReGuidOneProperty (pObj, L"Name", aGuidMap, GUID_PLAIN); } else if (pObj->InheritsFrom(L"__EventFilter")==S_OK) { hr = ReGuidOneProperty (pObj, L"Name", aGuidMap, GUID_PLAIN); hr = ReGuidOneProperty (pObj, L"Query", aGuidMap, GUID_QUERY); } else if (!_wcsicmp(bstrClass, bstrFilterToConsumerBindingClassPath)) { hr = ReGuidOneProperty (pObj, L"Consumer", aGuidMap, GUID_PATH); hr = ReGuidOneProperty (pObj, L"Filter", aGuidMap, GUID_PATH); } else if (!_wcsicmp(bstrClass, bstrActionAssocClassPath)) { hr = ReGuidOneProperty (pObj, L"ParentPath", aGuidMap, GUID_PATH); hr = ReGuidOneProperty (pObj, L"ChildPath", aGuidMap, GUID_PATH); hr = ReGuidOneProperty (pObj, L"EventFilter", aGuidMap, GUID_PATH); hr = ReGuidOneProperty (pObj, L"Query", aGuidMap, GUID_QUERY); } else if (!_wcsicmp(bstrClass, bstrConfigurationAssocClassPath)) { hr = ReGuidOneProperty (pObj, L"ParentPath", aGuidMap, GUID_PATH); hr = ReGuidOneProperty (pObj, L"ChildPath", aGuidMap, GUID_PATH); } else { ASSERT (FALSE); // should never happen. it's an invalid class. } CHECK_ERROR (hr); } return S_OK; } // // Detect all actions with duplicate names between the clipboard and the target // Fill up a GUID map with the dupes // static HRESULT FillActionGuidMap( SafeArrayOneDimWbemClassObject& saInstances, StringToStringMap& ActionNameToGuidMap, StringToStringMap& GuidMap) { for ( int i = 0, nLen = saInstances.GetSize(); i < nLen; i++) { IWbemClassObjectPtr pObj; CHECK_ERROR (saInstances.GetElement(i, &pObj)); ASSERT (pObj != NULL); _bstr_t bstrClass, bstrPath; CHECK_ERROR (GetStringProperty (pObj, L"__CLASS", bstrClass)); // now re-guid the appropriate properties if (_wcsicmp(bstrClass, bstrActionConfigurationClassPath) != 0) continue; // we only care about actions! _bstr_t bstrGuidCurrentAction, bstrName; bool bFound = false; CHECK_ERROR (GetStringProperty(pObj, L"Name", bstrName)); CHECK_ERROR (ActionNameToGuidMap.Find(bstrName, bstrGuidCurrentAction, bFound)); if (bFound) { // there's a duplicate! We'll store a mapping of the GUID in // the clipboard's instance to the duplicate action GUID. _bstr_t bstrGUID; CHECK_ERROR (GetStringProperty (pObj, L"GUID", bstrGUID)); CHECK_ERROR (GuidMap.Add(bstrGUID, bstrGuidCurrentAction)); } } return S_OK; } static HRESULT Copy(LPTSTR szSourceComputer, LPTSTR szGUID, bstr_t& bstrParentGUID, // [out] SafeArrayOneDimWbemClassObject& saInstances, CSystem& System) { // impersonate the caller. Important since we're now calling into // WMI and need to make sure caller is allowed CHECK_ERROR(CoImpersonateClient()); _bstr_t bstrNamespace = L"\\\\"; bstrNamespace += szSourceComputer; bstrNamespace += L"\\"; bstrNamespace += bstrHealthMonNamespace; IWbemServicesPtr WMI = g_pIWbemServices; // IWbemLocatorPtr WMILocator; // IWbemServicesPtr WMI; // CHECK_ERROR (WMILocator.CreateInstance(__uuidof(WbemLocator),NULL)); // CHECK_ERROR (WMILocator->ConnectServer (bstrNamespace, // NULL, NULL, NULL, 0, NULL, NULL, // &WMI)); _bstr_t bstrPath; try { bstrPath = bstrBaseConfigurationPath; bstrPath += L".GUID=\""; bstrPath += szGUID; bstrPath += L"\""; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } IWbemClassObjectPtr smartpObj; HRESULT hr = WMI->GetObject (bstrPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpObj, NULL); if (hr == WBEM_E_NOT_FOUND) { return WBEM_E_NOT_FOUND; // it's not there } CHECK_ERROR (hr); // if it's a single action, then we fake the parent to be the system instance // same if it's the entire system that we're copying _bstr_t bstrClass; CHECK_ERROR (GetStringProperty(smartpObj, L"__CLASS", bstrClass)); if (!_wcsicmp (bstrClass, bstrActionConfigurationClassPath) || !_wcsicmp (bstrClass, bstrSystemConfigurationClassPath)) { try { bstrParentGUID = bstrTopGUID; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } } else { // build the query to find our parent _bstr_t bstrParentQuery; try { bstrParentQuery = L"ASSOCIATORS OF {"; bstrParentQuery += bstrPath; bstrParentQuery += L"} WHERE"; bstrParentQuery += L" ResultRole = ParentPath"; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // now exec the query to fetch the parent ULONG nRet; IEnumWbemClassObjectPtr pEnumParent; IWbemClassObjectPtr smartpParent; CHECK_ERROR (WMI->ExecQuery (strLanguage, bstrParentQuery, 0, NULL, &pEnumParent)); hr = pEnumParent->Next(5000, 1, &smartpParent, &nRet); if (FAILED(hr)) CHECK_ERROR (hr); if (hr == WBEM_S_TIMEDOUT) CHECK_ERROR (RPC_E_TIMEOUT); // it timed out. bad! if (hr != WBEM_S_NO_ERROR) CHECK_ERROR (E_FAIL); // no parent found. bad! CHECK_ERROR (GetStringProperty(smartpParent, L"GUID", bstrParentGUID)); } // now actually go get the array int nArrayIndex = 0; CHECK_ERROR (saInstances.Clear()); CHECK_ERROR (saInstances.Create()); // now build an array of all the instances underneath this one CHECK_ERROR (BuildInstancesArray (saInstances, nArrayIndex, smartpObj, WMI)); // now, if this was the entire system that we were copying, copy the // unattached actions as well. if (!_wcsicmp (bstrClass, bstrSystemConfigurationClassPath)) { // now add all the actions to the array CHECK_ERROR (BuildInstancesArray (saInstances, nArrayIndex, smartpObj, WMI, true)); } CHECK_ERROR (saInstances.Resize(nArrayIndex)); return S_OK; } // the input is an array of instances to be added, in order. Add them. // Note that actions will be compared to actions currently on the box. static HRESULT Paste(LPCTSTR pszTargetComputer, LPCTSTR pszTargetParentGUID, LPCTSTR pszOriginalComputer, LPCTSTR pszOriginalParentGUID, SAFEARRAY* psa, BOOL bForceReplace, CSystem& System) { // impersonate the caller. Important since we're now calling into // WMI and need to make sure caller is allowed CHECK_ERROR(CoImpersonateClient()); _bstr_t bstrNamespace = L"\\\\"; bstrNamespace += pszTargetComputer; bstrNamespace += L"\\"; bstrNamespace += bstrHealthMonNamespace; TCHAR szComputerName[1024]; DWORD dwSize = 1024; ::GetComputerName(szComputerName, &dwSize); if (!_wcsicmp(szComputerName, pszTargetComputer)) pszTargetComputer = L"."; if (!_wcsicmp(szComputerName, pszOriginalComputer)) pszOriginalComputer = L"."; IWbemServicesPtr WMI = g_pIWbemServices; HRESULT hr; // Commented-out code below was used when we were not part of the agent. // IWbemLocatorPtr WMILocator; // IWbemServicesPtr WMI; // CHECK_ERROR (WMILocator.CreateInstance(__uuidof(WbemLocator),NULL)); // CHECK_ERROR (WMILocator->ConnectServer (bstrNamespace, // NULL, NULL, NULL, 0, NULL, NULL, // &WMI)); // if we didn't create the array, then don't // bother proceeding VARIANT varSA; varSA.vt = VT_ARRAY | VT_UNKNOWN; varSA.parray = psa; if (!SafeArrayOneDimWbemClassObject::IsValid(varSA)) CHECK_ERROR (E_INVALIDARG); // since we know that it's one of ours, and because our SafeArray wrapper // is the same length as a regular safe array, we can cast it. SafeArrayOneDimWbemClassObject& saInstancesOriginal = (SafeArrayOneDimWbemClassObject&)varSA; // if we're pasting in the same parent, then the behavior should // match Windows, where you add a " (2)" to the name and make a copy bool bPasteOnSameMachine = !_wcsicmp (pszTargetComputer,pszOriginalComputer); bool bPasteInSameFolder = bPasteOnSameMachine && !_wcsicmp (pszTargetParentGUID, pszOriginalParentGUID); _bstr_t bstrParentRelPath; try { bstrParentRelPath = bstrBaseConfigurationPath; bstrParentRelPath += L".GUID=\""; bstrParentRelPath += pszTargetParentGUID; bstrParentRelPath += L"\""; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // Pasting Algorithm // ========================================================== // 1. If child with same name already exists in parent: If bOverwrite = TRUE, // delete the other guy. If bOverwrite = FALSE, return an error. // 2. If actions with same name but *different* contents already exists in parent: // If bOverwrite = TRUE, delete the other actions. If bOverwrite = FALSE, // return an error. // 3. If actions with same name and *same* contents already exists in parent, // reassign the to-be-pasted GUID's to match the target. // 4. Now simply Put each member of the array. //=========================================================== // fetch this parent instance IWbemClassObjectPtr smartpParentInstance; CHECK_ERROR (WMI->GetObject (bstrParentRelPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpParentInstance, NULL)); // get the full, normalized path CHECK_ERROR (GetStringProperty(smartpParentInstance, L"__RELPATH", bstrParentRelPath)); // First, get the top instance of the array. This is the parent IWbemClassObjectPtr smartpTopInstance; CHECK_ERROR (saInstancesOriginal.GetElement (0, &smartpTopInstance)); int nTopIndex = 0; // now get the name of that top instance _bstr_t bstrTopInstanceName, bstrTopInstanceClass; CHECK_ERROR (GetStringProperty(smartpTopInstance, L"__CLASS", bstrTopInstanceClass)); if (bstrTopInstanceClass == bstrSystemConfigurationClassPath) { // we cannot (yet) paste the entire system CHECK_ERROR (E_INVALIDARG); } // now see if we're only copying actions-- which will be the case if an // action (or the filter, binding, etc.) is first on the list of instances // coming back from the Copy command. // BUGBUG: need to do something better than simply looking for the string // "Consumer" below-- not all event consumers will contain that string! bool bIsActionTopInstance = bstrTopInstanceClass == bstrActionConfigurationClassPath || bstrTopInstanceClass == bstrFilterToConsumerBindingClassPath || wcsstr ((LPCTSTR) bstrTopInstanceClass, L"Consumer") != NULL || bstrTopInstanceClass == bstrEventFilterClassPath; if (bIsActionTopInstance) { // we need to find the name of the action. But the action is not on top-- it could // be any of the top 4 instances (action config, filter, consumer or binding). So go look. // we've already checked the zeroth, so we can start at 1. for (int i = 1; i < 4; i++) { CHECK_ERROR (saInstancesOriginal.GetElement (i, &smartpTopInstance)); CHECK_ERROR (GetStringProperty(smartpTopInstance, L"__CLASS", bstrTopInstanceClass)); if (bstrTopInstanceClass == bstrActionConfigurationClassPath) { // found it! we will get the name nTopIndex = i; break; } } if (i == 4) { CHECK_ERROR (E_INVALIDARG); // uh-oh. the instance array was corrupted. } } // finally, get the name of the instance CHECK_ERROR (GetStringProperty(smartpTopInstance, L"Name", bstrTopInstanceName)); // Build a query to look for siblings in the new parent _bstr_t bstrQueryChildren; try { if (bIsActionTopInstance) { // for actions, there is no parent. Just list all actions bstrQueryChildren = L"SELECT * FROM MicrosoftHM_ActionConfiguration"; } else { // first, make a query for get kids of this parent bstrQueryChildren = L"ASSOCIATORS OF {"; bstrQueryChildren += bstrParentRelPath; bstrQueryChildren += L"} WHERE"; bstrQueryChildren += L" ResultRole = ChildPath"; } } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // now execute this query. Look for siblings with matching (i.e. conflicting) names StringToStringMap TopLevelNameConflictMap; _bstr_t bstrConflictGUID; IEnumWbemClassObjectPtr pEnum; // TRACE (L"%s\n", (LPCTSTR) bstrQueryChildren); if (FAILED (hr = WMI->ExecQuery (strLanguage, bstrQueryChildren, 0, NULL, &pEnum))) { // TRACE (L"%s\n", (LPCTSTR) bstrQueryChildren); if (hr != WBEM_E_NOT_FOUND) // notfound is OK-- there may be no children. We're done. { CHECK_ERROR (hr); } } else { _bstr_t bstrChildName; IWbemClassObjectPtr smartpInstance; ULONG nRet; for ( hr = pEnum->Next(5000, 1, &smartpInstance, &nRet); SUCCEEDED(hr) && hr == WBEM_S_NO_ERROR; hr = pEnum->Next(5000, 1, &smartpInstance, &nRet)) { _bstr_t bstrChildName, bstrChildClass; CHECK_ERROR (GetStringProperty(smartpInstance, L"Name", bstrChildName)); CHECK_ERROR (GetStringProperty(smartpInstance, L"__CLASS", bstrChildClass)); // if this child is an action, then the only case where we care about // conflicts is in the top-level. Otherwise, the actions are not really // "children" (they're just associated via the ConfigActionAssociation class) // so we can ignore them. But if this is an action as the // top instance, you bet we want to check for conflicts! if (!bIsActionTopInstance && (bstrChildClass == bstrActionConfigurationClassPath)) continue; // store this for later, in case we have to rename the top item. if (bPasteInSameFolder) CHECK_ERROR(TopLevelNameConflictMap.Add(bstrChildName, L"NotUsed")); // check for conflict if (!_wcsicmp (bstrChildName, bstrTopInstanceName)) { CHECK_ERROR (GetStringProperty(smartpInstance, L"GUID", bstrConflictGUID)); if (!bPasteInSameFolder) { // uh, oh. We have a name conflict. Depending on whether the // user wants to perform any overwrites, we will either have to // delete the conflicting one or return an error. if (bForceReplace) { TRACE(L"Paste: Instance Name Conflict. We will delete the conflicting one"); break; } else { TRACE(L"Paste: Instance Name Conflict. Returning an error."); return WBEM_E_ALREADY_EXISTS; } } } } if (hr == WBEM_S_TIMEDOUT) CHECK_ERROR (RPC_E_TIMEOUT); // it timed out. bad! CHECK_ERROR (hr); } // OK, Now build a map of the names of actions already on the target // and their GUID's try { bstrQueryChildren = L"SELECT * FROM "; bstrQueryChildren += bstrActionConfigurationClassPath; } catch (_com_error e) { CHECK_ERROR (e.Error()); // out of memory } // we will store a hashtable of mapping action names to their // GUID's. This will help us in re-GUIDing and in identifying conflicts. StringToStringMap ActionNameToGuidMap; // now execute this query if (FAILED (hr = WMI->ExecQuery (strLanguage, bstrQueryChildren, 0, NULL, &pEnum))) { // TRACE (L"%s\n", (LPCTSTR) bstrQueryChildren); if (hr != WBEM_E_NOT_FOUND) // notfound is OK-- there may be no children. We're done. { CHECK_ERROR (hr); } } else { _bstr_t bstrChildName; IWbemClassObjectPtr smartpInstance; ULONG nRet; for ( hr = pEnum->Next(5000, 1, &smartpInstance, &nRet); SUCCEEDED(hr) && hr == WBEM_S_NO_ERROR; hr = pEnum->Next(5000, 1, &smartpInstance, &nRet)) { _bstr_t bstrActionName, bstrActionGUID; CHECK_ERROR (GetStringProperty(smartpInstance, L"Name", bstrActionName)); CHECK_ERROR (GetStringProperty(smartpInstance, L"GUID", bstrActionGUID)); CHECK_ERROR (ActionNameToGuidMap.Add(bstrActionName, bstrActionGUID)); } } // OK, now we will make a copy of the array. bool bAlreadyFound = false; SafeArrayOneDimWbemClassObject saInstancesNew; CHECK_ERROR (saInstancesNew.Create()); for (int i = 0, nLen = saInstancesOriginal.GetSize(); i < nLen; i++) { IWbemClassObjectPtr smartpInstance; CHECK_ERROR (saInstancesOriginal.GetElement (i, &smartpInstance)); // make a copy & store it. Note that it's a no-no to take instances // from one machine and PutInstance them on another machine-- they will // fail intermittently. So we in turn do a "manual clone" here to // spawn the instance locally and copy the properties one by one // On the same machine, it's faster and more efficient to use Clone(), // so we do. IWbemClassObjectPtr smartpNewInstance; if (bPasteOnSameMachine) { CHECK_ERROR (smartpInstance->Clone(&smartpNewInstance)); } else { CHECK_ERROR (CopyInstance (WMI, smartpInstance, smartpNewInstance)); } CHECK_ERROR (saInstancesNew.SafePutElement(i, smartpNewInstance)); // reassign, considering that we're re-GUIDing it if (i == nTopIndex) smartpTopInstance = smartpNewInstance; /* if (!bAlreadyFound) { // get name, GUID, and class _bstr_t bstrClass; CHECK_ERROR (GetStringProperty(smartpNewInstance, "__CLASS", &bstrClass); // if it's action-related, see if it's already there if (!_wcsicmp(bstrFilterToConsumerBindingClassPath, bstrClass) || !_wcsicmp(bstrEventConsumerClassPath, bstrClass) || !_wcsicmp(bstrActionAssocClassPath, bstrClass) || !_wcsicmp(bstrActionConfigurationClassPath, bstrClass) ) { CHECK_ERROR (GetStringProperty(smartpNewInstance, "Name", &bstrActionName); _bstr_t bstrGuidNew, bstrName; bool bFound; CHECK_ERROR (GetStringProperty(pObj, L"Name", bstrName)); CHECK_ERROR (GuidMap.Find(bstrName, bstrGuidNew, bFound)); if (bFound) { } } */ } // trim the new array CHECK_ERROR (saInstancesNew.Resize(nLen)); // now find all the actions we're associated to, compare their names // to actions on the target machine, and fill up a hashtable map // with the list of conflicts. Note that if this is just one action that // we're copying locally, there's no need to look for conflicts because // we're renaming the action anyway. StringToStringMap GuidMap; if (! (bIsActionTopInstance && bPasteInSameFolder) ) { CHECK_ERROR (FillActionGuidMap(saInstancesNew, ActionNameToGuidMap, GuidMap)); int nDuplicateCount = GuidMap.GetSize(); if (nDuplicateCount > 0 && !bPasteOnSameMachine) { // uh, oh. We have a name conflict. Depending on whether the // user wants to perform any overwrites, we will either have to // delete the conflicting one or return an error. if (bForceReplace) { TRACE(L"Paste: Action Name Conflict. We will delete the conflicting one"); } else { TRACE(L"Paste: Action Name Conflict. Returning an error."); return WBEM_E_ALREADY_EXISTS; } } } // Now, let's change all the GUID's (except for the actions // that we'll be replacing, as above). CHECK_ERROR (ReGuid (saInstancesNew, GuidMap)); // Now, let's see if I need to rename the top-level item to // resolve name conflicts if ((LPCWSTR)bstrConflictGUID != NULL) { if (bPasteInSameFolder) { // if we get here, we're renaming our top item to avoid // a name conflict, just like Explorer does i = 2; bool bFound = true; _bstr_t bstrName; do { // compose a name, like Foo (2), Foo (3), etc. like explorer does WCHAR szNumber[20]; bstrName = bstrTopInstanceName; bstrName += L" ("; bstrName += _itow (i++, szNumber, 10); bstrName += L")"; _bstr_t NotUsed; CHECK_ERROR (TopLevelNameConflictMap.Find(bstrName, NotUsed, bFound)); } while (bFound); // when we get to here, we've found a free name CHECK_ERROR (PutStringProperty(smartpTopInstance, L"Name", bstrName)); } else if (! bIsActionTopInstance) { // we're almost there. Now we need to delete the top-level item on the // target, if present, to make room for us. Use the delete method. // note that we *do not* go through this codepath for actions, because // for actions we will just lay down a new action, with the same GUID, // right over the old one. If we actually did call delete (via the agent) // the agent would clobber all the action assocations, whereas we want to // keep them there and have the new action just fall right into place IWbemClassObjectPtr smartpSystemClass, smartpInParamsClass, smartpInParamsInstance, smartpResults; // impersonate the caller. Important since we're now calling into // WMI and need to make sure caller is allowed CHECK_ERROR(CoImpersonateClient()); // BUGBUG: For next release, we need to think about how to provide // safer functionality here, so that we don't actually delete the // target until we've successfully added the new stuff // until then, just delete the conflict CHECK_ERROR(System.FindAndDeleteByGUID(bstrConflictGUID)); /* // now that this code lives inside the agent, there's no need to call // an external method on the agent. It's much easier-- just call // the FindAndDeleteByGUID function on the system class! // get the system class CHECK_ERROR (WMI->GetObject (bstrSystemConfigurationClassPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpSystemClass, NULL)); // get the delete method in-params "class" CHECK_ERROR (smartpSystemClass->GetMethod (L"Delete", 0, &smartpInParamsClass, NULL)); CHECK_ERROR (smartpInParamsClass->SpawnInstance (0, &smartpInParamsInstance)); CHECK_ERROR (PutStringProperty(smartpInParamsInstance, L"TargetGUID", bstrTopInstanceGUID)); CHECK_ERROR (WMI->ExecMethod(bstrSystemConfigurationClassPath, L"Delete", WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, smartpInParamsInstance, &smartpResults, NULL)); DWORD dwReturnValue; CHECK_ERROR (GetUint32Property (smartpResults, L"ReturnValue", dwReturnValue)); CHECK_ERROR (dwReturnValue); */ } } // Now we need to compute the association to link the new instances up to the // parent instance. This should get the agent to pick up the change and set // all the balls in motion. Note that actions don't require parent associations-- // actions are just global instances available everywhere. IWbemClassObjectPtr smartpAssocInstance; if (! bIsActionTopInstance) { IWbemClassObjectPtr smartpAssocClass; _bstr_t bstrParentPath, bstrTopInstancePath, bstrTopInstanceRelPath; CHECK_ERROR (GetStringProperty(smartpTopInstance, L"__RELPATH", bstrTopInstanceRelPath)); try { bstrParentPath = bstrLocalHealthMonNamespace + L":" + bstrParentRelPath; bstrTopInstancePath = bstrLocalHealthMonNamespace + L":" + bstrTopInstanceRelPath; } catch (_com_error e) { CHECK_ERROR (e.Error()); } // now create the association CHECK_ERROR (WMI->GetObject (bstrConfigurationAssocClassPath, WBEM_FLAG_RETURN_WBEM_COMPLETE, NULL, &smartpAssocClass, NULL)); CHECK_ERROR(smartpAssocClass->SpawnInstance(0, &smartpAssocInstance)); CHECK_ERROR(PutStringProperty(smartpAssocInstance, L"ParentPath", bstrParentPath)); CHECK_ERROR(PutStringProperty(smartpAssocInstance, L"ChildPath", bstrTopInstancePath)); } // Whew! Finally, we're ready to paste the new items. Let 'er rip! for (i = 0, nLen = saInstancesNew.GetSize(); i < nLen; i++) { IWbemClassObjectPtr smartpInstance; CHECK_ERROR (saInstancesNew.GetElement (i, &smartpInstance)); _bstr_t bstrClass, bstrPath; CHECK_ERROR (GetStringProperty(smartpInstance, L"__RELPATH", bstrPath)); CHECK_ERROR (GetStringProperty(smartpInstance, L"__CLASS", bstrClass)); // If the user is pasting on the same machine, there's no need to write // actions, since we will simply use associations to the same actions. // The one exception is if we're pasting a single action (which will // result in a new action being laid down). if (bPasteOnSameMachine && ! bIsActionTopInstance) { if (!_wcsicmp(bstrFilterToConsumerBindingClassPath, bstrClass) || !_wcsicmp(bstrEventConsumerClassPath, bstrClass) || !_wcsicmp(bstrEventFilterClassPath, bstrClass) || !_wcsicmp(bstrActionConfigurationClassPath, bstrClass) ) { continue; // skip if it's an action } } // TODO: as an optmization, we should consider not laying down // multiple copies of the same action. WMI should handle our saves as // simple instance modification events, but it would speed things up // to filter out multiple copies of actions. // impersonate the caller. Important since we're now calling into // WMI and need to make sure caller is allowed CHECK_ERROR(CoImpersonateClient()); // finally, lay down the new instance! HRESULT hr = WMI->PutInstance(smartpInstance, WBEM_FLAG_RETURN_WBEM_COMPLETE | WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL); if (hr == WBEM_E_ACCESS_DENIED) { // we may have trouble overwriting action instances when those // were created by another user. But since admin users should // be able to override this restriction, and only admins can // use HM in this release, I think that we're OK. // BUGBUG: in future HM releases, we should consider deleting // old action instances if the SID's are different from ours. } if (FAILED(hr)) { CHECK_ERROR (hr); } if (i == 0 && (bool) smartpAssocInstance) { // now that we've stored the top-level parent, let's add the association // to the parent. There's a bug in the rest of the agent that it won't // pick up action associations unless the parent instance is already // associated. Hopefully this will be fixed, but until then we'll link up // the new top instance right at first. CHECK_ERROR(WMI->PutInstance(smartpAssocInstance, WBEM_FLAG_RETURN_WBEM_COMPLETE | WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL)); } //BUGBUG: shouldn't lay down anything that's already there and the same as us! //BUGBUG: before calling it a conflict, must compare properties! } return S_OK; } // Below are the actual entry points for the old agent code // to call the new copy & paste functionality HRESULT CSystem::AgentPaste(LPTSTR pszTargetGUID, SAFEARRAY* psa, LPTSTR pszOriginalSystem, LPTSTR pszOriginalParentGUID, BOOL bForceReplace) { HRESULT hr = Paste(L".", pszTargetGUID, pszOriginalSystem, pszOriginalParentGUID, psa, bForceReplace, *this); // BUGBUG: the agent returns the HRESULT of the Paste back // through the __ReturnValue of the method, not through WMI // errors. This is bad-- needs to be changed. if (hr == WBEM_E_ALREADY_EXISTS) return 2; // 2 is what console recognizes here. else if (FAILED(hr)) return hr; else return 0; } HRESULT CSystem::AgentCopy(LPTSTR pszGUID, SAFEARRAY** ppsa, LPTSTR *pszOriginalParentGUID) { SafeArrayOneDimWbemClassObject sa; CHECK_ERROR (sa.Create()); _bstr_t bstrParentGUID; // now call the underlying copy routine HRESULT hr = Copy(L".", pszGUID, bstrParentGUID, sa, *this); CHECK_ERROR (hr); VARIANT var = sa.Detach(); *ppsa = var.parray; // copy the BSTR into a new[] string, as the caller expects. // should really use a smart pointer or _bstr_t instead.... TCHAR* p = new TCHAR[bstrParentGUID.length()+1]; if (p == NULL) CHECK_ERROR (E_OUTOFMEMORY); wcscpy (p, bstrParentGUID); *pszOriginalParentGUID = p; return hr; }