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.

983 lines
30 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation, 1999 - 1999
  6. //
  7. // File: helpdoc.cpp
  8. //
  9. //--------------------------------------------------------------------------
  10. /*
  11. * There are two ways by which help collection is recognized dirty. First is if a snapin
  12. * is added/removed or extension is enabled/disabled, but this is only for this instance
  13. * of console file.
  14. * Second is if the modification time of console file is different from modification time
  15. * of collection. This is because an author may have added/removed a snapin without bringing
  16. * up help and saves console file. So the modification time on console file is later than
  17. * collection. Next time he/she opens console file and brings help, the help collection is
  18. * regenerated.
  19. */
  20. // mmchelp.cpp : implmentation of the HelpTopics class
  21. //
  22. #include "stdafx.h"
  23. #include "strings.h"
  24. #include "helpdoc.h"
  25. #include "nodemgr.h"
  26. #include "regutil.h"
  27. #include "scopndcb.h"
  28. #ifdef DBG
  29. CTraceTag tagHelpCollection (_T("Help"), _T(".COL construction"));
  30. #endif
  31. SC ScGetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cchBaseName);
  32. BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2);
  33. HRESULT CHelpDoc::Initialize(HELPDOCINFO* pDocInfo)
  34. {
  35. ASSERT(pDocInfo != NULL);
  36. m_pDocInfo = pDocInfo;
  37. return BuildFilePath();
  38. }
  39. HRESULT CHelpDoc::BuildFilePath()
  40. {
  41. DECLARE_SC(sc, TEXT("CHelpDoc::BuildFilePath"));
  42. USES_CONVERSION;
  43. do // false loop
  44. {
  45. // Get temp directory
  46. DWORD dwRet = GetTempPath(countof(m_szFilePath), m_szFilePath);
  47. if (dwRet == 0 || dwRet > MAX_PATH)
  48. break;
  49. // Make sure that the temp path exists and it is a dir
  50. dwRet = GetFileAttributes(m_szFilePath);
  51. if ( (0xFFFFFFFF == dwRet) || !(FILE_ATTRIBUTE_DIRECTORY & dwRet) )
  52. break;
  53. // Get base name of console file (if no name use "default")
  54. WCHAR szBaseName[MAX_PATH];
  55. int cchBaseName = MAX_PATH;
  56. if (m_pDocInfo->m_pszFileName && m_pDocInfo->m_pszFileName[0])
  57. {
  58. TCHAR szShortFileName[MAX_PATH] = {0};
  59. if ( 0 == GetShortPathName( OLE2CT( m_pDocInfo->m_pszFileName ), szShortFileName, countof(szShortFileName)) )
  60. {
  61. sc = StringCchCopyW(szBaseName, cchBaseName, L"default"); // Does not need to be localized
  62. if(sc)
  63. return sc.ToHr();
  64. }
  65. else
  66. sc = ScGetBaseFileName( T2CW(szShortFileName), szBaseName, countof(szBaseName));
  67. if(sc)
  68. return sc.ToHr();
  69. }
  70. else
  71. {
  72. sc = StringCchCopyW(szBaseName, cchBaseName, L"default"); // Does not need to be localized
  73. if(sc)
  74. return sc.ToHr();
  75. }
  76. TCHAR* pszBaseName = OLE2T(szBaseName);
  77. // construct help file path
  78. sc = StringCchCat(m_szFilePath, countof(m_szFilePath), pszBaseName);
  79. if(sc)
  80. return sc.ToHr();
  81. sc = StringCchCat(m_szFilePath, countof(m_szFilePath), _T(".col"));
  82. if(sc)
  83. return sc.ToHr();
  84. return S_OK;
  85. } while (0);
  86. // clear path on failure
  87. m_szFilePath[0] = 0;
  88. return E_FAIL;
  89. }
  90. bool entry_title_comp(EntryPair* pE1, EntryPair* pE2)
  91. {
  92. return pE1->second < pE2->second;
  93. }
  94. //------------------------------------------------------------------------------
  95. // Enumerate the snapins in the snap-in cache. Call AddSnapInToList for each one.
  96. // Open the snap-ins registry key for AddSnapInToList to use. When all the
  97. // snap-ins have been added, sort the resulting entries by snap-in name.
  98. //------------------------------------------------------------------------------
  99. HRESULT CHelpDoc::CreateSnapInList()
  100. {
  101. DECLARE_SC(sc, TEXT("CHelpDoc::CreateSnapInList"));
  102. CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache();
  103. ASSERT(pSnapInsCache != NULL);
  104. m_entryMap.clear();
  105. m_entryList.clear();
  106. // open MMC\Snapins key
  107. sc = ScFromWin32 ( m_keySnapIns.Open(HKEY_LOCAL_MACHINE, SNAPINS_KEY, KEY_READ) );
  108. if (sc)
  109. return sc.ToHr();
  110. // mark all snapins which have external references
  111. sc = pSnapInsCache->ScMarkExternallyReferencedSnapins();
  112. if (sc)
  113. return sc.ToHr();
  114. // Add each snap-in and its static extensions to the list
  115. CSnapInsCache::iterator c_it;
  116. for (c_it = pSnapInsCache->begin(); c_it != pSnapInsCache->end(); ++c_it)
  117. {
  118. const CSnapIn *pSnapin = c_it->second;
  119. if (!pSnapin)
  120. return (sc = E_UNEXPECTED).ToHr();
  121. bool bIsExternallyReferenced = false;
  122. sc = pSnapin->ScTempState_IsExternallyReferenced( bIsExternallyReferenced );
  123. if (sc)
  124. return sc.ToHr();
  125. // skip if snapin is not externally referenced
  126. if ( !bIsExternallyReferenced )
  127. continue;
  128. AddSnapInToList(pSnapin->GetSnapInCLSID());
  129. // we do not need to add extensions, since they are in cache anyway
  130. // and must be marked as externally referenced, (so will be added by the code above)
  131. // but it is worth to assert that
  132. #ifdef DBG
  133. {
  134. CExtSI* pExt = pSnapin->GetExtensionSnapIn();
  135. while (pExt != NULL)
  136. {
  137. CSnapIn *pSnapin = pExt->GetSnapIn();
  138. sc = ScCheckPointers( pSnapin, E_UNEXPECTED );
  139. if (sc)
  140. {
  141. sc.TraceAndClear();
  142. break;
  143. }
  144. bool bExtensionExternallyReferenced = false;
  145. sc = pSnapin->ScTempState_IsExternallyReferenced( bExtensionExternallyReferenced );
  146. if (sc)
  147. {
  148. sc.TraceAndClear();
  149. break;
  150. }
  151. // assert it is in the cache and is marked properly
  152. CSnapInPtr spSnapin;
  153. ASSERT( SC(S_OK) == pSnapInsCache->ScFindSnapIn( pExt->GetCLSID(), &spSnapin ) );
  154. ASSERT( bExtensionExternallyReferenced );
  155. pExt = pExt->Next();
  156. }
  157. }
  158. #endif // DBG
  159. }
  160. m_keySnapIns.Close();
  161. // our snap-in set is now up to date
  162. pSnapInsCache->SetHelpCollectionDirty(false);
  163. // copy items from map to list container so they can be sorted
  164. EntryMap::iterator it;
  165. for (it = m_entryMap.begin(); it != m_entryMap.end(); it++ )
  166. {
  167. m_entryList.push_back(&(*it));
  168. }
  169. sort(m_entryList.begin(), m_entryList.end(), entry_title_comp);
  170. return sc.ToHr();
  171. }
  172. //-----------------------------------------------------------------
  173. // Add an entry to the snap-in list for the specified snap-in CLSID.
  174. // Then recursively add any dynamic-only extensions that are registered
  175. // to extend this snap-in. This list is indexed by snap-in CLSID to
  176. // speed up checking for duplicate snap-ins.
  177. //-----------------------------------------------------------------
  178. void CHelpDoc::AddSnapInToList(const CLSID& rclsid)
  179. {
  180. DECLARE_SC(sc, TEXT("CHelpDoc::AddSnapInToList"));
  181. // check if already included
  182. if (m_entryMap.find(rclsid) != m_entryMap.end())
  183. return;
  184. // open the snap-in key
  185. OLECHAR szCLSID[40];
  186. int iRet = StringFromGUID2(rclsid, szCLSID, countof(szCLSID));
  187. ASSERT(iRet != 0);
  188. USES_CONVERSION;
  189. CRegKeyEx keyItem;
  190. LONG lStat = keyItem.Open(m_keySnapIns, OLE2T(szCLSID), KEY_READ);
  191. if (lStat != ERROR_SUCCESS)
  192. return;
  193. // get the snap-in name
  194. WTL::CString strName;
  195. sc = ScGetSnapinNameFromRegistry (keyItem, strName);
  196. #ifdef DBG
  197. if (sc)
  198. {
  199. USES_CONVERSION;
  200. sc.SetSnapinName(W2T (szCLSID)); // only guid is valid ...
  201. TraceSnapinError(_T("Failure reading \"NameString\" value from registry"), sc);
  202. sc.Clear();
  203. }
  204. #endif // DBG
  205. // Add to snap-in list
  206. if (lStat == ERROR_SUCCESS)
  207. {
  208. wstring s(T2COLE(strName));
  209. m_entryMap[rclsid] = s;
  210. }
  211. // Get list of registered extensions
  212. CExtensionsCache ExtCache;
  213. HRESULT hr = MMCGetExtensionsForSnapIn(rclsid, ExtCache);
  214. ASSERT(SUCCEEDED(hr));
  215. if (hr != S_OK)
  216. return;
  217. // Pass each dynamic extension to AddSnapInToList
  218. // Note that the EXT_TYPE_DYNAMIC flag will be set for any extension
  219. // that is dynamic-only for at least one nodetype. It may also be a
  220. // static extension for another node type, so we don't check that the
  221. // EXT_TYPE_STATIC flag is not set.
  222. CExtensionsCacheIterator ExtIter(ExtCache);
  223. for (; ExtIter.IsEnd() == FALSE; ExtIter.Advance())
  224. {
  225. if (ExtIter.GetValue() & CExtSI::EXT_TYPE_DYNAMIC)
  226. {
  227. CLSID clsid = ExtIter.GetKey();
  228. AddSnapInToList(clsid);
  229. }
  230. }
  231. }
  232. //----------------------------------------------------------------------
  233. // Add a single file to a help collection. The file is added as a title
  234. // and if bAddFolder is specified a folder is also added.
  235. //----------------------------------------------------------------------
  236. HRESULT CHelpDoc::AddFileToCollection(
  237. LPCWSTR pszTitle,
  238. LPCWSTR pszFilePath,
  239. BOOL bAddFolder )
  240. {
  241. DECLARE_SC (sc, _T("CHelpDoc::AddFileToCollection"));
  242. /*
  243. * redirect the help file to the user's UI language, if necessary
  244. */
  245. WTL::CString strFilePath = pszFilePath;
  246. LANGID langid = ENGLANGID;
  247. sc = ScRedirectHelpFile (strFilePath, langid);
  248. if (sc)
  249. return (sc.ToHr());
  250. Trace (tagHelpCollection, _T("AddFileToCollection: %s - %s (langid=0x%04x)"), pszTitle, (LPCTSTR) strFilePath, langid);
  251. USES_CONVERSION;
  252. pszFilePath = T2CW (strFilePath);
  253. DWORD dwError = 0;
  254. m_spCollection->AddTitle (pszTitle, pszFilePath, pszFilePath, L"", L"",
  255. langid, FALSE, NULL, &dwError, TRUE, L"");
  256. if (dwError != 0)
  257. return ((sc = E_FAIL).ToHr());
  258. if (bAddFolder)
  259. {
  260. // Folder ID parameter has the form "=title"
  261. WCHAR szTitleEq[MAX_PATH+1];
  262. szTitleEq[0] = L'=';
  263. sc = StringCchCopyW(szTitleEq+1, countof(szTitleEq) -1, pszTitle);
  264. if(sc)
  265. return sc.ToHr();
  266. m_spCollection->AddFolder(szTitleEq, 1, &dwError, langid);
  267. if (dwError != 0)
  268. return ((sc = E_FAIL).ToHr());
  269. }
  270. return (sc.ToHr());
  271. }
  272. /*+-------------------------------------------------------------------------*
  273. * CHelpDoc::ScRedirectHelpFile
  274. *
  275. * This method is for MUI support. On MUI systems, where the user's UI
  276. * language is not US English, we will attempt to redirect the help file to
  277. *
  278. * <dir>\mui\<langid>\<helpfile>
  279. *
  280. * <dir> Takes one of two values: If the helpfile passed in is fully
  281. * qualified, <dir> is the supplied directory. If the helpfile
  282. * passed in is unqualified, then <dir> is %SystemRoot%\Help.
  283. * <langid> The langid of the user's UI language, formatted as %04x
  284. * <helpfile> The name of the original .chm file.
  285. *
  286. * This function returns:
  287. *
  288. * S_OK if the helpfile was successfully redirected
  289. * S_FALSE if the helpfile wasn't redirected
  290. *--------------------------------------------------------------------------*/
  291. SC CHelpDoc::ScRedirectHelpFile (
  292. WTL::CString& strHelpFile, /* I/O:help file (maybe redirected) */
  293. LANGID& langid) /* O:language ID of output help file */
  294. {
  295. DECLARE_SC (sc, _T("CHelpDoc::ScRedirectHelpFile"));
  296. typedef LANGID (WINAPI* GetUILangFunc)(void);
  297. static GetUILangFunc GetUserDefaultUILanguage_ = NULL;
  298. static GetUILangFunc GetSystemDefaultUILanguage_ = NULL;
  299. static bool fAttemptedGetProcAddress = false;
  300. /*
  301. * validate input
  302. */
  303. if (strHelpFile.IsEmpty())
  304. return (sc = E_FAIL);
  305. /*
  306. * assume no redirection is required
  307. */
  308. sc = S_FALSE;
  309. langid = ENGLANGID;
  310. Trace (tagHelpCollection, _T("Checking for redirection of %s"), (LPCTSTR) strHelpFile);
  311. /*
  312. * GetUser/SystemDefaultUILanguage are unsupported on systems < Win2K,
  313. * so load them dynamically
  314. */
  315. if (!fAttemptedGetProcAddress)
  316. {
  317. fAttemptedGetProcAddress = true;
  318. HMODULE hMod = GetModuleHandle (_T("kernel32.dll"));
  319. if (hMod)
  320. {
  321. GetUserDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetUserDefaultUILanguage");
  322. GetSystemDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetSystemDefaultUILanguage");
  323. }
  324. }
  325. /*
  326. * if we couldn't load the MUI APIs, don't redirect
  327. */
  328. if ((GetUserDefaultUILanguage_ == NULL) || (GetSystemDefaultUILanguage_ == NULL))
  329. {
  330. Trace (tagHelpCollection, _T("Couldn't load GetUser/SystemDefaultUILanguage, not redirecting"));
  331. return (sc);
  332. }
  333. /*
  334. * find out what languages the system and user are using
  335. */
  336. const LANGID langidUser = GetUserDefaultUILanguage_();
  337. const LANGID langidSystem = GetSystemDefaultUILanguage_();
  338. /*
  339. * we only redirect if we're running on MUI and MUI is always hosted on
  340. * the US English release, so if the system UI language isn't US English,
  341. * don't redirect
  342. */
  343. if (langidSystem != ENGLANGID)
  344. {
  345. langid = langidSystem;
  346. Trace (tagHelpCollection, _T("System UI language is not US English (0x%04x), not redirecting"), langidSystem);
  347. return (sc);
  348. }
  349. /*
  350. * if the user's language is US English, don't redirect
  351. */
  352. if (langidUser == ENGLANGID)
  353. {
  354. Trace (tagHelpCollection, _T("User's UI language is US English, not redirecting"));
  355. return (sc);
  356. }
  357. /*
  358. * the user's language is different from the default, see if we can
  359. * find a help file that matches the user's UI langugae
  360. */
  361. ASSERT (langidUser != langidSystem);
  362. WTL::CString strName;
  363. WTL::CString strPathPrefix;
  364. /*
  365. * look for a path seperator to see if this is a fully qualified filename
  366. */
  367. int iLastSep = strHelpFile.ReverseFind (_T('\\'));
  368. /*
  369. * if it's fully qualified, construct a MUI directory name, e.g.
  370. *
  371. * <path>\mui\<langid>\<filename>
  372. */
  373. if (iLastSep != -1)
  374. {
  375. strName = strHelpFile.Mid (iLastSep+1);
  376. strPathPrefix = strHelpFile.Left (iLastSep);
  377. }
  378. /*
  379. * otherwise, it's not fully qualified, default to %SystemRoot%\Help, e.g.
  380. *
  381. * %SystemRoot%\Help\mui\<langid>\<filename>
  382. */
  383. else
  384. {
  385. strName = strHelpFile;
  386. UINT cchBuffer = MAX_PATH;
  387. LPTSTR szBuffer = strPathPrefix.GetBuffer(cchBuffer);
  388. if(!szBuffer)
  389. return (sc = E_FAIL);
  390. ExpandEnvironmentStrings (_T("%SystemRoot%\\Help"), szBuffer, cchBuffer);
  391. strPathPrefix.ReleaseBuffer();
  392. }
  393. WTL::CString strRedirectedHelpFile;
  394. strRedirectedHelpFile.Format (_T("%s\\mui\\%04x\\%s"),
  395. (LPCTSTR) strPathPrefix,
  396. langidUser,
  397. (LPCTSTR) strName);
  398. /*
  399. * see if the redirected help file exists
  400. */
  401. DWORD dwAttr = GetFileAttributes (strRedirectedHelpFile);
  402. if ((dwAttr == 0xFFFFFFFF) ||
  403. (dwAttr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE)))
  404. {
  405. #ifdef DBG
  406. Trace (tagHelpCollection, _T("Attempting redirection to %s, %s"),
  407. (LPCTSTR) strRedirectedHelpFile,
  408. (dwAttr == 0xFFFFFFFF) ? _T("not found") :
  409. (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? _T("found as directory") :
  410. _T("file offline"));
  411. #endif
  412. return (sc);
  413. }
  414. /*
  415. * if we get here, we've found a help file that matches the user's UI
  416. * language; return it and the UI language ID
  417. */
  418. Trace (tagHelpCollection, _T("Help redirected to %s"), (LPCTSTR) strRedirectedHelpFile);
  419. strHelpFile = strRedirectedHelpFile;
  420. langid = langidUser;
  421. /*
  422. * we redirected, return S_OK
  423. */
  424. return (sc = S_OK);
  425. }
  426. //-------------------------------------------------------------------------------
  427. // Delete the current help file collection. First delete it as a collection, then
  428. // delete the file itself. It is possible that the file doesn't exist when this
  429. // is called, so it's not a failure if it can't be deleted.
  430. //-------------------------------------------------------------------------------
  431. void
  432. CHelpDoc::DeleteHelpFile()
  433. {
  434. // Delete existing help file
  435. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  436. FILE_ATTRIBUTE_NORMAL, NULL);
  437. if (hFile != INVALID_HANDLE_VALUE)
  438. {
  439. ::CloseHandle(hFile);
  440. IHHCollectionWrapperPtr spOldCollection;
  441. spOldCollection.CreateInstance(CLSID_HHCollectionWrapper);
  442. USES_CONVERSION;
  443. WCHAR* pszFilePath = T2OLE(m_szFilePath);
  444. DWORD dwError = spOldCollection->Open(pszFilePath);
  445. if (dwError == 0)
  446. spOldCollection->RemoveCollection(FALSE);
  447. ::DeleteFile(m_szFilePath);
  448. }
  449. }
  450. //----------------------------------------------------------------------------
  451. // Create a new help doc file for the current MMC console. This function
  452. // enumerates all of the snap-in's used in the console and all their possible
  453. // extension snap-ins. It queries each snap-in for a single help topic file and
  454. // any linked help files. These files are added to a collection file which
  455. // is then saved with the same base file name, creation time, and modification
  456. // time as the console file.
  457. //-----------------------------------------------------------------------------
  458. HRESULT CHelpDoc::CreateHelpFile()
  459. {
  460. DECLARE_SC(sc, TEXT("CHelpDoc::CreateHelpFile"));
  461. USES_CONVERSION;
  462. HelpCollectionEntrySet HelpFiles;
  463. DWORD dwError;
  464. ASSERT(m_spCollection == NULL);
  465. m_spCollection.CreateInstance(CLSID_HHCollectionWrapper);
  466. ASSERT(m_spCollection != NULL);
  467. if (m_spCollection == NULL)
  468. return E_FAIL;
  469. HRESULT hr = CreateSnapInList();
  470. if (hr != S_OK)
  471. return hr;
  472. IMallocPtr spIMalloc;
  473. hr = CoGetMalloc(MEMCTX_TASK, &spIMalloc);
  474. ASSERT(hr == S_OK);
  475. if (hr != S_OK)
  476. return hr;
  477. // Delete existing file before rebuilding it, or help files will
  478. // be appended to the existing files
  479. DeleteHelpFile();
  480. // open new collection file
  481. WCHAR* pszFilePath = T2OLE(m_szFilePath);
  482. dwError = m_spCollection->Open(pszFilePath);
  483. ASSERT(dwError == 0);
  484. if (dwError != 0)
  485. return E_FAIL;
  486. // Have collection automatically find linked files
  487. m_spCollection->SetFindMergedCHMS(TRUE);
  488. AddFileToCollection(L"mmc", T2CW(SC::GetHelpFile()), TRUE);
  489. /*
  490. * Build a set of unique help files provided by the snap-ins
  491. */
  492. EntryPtrList::iterator it;
  493. for (it = m_entryList.begin(); it != m_entryList.end(); ++it)
  494. {
  495. TRACE(_T("Help snap-in: %s\n"), (*it)->second.c_str());
  496. USES_CONVERSION;
  497. HRESULT hr;
  498. OLECHAR szHelpFilePath[MAX_PATH];
  499. const CLSID& clsid = (*it)->first;
  500. // Create an instance of the snap-in to query
  501. IUnknownPtr spIUnknown;
  502. hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC, IID_IUnknown, (void**)&spIUnknown);
  503. if (FAILED(hr))
  504. continue;
  505. // use either ISnapinHelp or ISnapinHelp2 to get the main topic file
  506. ISnapinHelpPtr spIHelp = spIUnknown;
  507. ISnapinHelp2Ptr spIHelp2 = spIUnknown;
  508. if (spIHelp == NULL && spIHelp2 == NULL)
  509. continue;
  510. LPWSTR pszHelpFile = NULL;
  511. hr = (spIHelp2 != NULL) ? spIHelp2->GetHelpTopic(&pszHelpFile) :
  512. spIHelp->GetHelpTopic(&pszHelpFile);
  513. if (hr == S_OK)
  514. {
  515. /*
  516. * Put this help file in the collection entry set. The
  517. * set will prevent duplicating help file names.
  518. */
  519. HelpFiles.insert (CHelpCollectionEntry (pszHelpFile, clsid));
  520. spIMalloc->Free(pszHelpFile);
  521. // if IsnapinHelp2, query for additional help files
  522. pszHelpFile = NULL;
  523. if (spIHelp2 == NULL ||
  524. spIHelp2->GetLinkedTopics(&pszHelpFile) != S_OK ||
  525. pszHelpFile == NULL)
  526. continue;
  527. // There may be multiple names separated by ';'s
  528. // Add each as a separate title.
  529. // Note: there is no call to AddFolder because linked files
  530. // do not appear in the TOC.
  531. WCHAR *pchStart = wcstok(pszHelpFile, L";");
  532. while (pchStart != NULL)
  533. {
  534. // Must use base file name as title ID
  535. WCHAR szTitleID[MAX_PATH];
  536. sc = ScGetBaseFileName(pchStart, szTitleID, countof(szTitleID));
  537. if(!sc.IsError())
  538. {
  539. AddFileToCollection(szTitleID, pchStart, FALSE);
  540. }
  541. // position to start of next string
  542. pchStart = wcstok(NULL, L";");
  543. }
  544. spIMalloc->Free(pszHelpFile);
  545. }
  546. }
  547. /*
  548. * Put all of the help files provided by the snap-ins in the help collection.
  549. */
  550. HelpCollectionEntrySet::iterator itHelpFile;
  551. for (itHelpFile = HelpFiles.begin(); itHelpFile != HelpFiles.end(); ++itHelpFile)
  552. {
  553. const CHelpCollectionEntry& file = *itHelpFile;
  554. AddFileToCollection(file.m_strCLSID.c_str(), file.m_strHelpFile.c_str(), TRUE);
  555. }
  556. dwError = m_spCollection->Save();
  557. ASSERT(dwError == 0);
  558. dwError = m_spCollection->Close();
  559. ASSERT(dwError == 0);
  560. // Force creation/modify times to match the console file
  561. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  562. FILE_ATTRIBUTE_NORMAL, NULL);
  563. ASSERT(hFile != INVALID_HANDLE_VALUE);
  564. if (hFile != INVALID_HANDLE_VALUE)
  565. {
  566. BOOL bStat = ::SetFileTime(hFile, &m_pDocInfo->m_ftimeCreate, NULL, &m_pDocInfo->m_ftimeModify);
  567. ASSERT(bStat);
  568. ::CloseHandle(hFile);
  569. ASSERT(IsHelpFileValid());
  570. }
  571. return S_OK;
  572. }
  573. //-----------------------------------------------------------------------------
  574. // Determine if the current help doc file is valid. A help file is valid if it
  575. // has the base file name, creation time, and modification time as the MMC
  576. // console doc file.
  577. //-----------------------------------------------------------------------------
  578. BOOL CHelpDoc::IsHelpFileValid()
  579. {
  580. // Try to open the help file
  581. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING,
  582. FILE_ATTRIBUTE_NORMAL, NULL);
  583. if (hFile == INVALID_HANDLE_VALUE)
  584. return FALSE;
  585. // Check file creation and modification times
  586. FILETIME ftimeCreate;
  587. FILETIME ftimeModify;
  588. BOOL bStat = ::GetFileTime(hFile, &ftimeCreate, NULL, &ftimeModify);
  589. ASSERT(bStat);
  590. ::CloseHandle(hFile);
  591. return MatchFileTimes(ftimeCreate,m_pDocInfo->m_ftimeCreate) &&
  592. MatchFileTimes(ftimeModify,m_pDocInfo->m_ftimeModify);
  593. }
  594. //--------------------------------------------------------------------------
  595. // If the current help doc file is valid then update its creation and
  596. // modification times to match the new doc info.
  597. //--------------------------------------------------------------------------
  598. HRESULT CHelpDoc::UpdateHelpFile(HELPDOCINFO* pNewDocInfo)
  599. {
  600. if (IsHelpFileValid())
  601. {
  602. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  603. FILE_ATTRIBUTE_NORMAL, NULL);
  604. if (hFile == INVALID_HANDLE_VALUE)
  605. return E_FAIL;
  606. BOOL bStat = ::SetFileTime(hFile, &pNewDocInfo->m_ftimeCreate, NULL, &pNewDocInfo->m_ftimeModify);
  607. ASSERT(bStat);
  608. ::CloseHandle(hFile);
  609. }
  610. return S_OK;
  611. }
  612. //------------------------------------------------------------------------
  613. // Delete the current help doc file
  614. //------------------------------------------------------------------------
  615. HRESULT CNodeCallback::OnDeleteHelpDoc(HELPDOCINFO* pCurDocInfo)
  616. {
  617. CHelpDoc HelpDoc;
  618. HRESULT hr = HelpDoc.Initialize(pCurDocInfo);
  619. if (FAILED(hr))
  620. return hr;
  621. HelpDoc.DeleteHelpFile();
  622. return S_OK;
  623. }
  624. CHelpCollectionEntry::CHelpCollectionEntry(LPOLESTR pwzHelpFile, const CLSID& clsid)
  625. {
  626. if (!IsPartOfString (m_strHelpFile, pwzHelpFile))
  627. m_strHelpFile.erase(); // see KB Q172398
  628. m_strHelpFile = pwzHelpFile;
  629. WCHAR szCLSID[40];
  630. StringFromGUID2 (clsid, szCLSID, countof(szCLSID));
  631. m_strCLSID.erase(); // see KB Q172398
  632. m_strCLSID = szCLSID;
  633. }
  634. // ----------------------------------------------------------------------
  635. // CNodeCallack method implementation
  636. // ----------------------------------------------------------------------
  637. //------------------------------------------------------------------------
  638. // Get the pathname of the help doc for an MMC console doc. If the current
  639. // help doc is valid and there are no snap-in changes, return the current
  640. // doc. Otherwise, create a new help doc and return it.
  641. //------------------------------------------------------------------------
  642. HRESULT CNodeCallback::OnGetHelpDoc(HELPDOCINFO* pHelpInfo, LPOLESTR* ppszHelpFile)
  643. {
  644. DECLARE_SC(sc, TEXT("CNodeCallback::OnGetHelpDoc"));
  645. CHelpDoc HelpDoc;
  646. sc = HelpDoc.Initialize(pHelpInfo);
  647. if (sc)
  648. return sc.ToHr();
  649. CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache();
  650. sc = ScCheckPointers(pSnapInsCache);
  651. if(sc)
  652. return sc.ToHr();
  653. // Rebuild file if snap-in set changed or current file is not up to date
  654. if (pSnapInsCache->IsHelpCollectionDirty() || !HelpDoc.IsHelpFileValid())
  655. {
  656. sc = HelpDoc.CreateHelpFile();
  657. if(sc)
  658. return sc.ToHr();
  659. }
  660. // if ok, allocate and return file path string (OLESTR)
  661. LPCTSTR szHelpDoc = HelpDoc.GetFilePath();
  662. sc = ScCheckPointers(szHelpDoc);
  663. if(sc)
  664. return sc.ToHr();
  665. int cchHelpFile = lstrlen(szHelpDoc) + 1;
  666. *ppszHelpFile = reinterpret_cast<LPOLESTR> (CoTaskMemAlloc(cchHelpFile * sizeof(wchar_t)));
  667. sc = ScCheckPointers(*ppszHelpFile, E_OUTOFMEMORY);
  668. if (sc)
  669. return sc.ToHr();
  670. USES_CONVERSION;
  671. sc = StringCchCopyW(*ppszHelpFile, cchHelpFile, T2COLE(HelpDoc.GetFilePath()));
  672. if(sc)
  673. return sc.ToHr();
  674. return sc.ToHr();
  675. }
  676. //+-------------------------------------------------------------------
  677. //
  678. // Member: CNodeCallback::DoesStandardSnapinHelpExist
  679. //
  680. // Synopsis: Given the selection context, see if Standard MMC style help
  681. // exists (snapin implements ISnapinHelp[2] interface.
  682. // If not we wantto put "Help On <Snapin> which is MMC1.0 legacy
  683. // help mechanism.
  684. //
  685. // Arguments: [hNode] - [in] the node selection context.
  686. // [bStandardHelpExists] - [out] Does standard help exists or not?
  687. //
  688. // Returns: HRESULT
  689. //
  690. //--------------------------------------------------------------------
  691. HRESULT
  692. CNodeCallback::DoesStandardSnapinHelpExist(HNODE hNode, bool& bStandardHelpExists)
  693. {
  694. DECLARE_SC(sc, TEXT("CNodeCallback::OnHasHelpDoc"));
  695. sc = ScCheckPointers( (void*) hNode);
  696. if (sc)
  697. return sc.ToHr();
  698. CNode *pNode = CNode::FromHandle(hNode);
  699. sc = ScCheckPointers(pNode, E_UNEXPECTED);
  700. if (sc)
  701. return sc.ToHr();
  702. bStandardHelpExists = false;
  703. // QI ComponentData for ISnapinHelp
  704. CMTNode* pMTNode = pNode->GetMTNode();
  705. sc = ScCheckPointers(pMTNode, E_UNEXPECTED);
  706. if(sc)
  707. return sc.ToHr();
  708. CComponentData* pCD = pMTNode->GetPrimaryComponentData();
  709. sc = ScCheckPointers(pCD, E_UNEXPECTED);
  710. if(sc)
  711. return sc.ToHr();
  712. IComponentData *pIComponentData = pCD->GetIComponentData();
  713. sc = ScCheckPointers(pIComponentData, E_UNEXPECTED);
  714. if (sc)
  715. return sc.ToHr();
  716. ISnapinHelp* pIHelp = NULL;
  717. sc = pIComponentData->QueryInterface(IID_ISnapinHelp, (void**)&pIHelp);
  718. // if no ISnapinHelp, try ISnapinHelp2
  719. if (sc)
  720. {
  721. sc = pIComponentData->QueryInterface(IID_ISnapinHelp2, (void**)&pIHelp);
  722. if (sc)
  723. {
  724. // no ISnapinHelp2 either
  725. sc.Clear(); // not an error.
  726. return sc.ToHr();
  727. }
  728. }
  729. // make sure we got a valid pointer
  730. sc = ScCheckPointers(pIHelp, E_UNEXPECTED);
  731. if(sc)
  732. {
  733. sc.Clear();
  734. return sc.ToHr();
  735. }
  736. bStandardHelpExists = true;
  737. pIHelp->Release();
  738. return (sc).ToHr();
  739. }
  740. //-----------------------------------------------------------------------
  741. // Update the current help doc file to match the new MMC console doc
  742. //-----------------------------------------------------------------------
  743. HRESULT CNodeCallback::OnUpdateHelpDoc(HELPDOCINFO* pCurDocInfo, HELPDOCINFO* pNewDocInfo)
  744. {
  745. CHelpDoc HelpDoc;
  746. HRESULT hr = HelpDoc.Initialize(pCurDocInfo);
  747. if (FAILED(hr))
  748. return hr;
  749. return HelpDoc.UpdateHelpFile(pNewDocInfo);
  750. }
  751. SC ScGetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cBaseName)
  752. {
  753. DECLARE_SC(sc, TEXT("GetBaseFileName"));
  754. sc = ScCheckPointers(pszFilePath, pszBaseName);
  755. if(sc)
  756. return sc;
  757. // Find last '\'
  758. LPCWSTR pszTemp = wcsrchr(pszFilePath, L'\\');
  759. // if no '\' found, find drive letter terminator':'
  760. if (pszTemp == NULL)
  761. pszTemp = wcsrchr(pszFilePath, L':');
  762. // if neither found, there is no path
  763. // else skip over last char of path
  764. if (pszTemp == NULL)
  765. pszTemp = pszFilePath;
  766. else
  767. pszTemp++;
  768. // find last '.' (assume that extension follows)
  769. WCHAR *pchExtn = wcsrchr(pszTemp, L'.');
  770. // How many chars excluding extension ?
  771. int cCnt = pchExtn ? (pchExtn - pszTemp) : wcslen(pszTemp);
  772. ASSERT(cBaseName > cCnt);
  773. if (cBaseName <= cCnt)
  774. return (sc = E_FAIL);
  775. // Copy to output buffer
  776. memcpy(pszBaseName, pszTemp, cCnt * sizeof(WCHAR));
  777. pszBaseName[cCnt] = L'\0';
  778. return sc;
  779. }
  780. //
  781. // Compare two file times. Two file times are a match if they
  782. // differ by no more than 2 seconds. This difference is allowed
  783. // because a FAT file system stores times with a 2 sec resolution.
  784. //
  785. inline BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2)
  786. {
  787. // file system time resolution (2 sec) in 100's of nanosecs
  788. const static LONGLONG FileTimeResolution = 20000000;
  789. LONGLONG& ll1 = *(LONGLONG*)&ftime1;
  790. LONGLONG& ll2 = *(LONGLONG*)&ftime2;
  791. return (abs(ll1 - ll2) <= FileTimeResolution);
  792. }