Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

657 lines
19 KiB

  1. /*
  2. PLEASE NOTE!
  3. OneStop and MultiUser do not get along well. This code does some hacks to get stuff to work, and
  4. mobsync should not be invoked from the shell while OE is running.
  5. Some assumptions:
  6. There will never be a user 0
  7. */
  8. /*
  9. File: SyncHndl.cpp
  10. Implementation of OneStop Sync Handler
  11. */
  12. #include "pch.hxx"
  13. #include "resource.h"
  14. #include "synchndl.h"
  15. #include "syncenum.h"
  16. #include "syncprop.h"
  17. #include "spoolapi.h"
  18. #include "imnact.h"
  19. #include "multiusr.h"
  20. #include "instance.h"
  21. HRESULT CreateInstance_OneStopHandler(IUnknown *pUnkOuter, IUnknown **ppUnknown)
  22. {
  23. HRESULT hr = S_OK;
  24. TraceCall("CreateInstance_OneStopHandler");
  25. // We don't support aggregation and our factory knows it
  26. Assert(NULL == pUnkOuter);
  27. // Shouldn't be getting bad args from the factory either
  28. Assert(NULL != ppUnknown);
  29. *ppUnknown = new COneStopHandler;
  30. if (NULL == *ppUnknown)
  31. hr = E_OUTOFMEMORY;
  32. return hr;
  33. }
  34. COneStopHandler::COneStopHandler():
  35. m_cRef(1), m_pOfflineHandlerItems(NULL), m_pOfflineSynchronizeCallback(NULL),
  36. m_dwSyncFlags(0), m_fInOE(FALSE), m_dwUserID(0)
  37. {
  38. Assert(g_pInstance);
  39. if (SUCCEEDED(CoIncrementInit("COneStopHandler::COneStopHandler", MSOEAPI_START_COMOBJECT, NULL, NULL)))
  40. m_fInit = 1;
  41. else
  42. m_fInit = 0;
  43. }
  44. COneStopHandler::~COneStopHandler()
  45. {
  46. Assert(g_pInstance);
  47. if (m_pOfflineHandlerItems)
  48. OHIL_Release(m_pOfflineHandlerItems);
  49. if(m_fInit)
  50. g_pInstance->CoDecrementInit("COneStopHandler::COneStopHandler", NULL);
  51. }
  52. STDMETHODIMP COneStopHandler::QueryInterface(REFIID riid, LPVOID FAR *ppvObj)
  53. {
  54. TraceCall("COneStopHandler::QueryInterface");
  55. if(!ppvObj)
  56. return E_INVALIDARG;
  57. *ppvObj = NULL;
  58. if (IsEqualIID(riid, IID_IUnknown))
  59. *ppvObj = SAFECAST(this, IUnknown *);
  60. else if (IsEqualIID(riid, IID_ISyncMgrSynchronize))
  61. *ppvObj = SAFECAST(this, ISyncMgrSynchronize *);
  62. else
  63. return E_NOINTERFACE;
  64. InterlockedIncrement(&m_cRef);
  65. return NOERROR;
  66. }
  67. STDMETHODIMP_(ULONG) COneStopHandler::AddRef()
  68. {
  69. TraceCall("COneStopHandler::AddRef");
  70. return InterlockedIncrement(&m_cRef);
  71. }
  72. STDMETHODIMP_(ULONG) COneStopHandler::Release()
  73. {
  74. TraceCall("COneStopHandler::Release");
  75. LONG cRef = InterlockedDecrement(&m_cRef);
  76. if (cRef > 0)
  77. return (ULONG)cRef;
  78. delete this;
  79. return 0;
  80. }
  81. BOOL CreateOneStopItems(IImnEnumAccounts *pEnum, LPSYNCMGRHANDLERITEMS pOfflineHandlerItems, DWORD dwUserID, HICON *hicn)
  82. {
  83. BOOL bAnything = FALSE;
  84. IImnAccount *pAccount = NULL;
  85. LPWSTR pwsz = NULL;
  86. SYNCMGRHANDLERITEM *pItem = NULL;
  87. CHAR szAcctID[CCHMAX_ACCOUNT_NAME];
  88. CHAR szAcctName[CCHMAX_ACCOUNT_NAME];
  89. WCHAR wszItemName[MAX_SYNCMGRITEMNAME];
  90. DWORD dwAvail;
  91. int cDiff;
  92. ACCTTYPE accttype;
  93. ULONG cb;
  94. HRESULT hr;
  95. // Iterate through the accounts
  96. pEnum->SortByAccountName();
  97. while(SUCCEEDED(pEnum->GetNext(&pAccount)) &&
  98. SUCCEEDED(pAccount->GetPropSz(AP_ACCOUNT_ID, szAcctID, ARRAYSIZE(szAcctID))) &&
  99. SUCCEEDED(pAccount->GetPropSz(AP_ACCOUNT_NAME, szAcctName, ARRAYSIZE(szAcctName))) )
  100. {
  101. if (!(pwsz = PszToUnicode(CP_ACP, szAcctName)))
  102. break;
  103. // Safe to allocate this item, we have enough info to make the node
  104. if (pItem = OHIL_AddItem(pOfflineHandlerItems))
  105. {
  106. StrCpyNA(pItem->szAcctName, szAcctName, ARRAYSIZE(pItem->szAcctName));
  107. StrCpyNW(pItem->offlineItem.wszItemName, pwsz, ARRAYSIZE(pItem->offlineItem.wszItemName));
  108. StrCpyNA(pItem->szAcctID, szAcctID, ARRAYSIZE(pItem->szAcctID));
  109. // Handle the Account GUID
  110. cb = sizeof(SYNCMGRITEMID);
  111. if (FAILED(pAccount->GetProp(AP_UNIQUE_ID, (LPBYTE)&(pItem->offlineItem.ItemID), &cb)))
  112. {
  113. if (FAILED(CoCreateGuid(&(pItem->offlineItem.ItemID))) ||
  114. FAILED(pAccount->SetProp(AP_UNIQUE_ID, (LPBYTE)(&(pItem->offlineItem.ItemID)), sizeof(SYNCMGRITEMID))) ||
  115. FAILED(pAccount->SaveChanges()))
  116. ZeroMemory(&(pItem->offlineItem.ItemID), sizeof(SYNCMGRITEMID));
  117. }
  118. // Need to do something with this...
  119. pItem->offlineItem.wszStatus[0] = 0;
  120. if (SUCCEEDED(pAccount->GetAccountType(&accttype)))
  121. {
  122. if (ACCT_MAIL == accttype)
  123. pItem->offlineItem.hIcon = hicn[1];
  124. else
  125. pItem->offlineItem.hIcon = hicn[2];
  126. pItem->accttype = accttype;
  127. }
  128. else
  129. {
  130. pItem->offlineItem.hIcon = hicn[0];
  131. pItem->accttype = ACCT_LAST;
  132. }
  133. // Default to syncing the server, no folders synced by default
  134. if (SUCCEEDED(pAccount->GetPropDw(AP_AVAIL_OFFLINE, &dwAvail)))
  135. pItem->offlineItem.dwItemState = dwAvail ? SYNCMGRITEMSTATE_CHECKED : 0;
  136. else
  137. // Default to checked
  138. pItem->offlineItem.dwItemState = SYNCMGRITEMSTATE_CHECKED;
  139. // Default to not roaming for now...
  140. pItem->offlineItem.dwFlags = SYNCMGRITEM_HASPROPERTIES;
  141. pItem->dwUserID = dwUserID;
  142. pItem->offlineItem.cbSize = sizeof(SYNCMGRITEM);
  143. bAnything = TRUE;
  144. }
  145. MemFree(pwsz);
  146. pAccount->Release();
  147. }
  148. return bAnything;
  149. }
  150. STDMETHODIMP COneStopHandler::Initialize(DWORD dwReserved, DWORD dwSyncFlags,
  151. DWORD cbCookie, BYTE const*lpCookie)
  152. {
  153. HRESULT hr = S_FALSE;
  154. IImnEnumAccounts *pEnum = NULL;
  155. HKEY hkey = NULL;
  156. HICON hicn[3] = {NULL, NULL, NULL};
  157. DWORD dwIndex = 0;
  158. DWORD dwItemID = 0;
  159. ULONG ulCount = 0;
  160. ULONG ulTemp = 0;
  161. BOOL bAnything = FALSE;
  162. BOOL fMultiUser;
  163. TCHAR szSubKey[80];
  164. TCHAR szFullKey[MAX_PATH], szFullKey2[MAX_PATH];
  165. FILETIME dummy;
  166. DWORD cb;
  167. DWORD dwUserID;
  168. Assert(g_hLocRes);
  169. Assert(g_pAcctMan);
  170. if (!m_fInit)
  171. return E_FAIL;
  172. // Allocate memory for the list
  173. if (!(m_pOfflineHandlerItems = OHIL_Create()))
  174. {
  175. hr = E_OUTOFMEMORY;
  176. goto exit;
  177. }
  178. // Preload the icons for mail and news
  179. hicn[0] = LoadIcon(g_hLocRes, MAKEINTRESOURCE(idiMailNews));
  180. hicn[1] = LoadIcon(g_hLocRes, MAKEINTRESOURCE(idiMail));
  181. hicn[2] = LoadIcon(g_hLocRes, MAKEINTRESOURCE(idiNews));
  182. // Save the flags away - they are good for the life of this sync
  183. m_dwSyncFlags = dwSyncFlags;
  184. // Were we invoked by OE with the UserID of the current user?
  185. if (m_fInOE = (lpCookie && (sizeof(DWORD) == cbCookie)))
  186. {
  187. // We only care about the current user
  188. if (SUCCEEDED(g_pAcctMan->InitUser(NULL, NULL, 0)))
  189. {
  190. if (SUCCEEDED(g_pAcctMan->Enumerate(SRV_MAIL | SRV_NNTP, &pEnum)))
  191. {
  192. GetCurrentUserID(&m_dwUserID);
  193. CreateOneStopItems(pEnum, m_pOfflineHandlerItems, m_dwUserID, hicn);
  194. pEnum->Release();
  195. }
  196. }
  197. // Always want to handle if OE called us
  198. return S_OK;
  199. }
  200. // Need to enumerate all users in the current profile
  201. // Flush any changes
  202. SaveCurrentUserSettings();
  203. // Are there even any OE users in this profile to worry about?
  204. if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_PROFILE_ROOT, c_szRegLM, NULL, KEY_ENUMERATE_SUB_KEYS, &hkey))
  205. goto exit;
  206. hr = E_UNEXPECTED;
  207. cb = ARRAYSIZE(szSubKey);
  208. while (ERROR_SUCCESS == RegEnumKeyEx(hkey, dwIndex++, szSubKey, &cb, 0, NULL, NULL, &dummy))
  209. {
  210. cb = ARRAYSIZE(szSubKey);
  211. // Tell Acct Manager where to look
  212. wnsprintf(szFullKey, ARRAYSIZE(szFullKey), c_szPathFileFmt, c_szRegLM, szSubKey);
  213. wnsprintf(szFullKey2, ARRAYSIZE(szFullKey2), c_szPathFileFmt, szFullKey, c_szIAM);
  214. if (FAILED(g_pAcctMan->InitUser(NULL, szFullKey2, 0)))
  215. continue;
  216. // Does this user have any relevant accounts?
  217. if (FAILED(g_pAcctMan->GetAccountCount(ACCT_NEWS, &ulTemp)))
  218. continue;
  219. else
  220. {
  221. ulCount = ulTemp;
  222. if (FAILED(g_pAcctMan->GetAccountCount(ACCT_MAIL, &ulTemp)))
  223. continue;
  224. ulCount += ulTemp;
  225. if (0 == ulCount)
  226. {
  227. continue;
  228. }
  229. }
  230. if (FAILED(g_pAcctMan->Enumerate(SRV_MAIL | SRV_NNTP, &pEnum)))
  231. continue;
  232. GetCurrentUserID(&dwUserID);
  233. bAnything = CreateOneStopItems(pEnum, m_pOfflineHandlerItems, dwUserID, hicn) || bAnything;
  234. pEnum->Release();
  235. pEnum = NULL;
  236. }
  237. RegCloseKey(hkey);
  238. hkey = NULL;
  239. // If there is nothing to enumerate, don't worry about this sync event
  240. if (!bAnything)
  241. {
  242. hr = S_FALSE;
  243. goto exit;
  244. }
  245. return S_OK;
  246. exit:
  247. if (hkey)
  248. RegCloseKey(hkey);
  249. if (m_pOfflineHandlerItems)
  250. OHIL_Release(m_pOfflineHandlerItems);
  251. SafeRelease(pEnum);
  252. return hr;
  253. }
  254. STDMETHODIMP COneStopHandler::GetHandlerInfo(LPSYNCMGRHANDLERINFO *ppSyncMgrHandlerInfo)
  255. {
  256. SYNCMGRHANDLERINFO SMHI, *pSMHI;
  257. TCHAR szName[MAX_SYNCMGRHANDLERNAME];
  258. LPWSTR pwsz;
  259. if (!ppSyncMgrHandlerInfo)
  260. return E_INVALIDARG;
  261. *ppSyncMgrHandlerInfo = NULL;
  262. if (LoadIcon(g_hLocRes, MAKEINTRESOURCE(idiMailNews)) &&
  263. LoadString(g_hLocRes, idsAthena, szName, MAX_SYNCMGRHANDLERNAME))
  264. {
  265. if (MemAlloc((LPVOID *)&pSMHI, sizeof(SYNCMGRHANDLERINFO)))
  266. {
  267. #ifdef UNICODE
  268. StrCpyN(pSMHI->wszHandlerName, szName, ARRAYSIZE(pSMHI->wszHandlerName));
  269. #else
  270. if (pwsz = PszToUnicode(CP_ACP, szName))
  271. {
  272. StrCpyNW(pSMHI->wszHandlerName, pwsz, MAX_SYNCMGRHANDLERNAME);
  273. MemFree(pwsz);
  274. }
  275. else
  276. {
  277. MemFree(ppSyncMgrHandlerInfo);
  278. return E_OUTOFMEMORY;
  279. }
  280. #endif
  281. pSMHI->cbSize = sizeof(SYNCMGRHANDLERINFO);
  282. *ppSyncMgrHandlerInfo = pSMHI;
  283. return S_OK;
  284. }
  285. else
  286. return E_OUTOFMEMORY;
  287. }
  288. else
  289. return E_UNEXPECTED;
  290. }
  291. STDMETHODIMP COneStopHandler::EnumSyncMgrItems(ISyncMgrEnumItems** ppenumOffineItems)
  292. {
  293. if (m_pOfflineHandlerItems)
  294. {
  295. *ppenumOffineItems = new CEnumOfflineItems(m_pOfflineHandlerItems, 0);
  296. }
  297. else
  298. {
  299. *ppenumOffineItems = NULL;
  300. }
  301. return *ppenumOffineItems ? NOERROR: E_OUTOFMEMORY;
  302. }
  303. STDMETHODIMP COneStopHandler::GetItemObject(REFSYNCMGRITEMID ItemID, REFIID riid, void** ppv)
  304. {
  305. // Not implemented in OneStop v1 Spec
  306. return E_NOTIMPL;
  307. }
  308. STDMETHODIMP COneStopHandler::ShowProperties(HWND hwnd, REFSYNCMGRITEMID ItemID)
  309. {
  310. DWORD dwLastUser=0;
  311. SYNCMGRHANDLERITEM *pItem;
  312. BOOL fOkToEdit = TRUE;
  313. // We didn't provide any items, how can OneStop ask us about them?
  314. if (!m_pOfflineHandlerItems)
  315. return E_UNEXPECTED;
  316. pItem = m_pOfflineHandlerItems->pFirstOfflineItem;
  317. // This is slow, but shouldn't be many accounts...
  318. while (pItem)
  319. {
  320. if (IsEqualGUID(ItemID, pItem->offlineItem.ItemID))
  321. break;
  322. else
  323. pItem = pItem->pNextOfflineItem;
  324. }
  325. if (pItem)
  326. {
  327. if (dwLastUser != pItem->dwUserID)
  328. {
  329. if (fOkToEdit = SUCCEEDED(SwitchContext(pItem->dwUserID)))
  330. {
  331. dwLastUser = pItem->dwUserID;
  332. }
  333. }
  334. if (fOkToEdit)
  335. ShowPropSheet(hwnd, pItem->szAcctID, pItem->szAcctName, pItem->accttype);
  336. }
  337. else
  338. // Gave us an ItemID we don't know about!
  339. return E_INVALIDARG;
  340. return S_OK;
  341. }
  342. STDMETHODIMP COneStopHandler::SetProgressCallback(ISyncMgrSynchronizeCallback *lpCallBack)
  343. {
  344. LPSYNCMGRSYNCHRONIZECALLBACK pCallbackCurrent = m_pOfflineSynchronizeCallback;
  345. m_pOfflineSynchronizeCallback = lpCallBack;
  346. if (m_pOfflineSynchronizeCallback)
  347. m_pOfflineSynchronizeCallback->AddRef();
  348. if (pCallbackCurrent)
  349. pCallbackCurrent->Release();
  350. return NOERROR;
  351. }
  352. STDMETHODIMP COneStopHandler::PrepareForSync(ULONG cbNumItems, SYNCMGRITEMID* pItemIDs,
  353. HWND hwndParent, DWORD dwReserved)
  354. {
  355. HRESULT hr;
  356. SYNCMGRHANDLERITEM *pItem, *pPrev, *pTemp;
  357. IImnAccount *pAccount;
  358. DWORD dwLastUser;
  359. Assert(g_pAcctMan);
  360. if (cbNumItems > m_pOfflineHandlerItems->dwNumOfflineItems)
  361. {
  362. hr = E_INVALIDARG;
  363. goto exit;
  364. }
  365. if (!m_pOfflineHandlerItems)
  366. {
  367. hr = E_UNEXPECTED;
  368. goto exit;
  369. }
  370. if (!m_pOfflineSynchronizeCallback)
  371. {
  372. hr = E_FAIL;
  373. goto exit;
  374. }
  375. #if 0
  376. if (FAILED(hr = g_pSpooler->Init(NULL, FALSE)))
  377. {
  378. if (FACILITY_ITF == HRESULT_FACILITY(hr))
  379. hr = E_FAIL;
  380. goto exit;
  381. }
  382. #endif
  383. if (m_fInOE)
  384. dwLastUser = m_dwUserID;
  385. else
  386. dwLastUser = 0;
  387. pItem = m_pOfflineHandlerItems->pFirstOfflineItem;
  388. pPrev = NULL;
  389. // Go through all the servers that we know about
  390. while (pItem)
  391. {
  392. ULONG i=0;
  393. BOOL fOKToWrite = TRUE;
  394. // Is current server one that the user asked to sync?
  395. while (i < cbNumItems)
  396. {
  397. if (IsEqualGUID(pItemIDs[i], pItem->offlineItem.ItemID))
  398. break;
  399. else
  400. i++;
  401. }
  402. // No match?
  403. if (cbNumItems == i)
  404. pItem->offlineItem.dwItemState = 0;
  405. else
  406. pItem->offlineItem.dwItemState = 1;
  407. // Make sure the account manager is looking at the right user
  408. if (pItem->dwUserID != dwLastUser)
  409. {
  410. if (fOKToWrite = SUCCEEDED(InitUser(pItem->dwUserID)))
  411. dwLastUser = pItem->dwUserID;
  412. }
  413. // Only save changes if we know the registry is in sync with the account manager
  414. if (fOKToWrite)
  415. {
  416. if (SUCCEEDED(g_pAcctMan->FindAccount(AP_ACCOUNT_ID, pItem->szAcctID, &pAccount)))
  417. {
  418. if (SUCCEEDED(pAccount->SetPropDw(AP_AVAIL_OFFLINE, pItem->offlineItem.dwItemState)))
  419. pAccount->SaveChanges();
  420. pAccount->Release();
  421. }
  422. }
  423. // Can we delete this item from the list?
  424. if (0 == pItem->offlineItem.dwItemState)
  425. {
  426. if (pPrev)
  427. pPrev->pNextOfflineItem = pItem->pNextOfflineItem;
  428. else
  429. m_pOfflineHandlerItems->pFirstOfflineItem = pItem->pNextOfflineItem;
  430. m_pOfflineHandlerItems->dwNumOfflineItems--;
  431. // Move on to next item
  432. pTemp = pItem;
  433. pItem = pItem->pNextOfflineItem;
  434. MemFree(pTemp);
  435. }
  436. else
  437. {
  438. // Move on to next item
  439. pPrev = pItem;
  440. pItem = pItem->pNextOfflineItem;
  441. }
  442. }
  443. Assert(m_pOfflineHandlerItems->dwNumOfflineItems == cbNumItems);
  444. hr = S_OK;
  445. exit:
  446. m_pOfflineSynchronizeCallback->PrepareForSyncCompleted(hr);
  447. return hr;
  448. }
  449. STDMETHODIMP COneStopHandler::Synchronize(HWND hwndParent)
  450. {
  451. HRESULT hr;
  452. SYNCMGRHANDLERITEM *pItem;
  453. DWORD dwLastUser;
  454. Assert(g_pSpooler);
  455. if (!m_pOfflineSynchronizeCallback)
  456. {
  457. hr = E_FAIL;
  458. goto exit;
  459. }
  460. if (!m_pOfflineHandlerItems)
  461. {
  462. hr = E_UNEXPECTED;
  463. goto exit;
  464. }
  465. if (m_fInOE)
  466. dwLastUser = m_dwUserID;
  467. else
  468. dwLastUser = 0;
  469. pItem = m_pOfflineHandlerItems->pFirstOfflineItem;
  470. while (pItem)
  471. {
  472. BOOL fOkToSync = TRUE;
  473. if (dwLastUser != pItem->dwUserID)
  474. {
  475. if (fOkToSync = SUCCEEDED(SwitchContext(pItem->dwUserID)))
  476. dwLastUser = pItem->dwUserID;
  477. }
  478. if (fOkToSync)
  479. g_pSpooler->StartDelivery(hwndParent, pItem->szAcctID, FOLDERID_INVALID,
  480. DELIVER_UPDATE_ALL | DELIVER_NODIAL);
  481. pItem = pItem->pNextOfflineItem;
  482. }
  483. hr = S_OK;
  484. exit:
  485. m_pOfflineSynchronizeCallback->SynchronizeCompleted(hr);
  486. return hr;
  487. }
  488. STDMETHODIMP COneStopHandler::SetItemStatus(REFSYNCMGRITEMID ItemID, DWORD dwSyncMgrStatus)
  489. {
  490. return E_NOTIMPL;
  491. }
  492. STDMETHODIMP COneStopHandler::ShowError(HWND hWndParent, REFSYNCMGRERRORID ErrorID,
  493. ULONG *pcbNumItems, SYNCMGRITEMID **ppItemIDs)
  494. {
  495. // Can show any synchronization conflicts. Also gives a chance
  496. // to display any errors that occured during synchronization
  497. return E_NOTIMPL;
  498. }
  499. HRESULT SwitchContext(DWORD dwUserID)
  500. {
  501. HRESULT hr = S_OK;
  502. char szUsername[CCH_USERNAME_MAX_LENGTH];
  503. Assert(g_pAcctMan);
  504. if (UserIdToUsername(dwUserID, szUsername, ARRAYSIZE(szUsername)) &&
  505. SwitchToUser(szUsername, FALSE) )
  506. {
  507. // Reinitialize AcctMan
  508. if (FAILED(hr = g_pAcctMan->InitUser(NULL, NULL, 0)) &&
  509. FACILITY_ITF == HRESULT_FACILITY(hr) )
  510. hr = E_FAIL;
  511. }
  512. else
  513. hr = E_FAIL;
  514. return hr;
  515. }
  516. HRESULT InitUser(DWORD dwUserID)
  517. {
  518. HRESULT hr = S_OK;
  519. TCHAR szFullKey[MAX_PATH], szFullKey2[MAX_PATH];
  520. TCHAR szSubKey[80];
  521. Assert(g_pAcctMan);
  522. wnsprintf(szSubKey, ARRAYSIZE(szSubKey), "%08lx", dwUserID);
  523. // Figure out the full path to the Account Info for the current user
  524. wnsprintf(szFullKey, ARRAYSIZE(szFullKey), c_szPathFileFmt, c_szRegLM, szSubKey);
  525. wnsprintf(szFullKey2, ARRAYSIZE(szFullKey2), c_szPathFileFmt, szFullKey, c_szIAM);
  526. // Point account manager to an OE multiuser
  527. // Safe even if acct manager was already inited before - will reload accounts
  528. if (FAILED(hr = (g_pAcctMan->InitUser(NULL, szFullKey, 0))) &&
  529. (FACILITY_ITF == HRESULT_FACILITY(hr)))
  530. hr = E_FAIL;
  531. return hr;
  532. }