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.

803 lines
26 KiB

  1. /**********************************************************************/
  2. /** Microsoft Passport **/
  3. /** Copyright(c) Microsoft Corporation, 1999 - 2001 **/
  4. /**********************************************************************/
  5. /*
  6. nexusconfig.cpp
  7. FILE HISTORY:
  8. */
  9. // NexusConfig.cpp: implementation of the CNexusConfig class.
  10. //
  11. //////////////////////////////////////////////////////////////////////
  12. #include "stdafx.h"
  13. #include "NexusConfig.h"
  14. #include "PassportGuard.hpp"
  15. #include "helperfuncs.h"
  16. PassportLock CNexusConfig::m_ReadLock;
  17. #define ALT_ATTRIBUTE_SUFFIX L"Default"
  18. //////////////////////////////////////////////////////////////////////
  19. // Construction/Destruction
  20. //////////////////////////////////////////////////////////////////////
  21. // turn a char into it's hex value
  22. #define XTOI(x) (isalpha(x) ? (toupper(x)-'A'+10) : (x - '0'))
  23. //===========================================================================
  24. //
  25. // CNexusConfig
  26. //
  27. CNexusConfig::CNexusConfig() :
  28. m_defaultProfileSchema(NULL), m_defaultTicketSchema(NULL),
  29. m_valid(FALSE), m_szReason(NULL), m_refs(0)
  30. {
  31. }
  32. //===========================================================================
  33. //
  34. // ~CNexusConfig
  35. //
  36. CNexusConfig::~CNexusConfig()
  37. {
  38. // profile schemata
  39. if (!m_profileSchemata.empty())
  40. {
  41. BSTR2PS::iterator ita = m_profileSchemata.begin();
  42. for (; ita != m_profileSchemata.end(); ita++)
  43. {
  44. FREE_BSTR(ita->first);
  45. ita->second->Release();
  46. }
  47. m_profileSchemata.clear();
  48. }
  49. // ticket schemata
  50. if (!m_ticketSchemata.empty())
  51. {
  52. BSTR2TS::iterator ita = m_ticketSchemata.begin();
  53. for (; ita != m_ticketSchemata.end(); ita++)
  54. {
  55. FREE_BSTR(ita->first);
  56. ita->second->Release();
  57. }
  58. m_ticketSchemata.clear();
  59. }
  60. //
  61. if (!m_domainAttributes.empty())
  62. {
  63. BSTR2DA::iterator itc = m_domainAttributes.begin();
  64. for (; itc != m_domainAttributes.end(); itc++)
  65. {
  66. FREE_BSTR(itc->first);
  67. if (!itc->second->empty())
  68. {
  69. // Now we're deleting ATTRVALs
  70. ATTRMAP::iterator itd = itc->second->begin();
  71. for (; itd != itc->second->end(); itd++)
  72. {
  73. ATTRVAL* pAttrVal = itd->second;
  74. if(pAttrVal->bDoLCIDReplace)
  75. {
  76. FREE_BSTR(pAttrVal->bstrAttrVal);
  77. }
  78. else
  79. {
  80. LCID2ATTR::iterator ite = pAttrVal->pLCIDAttrMap->begin();
  81. for (;ite != pAttrVal->pLCIDAttrMap->end(); ite++)
  82. {
  83. FREE_BSTR(ite->second);
  84. }
  85. delete pAttrVal->pLCIDAttrMap;
  86. }
  87. FREE_BSTR(itd->first);
  88. delete itd->second;
  89. }
  90. }
  91. delete itc->second;
  92. }
  93. m_domainAttributes.clear();
  94. }
  95. if (m_szReason)
  96. FREE_BSTR(m_szReason);
  97. }
  98. //===========================================================================
  99. //
  100. // getFailureString
  101. //
  102. BSTR CNexusConfig::getFailureString()
  103. {
  104. if (m_valid)
  105. return NULL;
  106. return m_szReason;
  107. }
  108. //===========================================================================
  109. //
  110. // setReason
  111. //
  112. void CNexusConfig::setReason(LPTSTR reason)
  113. {
  114. if (m_szReason)
  115. FREE_BSTR(m_szReason);
  116. m_szReason = ALLOC_BSTR(reason);
  117. }
  118. //===========================================================================
  119. //
  120. // AddRef
  121. //
  122. CNexusConfig* CNexusConfig::AddRef()
  123. {
  124. InterlockedIncrement(&m_refs);
  125. return this;
  126. }
  127. //===========================================================================
  128. //
  129. // Release
  130. //
  131. void CNexusConfig::Release()
  132. {
  133. long refs = InterlockedDecrement(&m_refs);
  134. if (refs == 0)
  135. delete this;
  136. }
  137. //===========================================================================
  138. //
  139. // Read
  140. //
  141. BOOL CNexusConfig::Read(IXMLDocument* s)
  142. {
  143. //BUGBUG Put this here because having two threads in Read at the same
  144. // time is almost guaranteed to cause STL maps to hurl (heap
  145. // corruption, pointer nastiness, etc. Long term solution
  146. // is to move these to LKRHash tables as well.
  147. PassportGuard<PassportLock> readGuard(m_ReadLock);
  148. MSXML::IXMLDocumentPtr pDoc;
  149. MSXML::IXMLElementCollectionPtr pElts, pSchemas, pDomains, pAtts;
  150. MSXML::IXMLElementPtr pElt, pDom, pAtt;
  151. VARIANT iTopLevel, iSubNodes, iAtts;
  152. _bstr_t name(L"Name"), suffix(L"DomainSuffix"), lcidatt(L"lcid"), version(L"Version");
  153. LONG cTLN, cSN, cAtts;
  154. BSTR attribute = NULL, value = NULL;
  155. _bstr_t lcid;
  156. HRESULT hr = S_OK;
  157. try
  158. {
  159. pDoc = s;
  160. pElts = pDoc->root->children;
  161. m_bstrVersion = pDoc->root->getAttribute(version);
  162. VariantInit(&iTopLevel);
  163. iTopLevel.vt = VT_I4;
  164. cTLN = pElts->length;
  165. for (iTopLevel.lVal=0; iTopLevel.lVal < cTLN; iTopLevel.lVal++)
  166. {
  167. pElt = pElts->item(&iTopLevel);
  168. CComBSTR tagName;
  169. hr = pElt->get_tagName(&tagName);
  170. if(hr != S_OK)
  171. continue;
  172. if (!_wcsicmp(tagName,FOLDER_TICKET_SCHEMATA))
  173. {
  174. VariantInit(&iSubNodes);
  175. iSubNodes.vt = VT_I4;
  176. pSchemas = pElt->children;
  177. cSN = pSchemas->length;
  178. for (iSubNodes.lVal=0;iSubNodes.lVal < cSN;iSubNodes.lVal++)
  179. {
  180. pElt = pSchemas->item(&iSubNodes);
  181. // Read a schema
  182. // BUGBUG probably more efficient ways to handle this variant->BSTR issue
  183. BSTR schemaName = NULL;
  184. _bstr_t tmp = pElt->getAttribute(name);
  185. if (tmp.length() > 0)
  186. schemaName = ALLOC_BSTR(tmp);
  187. CTicketSchema *pSchema = new CTicketSchema();
  188. pSchema->AddRef();
  189. if (schemaName && pSchema)
  190. {
  191. BSTR2TS::value_type pMapVal(schemaName,pSchema);
  192. pSchema->ReadSchema(pElt);
  193. if (!_wcsicmp(schemaName,L"CORE"))
  194. m_defaultTicketSchema = pSchema;
  195. m_ticketSchemata.insert(pMapVal);
  196. }
  197. else
  198. {
  199. if (schemaName)
  200. FREE_BSTR(schemaName);
  201. if (pSchema)
  202. pSchema->Release();
  203. }
  204. }
  205. }
  206. else if (!_wcsicmp(tagName,FOLDER_PROFILE_SCHEMATA))
  207. {
  208. VariantInit(&iSubNodes);
  209. iSubNodes.vt = VT_I4;
  210. pSchemas = pElt->children;
  211. cSN = pSchemas->length;
  212. for (iSubNodes.lVal=0;iSubNodes.lVal < cSN;iSubNodes.lVal++)
  213. {
  214. pElt = pSchemas->item(&iSubNodes);
  215. // Read a schema
  216. // BUGBUG probably more efficient ways to handle this variant->BSTR issue
  217. BSTR schemaName = NULL;
  218. _bstr_t tmp = pElt->getAttribute(name);
  219. if (tmp.length() > 0)
  220. schemaName = ALLOC_BSTR(tmp);
  221. CProfileSchema *pSchema = new CProfileSchema();
  222. pSchema->AddRef();
  223. if (schemaName && pSchema)
  224. {
  225. BSTR2PS::value_type pMapVal(schemaName,pSchema);
  226. pSchema->Read(pElt);
  227. if (!_wcsicmp(schemaName,L"CORE"))
  228. m_defaultProfileSchema = pSchema;
  229. m_profileSchemata.insert(pMapVal);
  230. }
  231. else
  232. {
  233. if (schemaName)
  234. FREE_BSTR(schemaName);
  235. if (pSchema)
  236. pSchema->Release();
  237. }
  238. }
  239. }
  240. else if (!_wcsicmp(tagName,FOLDER_PASSPORT_NETWORK))
  241. {
  242. // individual domain attributes
  243. pElt = pElts->item(&iTopLevel);
  244. VariantInit(&iSubNodes);
  245. iSubNodes.vt = VT_I4;
  246. pDomains = pElt->children;
  247. cSN = pDomains->length;
  248. VariantInit(&iAtts);
  249. iAtts.vt = VT_I4;
  250. for (iSubNodes.lVal=0;iSubNodes.lVal < cSN;iSubNodes.lVal++)
  251. {
  252. pDom = pDomains->item(&iSubNodes);
  253. BSTR dname = NULL;
  254. _bstr_t rawdn = pDom->getAttribute(suffix);
  255. dname = ALLOC_BSTR(rawdn);
  256. if (!dname)
  257. continue;
  258. pAtts = pDom->children;
  259. cAtts = pAtts->length;
  260. // Add new hash table for this domain
  261. ATTRMAP *newsite = new ATTRMAP;
  262. if (!newsite)
  263. continue;
  264. BSTR2DA::value_type pNewSite(dname, newsite);
  265. m_domainAttributes.insert(pNewSite);
  266. for (iAtts.lVal = 0; iAtts.lVal < cAtts; iAtts.lVal++)
  267. {
  268. pAtt = pAtts->item(&iAtts);
  269. lcid = pAtt->getAttribute(lcidatt);
  270. bool bIsReplaceLcid = (_wcsicmp(lcid, L"lang_replace") == 0);
  271. pAtt->get_tagName(&attribute);
  272. pAtt->get_text(&value);
  273. TAKEOVER_BSTR(attribute);
  274. TAKEOVER_BSTR(value);
  275. if (attribute && value)
  276. {
  277. // Find or add the lcid->value map for this attr
  278. ATTRMAP::const_iterator lcit = newsite->find(attribute);
  279. ATTRVAL* attrval;
  280. if (lcit == newsite->end())
  281. {
  282. attrval = new ATTRVAL;
  283. if(attrval != NULL)
  284. {
  285. attrval->bDoLCIDReplace = bIsReplaceLcid;
  286. if(!bIsReplaceLcid)
  287. {
  288. attrval->pLCIDAttrMap = new LCID2ATTR;
  289. }
  290. else
  291. {
  292. attrval->bstrAttrVal = value;
  293. value = NULL;
  294. }
  295. ATTRMAP::value_type pAtt(attribute,attrval);
  296. newsite->insert(pAtt);
  297. attribute = NULL;
  298. }
  299. else
  300. {
  301. FREE_BSTR(attribute);
  302. attribute = NULL;
  303. FREE_BSTR(value);
  304. value = NULL;
  305. }
  306. }
  307. else
  308. {
  309. FREE_BSTR(attribute);
  310. attribute = NULL;
  311. attrval = lcit->second;
  312. }
  313. short iLcid = 0;
  314. if(!bIsReplaceLcid)
  315. {
  316. if (lcid.length() == 4)
  317. {
  318. LPWSTR szlcid = lcid;
  319. if (iswxdigit(szlcid[0]) && iswxdigit(szlcid[1]) &&
  320. iswxdigit(szlcid[2]) && iswxdigit(szlcid[3]))
  321. {
  322. iLcid =
  323. (XTOI(szlcid[0]) << 12) +
  324. (XTOI(szlcid[1]) << 8) +
  325. (XTOI(szlcid[2]) << 4) +
  326. (XTOI(szlcid[3]) << 0);
  327. }
  328. else
  329. {
  330. if (g_pAlert)
  331. g_pAlert->report(PassportAlertInterface::ERROR_TYPE,
  332. PM_LCID_ERROR, lcid);
  333. }
  334. }
  335. else if (lcid.length() == 2)
  336. {
  337. LPWSTR szlcid = lcid;
  338. if (iswxdigit(szlcid[0]) && iswxdigit(szlcid[1]))
  339. {
  340. iLcid =
  341. (XTOI(szlcid[0]) << 12) +
  342. (XTOI(szlcid[1]) << 8);
  343. }
  344. else
  345. {
  346. if (g_pAlert)
  347. g_pAlert->report(PassportAlertInterface::ERROR_TYPE,
  348. PM_LCID_ERROR, lcid);
  349. }
  350. }
  351. else if (lcid.length() > 0)
  352. {
  353. if (g_pAlert)
  354. g_pAlert->report(PassportAlertInterface::ERROR_TYPE,
  355. PM_LCID_ERROR, lcid);
  356. }
  357. LCID2ATTR::value_type lcAtt(iLcid,value);
  358. if (attrval && attrval->pLCIDAttrMap)
  359. {
  360. attrval->pLCIDAttrMap->insert(lcAtt);
  361. }
  362. else
  363. {
  364. FREE_BSTR(value);
  365. value = NULL;
  366. }
  367. }
  368. else
  369. {
  370. if (value)
  371. {
  372. FREE_BSTR(value);
  373. value = NULL;
  374. }
  375. }
  376. }
  377. else
  378. {
  379. if (attribute)
  380. {
  381. FREE_BSTR(attribute);
  382. attribute = NULL;
  383. }
  384. if (value)
  385. {
  386. FREE_BSTR(value);
  387. value = NULL;
  388. }
  389. }
  390. }
  391. }
  392. }
  393. }
  394. }
  395. catch (...)
  396. {
  397. // _bstr_t r = e.Description();
  398. if (attribute)
  399. {
  400. FREE_BSTR(attribute);
  401. attribute = NULL;
  402. }
  403. if (value)
  404. {
  405. FREE_BSTR(value);
  406. value = NULL;
  407. }
  408. return FALSE;
  409. }
  410. m_valid = TRUE;
  411. return TRUE;
  412. }
  413. //===========================================================================
  414. //
  415. // DomainExists
  416. //
  417. bool CNexusConfig::DomainExists(LPCWSTR domain)
  418. {
  419. BSTR2DA::const_iterator it = m_domainAttributes.find(_bstr_t(domain));
  420. return (it == m_domainAttributes.end() ? false : true);
  421. }
  422. //===========================================================================
  423. //
  424. // getDomainAttribute
  425. //
  426. void CNexusConfig::getDomainAttribute(
  427. LPCWSTR domain, // in
  428. LPCWSTR attr, // in
  429. DWORD valuebuflen, // in (chars, not bytes!)
  430. LPWSTR valuebuf, // out
  431. USHORT lcid, // in
  432. BOOL bNoAlt, // in if to try alternate attribute
  433. BOOL bExactLcid // if do exact lcid match
  434. )
  435. {
  436. BSTR2DA::const_iterator it;
  437. ATTRMAP::const_iterator daiter;
  438. ATTRVAL* pAttrVal;
  439. if(valuebuf == NULL)
  440. goto Cleanup;
  441. *valuebuf = L'\0';
  442. it = m_domainAttributes.find(_bstr_t(domain));
  443. if (it == m_domainAttributes.end())
  444. {
  445. // Not found
  446. goto Cleanup;
  447. }
  448. daiter = (*it).second->find(_bstr_t(attr));
  449. if (daiter == it->second->end())
  450. {
  451. // Not found
  452. goto Cleanup;
  453. }
  454. pAttrVal = daiter->second;
  455. if(pAttrVal->bDoLCIDReplace)
  456. {
  457. LPWSTR szSrc = pAttrVal->bstrAttrVal;
  458. LPWSTR szDst = valuebuf;
  459. DWORD dwLenRemaining = valuebuflen;
  460. while(*szSrc != L'\0' && dwLenRemaining != 0)
  461. {
  462. if(*szSrc == L'%')
  463. {
  464. szSrc++;
  465. switch((WCHAR)CharUpperW((LPWSTR)(*szSrc)))
  466. {
  467. case L'L':
  468. {
  469. WCHAR szLCID[32];
  470. _ultow(lcid, szLCID, 10);
  471. int nLength = lstrlenW(szLCID);
  472. for(int nIndex = 0;
  473. nIndex < nLength && dwLenRemaining != 0;
  474. nIndex++)
  475. {
  476. *(szDst++) = szLCID[nIndex];
  477. --dwLenRemaining;
  478. }
  479. szSrc++;
  480. }
  481. break;
  482. case L'C':
  483. {
  484. WCHAR szCharCode[3];
  485. //
  486. // TODO Insert code here to lookup char code
  487. // based on lcid.
  488. //
  489. lstrcpyW(szCharCode, L"EN");
  490. *(szDst++) = szCharCode[0];
  491. --dwLenRemaining;
  492. if(dwLenRemaining != 0)
  493. {
  494. *(szDst++) = szCharCode[1];
  495. --dwLenRemaining;
  496. }
  497. szSrc++;
  498. }
  499. break;
  500. default:
  501. *(szDst++) = L'%';
  502. --dwLenRemaining;
  503. if(dwLenRemaining != 0)
  504. {
  505. *(szDst++) = *(szSrc++);
  506. --dwLenRemaining;
  507. }
  508. break;
  509. }
  510. }
  511. else
  512. {
  513. *(szDst++) = *(szSrc++);
  514. dwLenRemaining--;
  515. }
  516. }
  517. if(dwLenRemaining != 0)
  518. *szDst = L'\0';
  519. else
  520. valuebuf[valuebuflen - 1] = L'\0';
  521. }
  522. else
  523. {
  524. LCID2ATTR* pLcidMap = pAttrVal->pLCIDAttrMap;
  525. LCID2ATTR::const_iterator lciter = pLcidMap->find(lcid);
  526. if (lciter == pLcidMap->end())
  527. {
  528. // try default lcid
  529. ATTRMAP::const_iterator defaultLcidIt;
  530. defaultLcidIt = (*it).second->find(_bstr_t(L"DEFAULTLCID"));
  531. if (defaultLcidIt != it->second->end())
  532. {
  533. ATTRVAL* pDefaultAttrVal;
  534. pDefaultAttrVal = (*defaultLcidIt).second;
  535. LCID2ATTR::const_iterator pDefaultLcidMapIt = pDefaultAttrVal->pLCIDAttrMap->find(0);
  536. if ((*pDefaultLcidMapIt).second && *((*pDefaultLcidMapIt).second))
  537. {
  538. LONG defaultLcid = 0;
  539. defaultLcid = FromHex((*pDefaultLcidMapIt).second);
  540. if (lcid == defaultLcid)
  541. {
  542. lciter = pLcidMap->find(0);
  543. }
  544. }
  545. }
  546. // BOOL bNoAlt, // in if to try alternate attribute
  547. // BOOL bExactLcid // if do exact lcid match
  548. if (lciter == pLcidMap->end() && !bNoAlt)
  549. {
  550. // try to find the alternative attribute by appending the suffix
  551. WCHAR wszAltAttr[MAX_PATH + 1];
  552. if( wnsprintf(wszAltAttr, MAX_PATH, L"%s%s", attr, ALT_ATTRIBUTE_SUFFIX ) > 0)
  553. {
  554. getDomainAttribute(domain, wszAltAttr, valuebuflen, valuebuf, lcid, TRUE, TRUE);
  555. if ( *valuebuf != 0 ) // found
  556. goto Cleanup;
  557. }
  558. }
  559. // Not found
  560. if (lciter == pLcidMap->end() && lcid != 0 && !bExactLcid)
  561. {
  562. lciter = pLcidMap->find(PRIMARYLANGID(lcid)); // primary language
  563. if (lciter == pLcidMap->end())
  564. {
  565. lciter = pLcidMap->find(0);
  566. if (lciter == pLcidMap->end())
  567. goto Cleanup;
  568. }
  569. }
  570. }
  571. if(lciter != pLcidMap->end())
  572. lstrcpynW(valuebuf, (*lciter).second, valuebuflen);
  573. }
  574. Cleanup:
  575. return;
  576. }
  577. //===========================================================================
  578. //
  579. // getProfileSchema
  580. //
  581. CProfileSchema* CNexusConfig::getProfileSchema(BSTR schemaName)
  582. {
  583. if (schemaName == NULL)
  584. return m_defaultProfileSchema;
  585. BSTR2PS::const_iterator it = m_profileSchemata.find(schemaName);
  586. if (it == m_profileSchemata.end())
  587. return NULL;
  588. else
  589. return (*it).second;
  590. }
  591. //===========================================================================
  592. //
  593. // getTicketSchema
  594. //
  595. CTicketSchema* CNexusConfig::getTicketSchema(BSTR schemaName)
  596. {
  597. if (schemaName == NULL)
  598. return m_defaultTicketSchema;
  599. BSTR2TS::const_iterator it = m_ticketSchemata.find(schemaName);
  600. if (it == m_ticketSchemata.end())
  601. return NULL;
  602. else
  603. return (*it).second;
  604. }
  605. //===========================================================================
  606. //
  607. // getDomains
  608. //
  609. LPCWSTR* CNexusConfig::getDomains(int *numDomains)
  610. {
  611. int i;
  612. if (!numDomains) return NULL;
  613. *numDomains = m_domainAttributes.size();
  614. if (*numDomains == 0)
  615. return NULL;
  616. LPCWSTR* retVal = new LPCWSTR[*numDomains];
  617. if (!retVal) return NULL;
  618. BSTR2DA::const_iterator itc = m_domainAttributes.begin();
  619. for (i = 0; itc != m_domainAttributes.end(); itc++, i++)
  620. {
  621. retVal[i] = itc->first;
  622. }
  623. return retVal;
  624. }
  625. //===========================================================================
  626. //
  627. // GetXMLInfo
  628. //
  629. BSTR CNexusConfig::GetXMLInfo()
  630. {
  631. return m_bstrVersion;
  632. }
  633. //===========================================================================
  634. //
  635. // Dump
  636. //
  637. void CNexusConfig::Dump(BSTR* pbstrDump)
  638. {
  639. if(pbstrDump == NULL)
  640. return;
  641. *pbstrDump = NULL;
  642. CComBSTR bstrDump;
  643. // because CComBSTR will throw an exception if a memory allocation fails
  644. // we need to wrap this code with a try/catch.
  645. try
  646. {
  647. BSTR2DA::const_iterator domainIterator;
  648. for(domainIterator = m_domainAttributes.begin();
  649. domainIterator != m_domainAttributes.end();
  650. domainIterator++)
  651. {
  652. ATTRMAP* pAttrMap = domainIterator->second;
  653. bstrDump += L"Domain: ";
  654. bstrDump += domainIterator->first;
  655. bstrDump += L"<BR><BR>";
  656. ATTRMAP::const_iterator attrIterator;
  657. for(attrIterator = pAttrMap->begin();
  658. attrIterator != pAttrMap->end();
  659. attrIterator++)
  660. {
  661. bstrDump += L"Attribute: ";
  662. bstrDump += attrIterator->first;
  663. bstrDump += L"<BR><BR>";
  664. ATTRVAL* pAttrVal = attrIterator->second;
  665. if(pAttrVal->bDoLCIDReplace)
  666. {
  667. bstrDump += L"LCID = lang_replace Value = ";
  668. bstrDump += pAttrVal->bstrAttrVal;
  669. bstrDump += L"<BR>";
  670. }
  671. else
  672. {
  673. LCID2ATTR* pLCIDMap = pAttrVal->pLCIDAttrMap;
  674. LCID2ATTR::const_iterator lcidIterator;
  675. for(lcidIterator = pLCIDMap->begin();
  676. lcidIterator != pLCIDMap->end();
  677. lcidIterator++)
  678. {
  679. WCHAR szBuf[32];
  680. bstrDump += L"LCID = ";
  681. bstrDump += _itow(lcidIterator->first, szBuf, 10);
  682. bstrDump += L" Value = ";
  683. bstrDump += lcidIterator->second;
  684. bstrDump += L"<BR>";
  685. }
  686. bstrDump += L"<BR>";
  687. }
  688. }
  689. bstrDump += L"<BR>";
  690. }
  691. *pbstrDump = bstrDump.Detach();
  692. }
  693. catch(...)
  694. {
  695. *pbstrDump = NULL;
  696. }
  697. }
  698. // EOF