//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1999 - 1999 // // File: helpdoc.cpp // //-------------------------------------------------------------------------- /* * There are two ways by which help collection is recognized dirty. First is if a snapin * is added/removed or extension is enabled/disabled, but this is only for this instance * of console file. * Second is if the modification time of console file is different from modification time * of collection. This is because an author may have added/removed a snapin without bringing * up help and saves console file. So the modification time on console file is later than * collection. Next time he/she opens console file and brings help, the help collection is * regenerated. */ // mmchelp.cpp : implmentation of the HelpTopics class // #include "stdafx.h" #include "strings.h" #include "helpdoc.h" #include "nodemgr.h" #include "regutil.h" #include "scopndcb.h" #ifdef DBG CTraceTag tagHelpCollection (_T("Help"), _T(".COL construction")); #endif BOOL GetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cBaseName); BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2); HRESULT CHelpDoc::Initialize(HELPDOCINFO* pDocInfo) { ASSERT(pDocInfo != NULL); m_pDocInfo = pDocInfo; return BuildFilePath(); } HRESULT CHelpDoc::BuildFilePath() { USES_CONVERSION; do // false loop { // Get temp directory DWORD dwRet = GetTempPath(MAX_PATH, m_szFilePath); if (dwRet == 0 || dwRet > MAX_PATH) break; // Make sure that the temp path exists and it is a dir dwRet = GetFileAttributes(m_szFilePath); if ( (0xFFFFFFFF == dwRet) || !(FILE_ATTRIBUTE_DIRECTORY & dwRet) ) break; // Get base name of console file (if no name use "default") WCHAR szBaseName[MAX_PATH]; if (m_pDocInfo->m_pszFileName && m_pDocInfo->m_pszFileName[0]) { TCHAR szShortFileName[MAX_PATH] = {0}; if ( 0 == GetShortPathName( OLE2CT( m_pDocInfo->m_pszFileName ), szShortFileName, countof(szShortFileName) - 1 ) ) wcscpy(szBaseName, L"default"); // Does not need to be localized else GetBaseFileName( T2CW(szShortFileName), szBaseName, MAX_PATH); } else { wcscpy(szBaseName, L"default"); // Does not need to be localized } TCHAR* pszBaseName = OLE2T(szBaseName); if (lstrlen(m_szFilePath) + lstrlen(pszBaseName) >= MAX_PATH - 4) break; // construct help file path lstrcat(m_szFilePath, pszBaseName); lstrcat(m_szFilePath, _T(".col")); return S_OK; } while (0); // clear path on failure m_szFilePath[0] = 0; return E_FAIL; } bool entry_title_comp(EntryPair* pE1, EntryPair* pE2) { return pE1->second < pE2->second; } //------------------------------------------------------------------------------ // Enumerate the snapins in the snap-in cache. Call AddSnapInToList for each one. // Open the snap-ins registry key for AddSnapInToList to use. When all the // snap-ins have been added, sort the resulting entries by snap-in name. //------------------------------------------------------------------------------ HRESULT CHelpDoc::CreateSnapInList() { DECLARE_SC(sc, TEXT("CHelpDoc::CreateSnapInList")); CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache(); ASSERT(pSnapInsCache != NULL); m_entryMap.clear(); m_entryList.clear(); // open MMC\Snapins key sc = ScFromWin32 ( m_keySnapIns.Open(HKEY_LOCAL_MACHINE, SNAPINS_KEY, KEY_READ) ); if (sc) return sc.ToHr(); // mark all snapins which have external references sc = pSnapInsCache->ScMarkExternallyReferencedSnapins(); if (sc) return sc.ToHr(); // Add each snap-in and its static extensions to the list CSnapInsCache::iterator c_it; for (c_it = pSnapInsCache->begin(); c_it != pSnapInsCache->end(); ++c_it) { const CSnapIn *pSnapin = c_it->second; if (!pSnapin) return (sc = E_UNEXPECTED).ToHr(); bool bIsExternallyReferenced = false; sc = pSnapin->ScTempState_IsExternallyReferenced( bIsExternallyReferenced ); if (sc) return sc.ToHr(); // skip if snapin is not externally referenced if ( !bIsExternallyReferenced ) continue; AddSnapInToList(pSnapin->GetSnapInCLSID()); // we do not need to add extensions, since they are in cache anyway // and must be marked as externally referenced, (so will be added by the code above) // but it is worth to assert that #ifdef DBG { CExtSI* pExt = pSnapin->GetExtensionSnapIn(); while (pExt != NULL) { CSnapIn *pSnapin = pExt->GetSnapIn(); sc = ScCheckPointers( pSnapin, E_UNEXPECTED ); if (sc) { sc.TraceAndClear(); break; } bool bExtensionExternallyReferenced = false; sc = pSnapin->ScTempState_IsExternallyReferenced( bExtensionExternallyReferenced ); if (sc) { sc.TraceAndClear(); break; } // assert it is in the cache and is marked properly CSnapInPtr spSnapin; ASSERT( SC(S_OK) == pSnapInsCache->ScFindSnapIn( pExt->GetCLSID(), &spSnapin ) ); ASSERT( bExtensionExternallyReferenced ); pExt = pExt->Next(); } } #endif // DBG } m_keySnapIns.Close(); // our snap-in set is now up to date pSnapInsCache->SetHelpCollectionDirty(false); // copy items from map to list container so they can be sorted EntryMap::iterator it; for (it = m_entryMap.begin(); it != m_entryMap.end(); it++ ) { m_entryList.push_back(&(*it)); } sort(m_entryList.begin(), m_entryList.end(), entry_title_comp); return sc.ToHr(); } //----------------------------------------------------------------- // Add an entry to the snap-in list for the specified snap-in CLSID. // Then recursively add any dynamic-only extensions that are registered // to extend this snap-in. This list is indexed by snap-in CLSID to // speed up checking for duplicate snap-ins. //----------------------------------------------------------------- void CHelpDoc::AddSnapInToList(const CLSID& rclsid) { DECLARE_SC(sc, TEXT("CHelpDoc::AddSnapInToList")); // check if already included if (m_entryMap.find(rclsid) != m_entryMap.end()) return; // open the snap-in key OLECHAR szCLSID[40]; int iRet = StringFromGUID2(rclsid, szCLSID, countof(szCLSID)); ASSERT(iRet != 0); USES_CONVERSION; CRegKeyEx keyItem; LONG lStat = keyItem.Open(m_keySnapIns, OLE2T(szCLSID), KEY_READ); if (lStat != ERROR_SUCCESS) return; // get the snap-in name WTL::CString strName; sc = ScGetSnapinNameFromRegistry (keyItem, strName); #ifdef DBG if (sc) { USES_CONVERSION; sc.SetSnapinName(W2T (szCLSID)); // only guid is valid ... TraceSnapinError(_T("Failure reading \"NameString\" value from registry"), sc); sc.Clear(); } #endif // DBG // Add to snap-in list if (lStat == ERROR_SUCCESS) { wstring s(T2COLE(strName)); m_entryMap[rclsid] = s; } // Get list of registered extensions CExtensionsCache ExtCache; HRESULT hr = MMCGetExtensionsForSnapIn(rclsid, ExtCache); ASSERT(SUCCEEDED(hr)); if (hr != S_OK) return; // Pass each dynamic extension to AddSnapInToList // Note that the EXT_TYPE_DYNAMIC flag will be set for any extension // that is dynamic-only for at least one nodetype. It may also be a // static extension for another node type, so we don't check that the // EXT_TYPE_STATIC flag is not set. CExtensionsCacheIterator ExtIter(ExtCache); for (; ExtIter.IsEnd() == FALSE; ExtIter.Advance()) { if (ExtIter.GetValue() & CExtSI::EXT_TYPE_DYNAMIC) { CLSID clsid = ExtIter.GetKey(); AddSnapInToList(clsid); } } } //---------------------------------------------------------------------- // Add a single file to a help collection. The file is added as a title // and if bAddFolder is specified a folder is also added. //---------------------------------------------------------------------- HRESULT CHelpDoc::AddFileToCollection( LPCWSTR pszTitle, LPCWSTR pszFilePath, BOOL bAddFolder ) { DECLARE_SC (sc, _T("CHelpDoc::AddFileToCollection")); /* * redirect the help file to the user's UI language, if necessary */ WTL::CString strFilePath = pszFilePath; LANGID langid = ENGLANGID; sc = ScRedirectHelpFile (strFilePath, langid); if (sc) return (sc.ToHr()); Trace (tagHelpCollection, _T("AddFileToCollection: %s - %s (langid=0x%04x)"), pszTitle, (LPCTSTR) strFilePath, langid); USES_CONVERSION; pszFilePath = T2CW (strFilePath); DWORD dwError = 0; m_spCollection->AddTitle (pszTitle, pszFilePath, pszFilePath, L"", L"", langid, FALSE, NULL, &dwError, TRUE, L""); if (dwError != 0) return ((sc = E_FAIL).ToHr()); if (bAddFolder) { // Folder ID parameter has the form "=title" WCHAR szTitleEq[MAX_PATH+1]; szTitleEq[0] = L'='; wcscpy(szTitleEq+1, pszTitle); m_spCollection->AddFolder(szTitleEq, 1, &dwError, langid); if (dwError != 0) return ((sc = E_FAIL).ToHr()); } return (sc.ToHr()); } /*+-------------------------------------------------------------------------* * CHelpDoc::ScRedirectHelpFile * * This method is for MUI support. On MUI systems, where the user's UI * language is not US English, we will attempt to redirect the help file to * * \mui\\ * * Takes one of two values: If the helpfile passed in is fully * qualified, is the supplied directory. If the helpfile * passed in is unqualified, then is %SystemRoot%\Help. * The langid of the user's UI language, formatted as %04x * The name of the original .chm file. * * This function returns: * * S_OK if the helpfile was successfully redirected * S_FALSE if the helpfile wasn't redirected *--------------------------------------------------------------------------*/ SC CHelpDoc::ScRedirectHelpFile ( WTL::CString& strHelpFile, /* I/O:help file (maybe redirected) */ LANGID& langid) /* O:language ID of output help file */ { DECLARE_SC (sc, _T("CHelpDoc::ScRedirectHelpFile")); typedef LANGID (WINAPI* GetUILangFunc)(void); static GetUILangFunc GetUserDefaultUILanguage_ = NULL; static GetUILangFunc GetSystemDefaultUILanguage_ = NULL; static bool fAttemptedGetProcAddress = false; /* * validate input */ if (strHelpFile.IsEmpty()) return (sc = E_FAIL); /* * assume no redirection is required */ sc = S_FALSE; langid = ENGLANGID; Trace (tagHelpCollection, _T("Checking for redirection of %s"), (LPCTSTR) strHelpFile); /* * GetUser/SystemDefaultUILanguage are unsupported on systems < Win2K, * so load them dynamically */ if (!fAttemptedGetProcAddress) { fAttemptedGetProcAddress = true; HMODULE hMod = GetModuleHandle (_T("kernel32.dll")); if (hMod) { GetUserDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetUserDefaultUILanguage"); GetSystemDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetSystemDefaultUILanguage"); } } /* * if we couldn't load the MUI APIs, don't redirect */ if ((GetUserDefaultUILanguage_ == NULL) || (GetSystemDefaultUILanguage_ == NULL)) { Trace (tagHelpCollection, _T("Couldn't load GetUser/SystemDefaultUILanguage, not redirecting")); return (sc); } /* * find out what languages the system and user are using */ const LANGID langidUser = GetUserDefaultUILanguage_(); const LANGID langidSystem = GetSystemDefaultUILanguage_(); /* * we only redirect if we're running on MUI and MUI is always hosted on * the US English release, so if the system UI language isn't US English, * don't redirect */ if (langidSystem != ENGLANGID) { langid = langidSystem; Trace (tagHelpCollection, _T("System UI language is not US English (0x%04x), not redirecting"), langidSystem); return (sc); } /* * if the user's language is US English, don't redirect */ if (langidUser == ENGLANGID) { Trace (tagHelpCollection, _T("User's UI language is US English, not redirecting")); return (sc); } /* * the user's language is different from the default, see if we can * find a help file that matches the user's UI langugae */ ASSERT (langidUser != langidSystem); WTL::CString strName; WTL::CString strPathPrefix; /* * look for a path seperator to see if this is a fully qualified filename */ int iLastSep = strHelpFile.ReverseFind (_T('\\')); /* * if it's fully qualified, construct a MUI directory name, e.g. * * \mui\\ */ if (iLastSep != -1) { strName = strHelpFile.Mid (iLastSep+1); strPathPrefix = strHelpFile.Left (iLastSep); } /* * otherwise, it's not fully qualified, default to %SystemRoot%\Help, e.g. * * %SystemRoot%\Help\mui\\ */ else { strName = strHelpFile; ExpandEnvironmentStrings (_T("%SystemRoot%\\Help"), strPathPrefix.GetBuffer(MAX_PATH), MAX_PATH); strPathPrefix.ReleaseBuffer(); } WTL::CString strRedirectedHelpFile; strRedirectedHelpFile.Format (_T("%s\\mui\\%04x\\%s"), (LPCTSTR) strPathPrefix, langidUser, (LPCTSTR) strName); /* * see if the redirected help file exists */ DWORD dwAttr = GetFileAttributes (strRedirectedHelpFile); if ((dwAttr == 0xFFFFFFFF) || (dwAttr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE))) { #ifdef DBG Trace (tagHelpCollection, _T("Attempting redirection to %s, %s"), (LPCTSTR) strRedirectedHelpFile, (dwAttr == 0xFFFFFFFF) ? _T("not found") : (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? _T("found as directory") : _T("file offline")); #endif return (sc); } /* * if we get here, we've found a help file that matches the user's UI * language; return it and the UI language ID */ Trace (tagHelpCollection, _T("Help redirected to %s"), (LPCTSTR) strRedirectedHelpFile); strHelpFile = strRedirectedHelpFile; langid = langidUser; /* * we redirected, return S_OK */ return (sc = S_OK); } //------------------------------------------------------------------------------- // Delete the current help file collection. First delete it as a collection, then // delete the file itself. It is possible that the file doesn't exist when this // is called, so it's not a failure if it can't be deleted. //------------------------------------------------------------------------------- void CHelpDoc::DeleteHelpFile() { // Delete existing help file HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { ::CloseHandle(hFile); IHHCollectionWrapperPtr spOldCollection; spOldCollection.CreateInstance(CLSID_HHCollectionWrapper); USES_CONVERSION; WCHAR* pszFilePath = T2OLE(m_szFilePath); DWORD dwError = spOldCollection->Open(pszFilePath); if (dwError == 0) spOldCollection->RemoveCollection(FALSE); ::DeleteFile(m_szFilePath); } } //---------------------------------------------------------------------------- // Create a new help doc file for the current MMC console. This function // enumerates all of the snap-in's used in the console and all their possible // extension snap-ins. It queries each snap-in for a single help topic file and // any linked help files. These files are added to a collection file which // is then saved with the same base file name, creation time, and modification // time as the console file. //----------------------------------------------------------------------------- HRESULT CHelpDoc::CreateHelpFile() { USES_CONVERSION; HelpCollectionEntrySet HelpFiles; DWORD dwError; ASSERT(m_spCollection == NULL); m_spCollection.CreateInstance(CLSID_HHCollectionWrapper); ASSERT(m_spCollection != NULL); if (m_spCollection == NULL) return E_FAIL; HRESULT hr = CreateSnapInList(); if (hr != S_OK) return hr; IMallocPtr spIMalloc; hr = CoGetMalloc(MEMCTX_TASK, &spIMalloc); ASSERT(hr == S_OK); if (hr != S_OK) return hr; // Delete existing file before rebuilding it, or help files will // be appended to the existing files DeleteHelpFile(); // open new collection file WCHAR* pszFilePath = T2OLE(m_szFilePath); dwError = m_spCollection->Open(pszFilePath); ASSERT(dwError == 0); if (dwError != 0) return E_FAIL; // Have collection automatically find linked files m_spCollection->SetFindMergedCHMS(TRUE); AddFileToCollection(L"mmc", T2CW(SC::GetHelpFile()), TRUE); /* * Build a set of unique help files provided by the snap-ins */ EntryPtrList::iterator it; for (it = m_entryList.begin(); it != m_entryList.end(); ++it) { TRACE(_T("Help snap-in: %s\n"), (*it)->second.c_str()); USES_CONVERSION; HRESULT hr; OLECHAR szHelpFilePath[MAX_PATH]; const CLSID& clsid = (*it)->first; // Create an instance of the snap-in to query IUnknownPtr spIUnknown; hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC, IID_IUnknown, (void**)&spIUnknown); if (FAILED(hr)) continue; // use either ISnapinHelp or ISnapinHelp2 to get the main topic file ISnapinHelpPtr spIHelp = spIUnknown; ISnapinHelp2Ptr spIHelp2 = spIUnknown; if (spIHelp == NULL && spIHelp2 == NULL) continue; LPWSTR pszHelpFile = NULL; hr = (spIHelp2 != NULL) ? spIHelp2->GetHelpTopic(&pszHelpFile) : spIHelp->GetHelpTopic(&pszHelpFile); if (hr == S_OK) { /* * Put this help file in the collection entry set. The * set will prevent duplicating help file names. */ HelpFiles.insert (CHelpCollectionEntry (pszHelpFile, clsid)); spIMalloc->Free(pszHelpFile); // if IsnapinHelp2, query for additional help files pszHelpFile = NULL; if (spIHelp2 == NULL || spIHelp2->GetLinkedTopics(&pszHelpFile) != S_OK || pszHelpFile == NULL) continue; // There may be multiple names separated by ';'s // Add each as a separate title. // Note: there is no call to AddFolder because linked files // do not appear in the TOC. WCHAR *pchStart = wcstok(pszHelpFile, L";"); while (pchStart != NULL) { // Must use base file name as title ID WCHAR szTitleID[MAX_PATH]; if (GetBaseFileName(pchStart, szTitleID, MAX_PATH)) { AddFileToCollection(szTitleID, pchStart, FALSE); } // position to start of next string pchStart = wcstok(NULL, L";"); } spIMalloc->Free(pszHelpFile); } } /* * Put all of the help files provided by the snap-ins in the help collection. */ HelpCollectionEntrySet::iterator itHelpFile; for (itHelpFile = HelpFiles.begin(); itHelpFile != HelpFiles.end(); ++itHelpFile) { const CHelpCollectionEntry& file = *itHelpFile; AddFileToCollection(file.m_strCLSID.c_str(), file.m_strHelpFile.c_str(), TRUE); } dwError = m_spCollection->Save(); ASSERT(dwError == 0); dwError = m_spCollection->Close(); ASSERT(dwError == 0); // Force creation/modify times to match the console file HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ASSERT(hFile != INVALID_HANDLE_VALUE); if (hFile != INVALID_HANDLE_VALUE) { BOOL bStat = ::SetFileTime(hFile, &m_pDocInfo->m_ftimeCreate, NULL, &m_pDocInfo->m_ftimeModify); ASSERT(bStat); ::CloseHandle(hFile); ASSERT(IsHelpFileValid()); } return S_OK; } //----------------------------------------------------------------------------- // Determine if the current help doc file is valid. A help file is valid if it // has the base file name, creation time, and modification time as the MMC // console doc file. //----------------------------------------------------------------------------- BOOL CHelpDoc::IsHelpFileValid() { // Try to open the help file HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return FALSE; // Check file creation and modification times FILETIME ftimeCreate; FILETIME ftimeModify; BOOL bStat = ::GetFileTime(hFile, &ftimeCreate, NULL, &ftimeModify); ASSERT(bStat); ::CloseHandle(hFile); return MatchFileTimes(ftimeCreate,m_pDocInfo->m_ftimeCreate) && MatchFileTimes(ftimeModify,m_pDocInfo->m_ftimeModify); } //-------------------------------------------------------------------------- // If the current help doc file is valid then update its creation and // modification times to match the new doc info. //-------------------------------------------------------------------------- HRESULT CHelpDoc::UpdateHelpFile(HELPDOCINFO* pNewDocInfo) { if (IsHelpFileValid()) { HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return E_FAIL; BOOL bStat = ::SetFileTime(hFile, &pNewDocInfo->m_ftimeCreate, NULL, &pNewDocInfo->m_ftimeModify); ASSERT(bStat); ::CloseHandle(hFile); } return S_OK; } //------------------------------------------------------------------------ // Delete the current help doc file //------------------------------------------------------------------------ HRESULT CNodeCallback::OnDeleteHelpDoc(HELPDOCINFO* pCurDocInfo) { CHelpDoc HelpDoc; HRESULT hr = HelpDoc.Initialize(pCurDocInfo); if (FAILED(hr)) return hr; HelpDoc.DeleteHelpFile(); return S_OK; } CHelpCollectionEntry::CHelpCollectionEntry(LPOLESTR pwzHelpFile, const CLSID& clsid) { if (!IsPartOfString (m_strHelpFile, pwzHelpFile)) m_strHelpFile.erase(); // see KB Q172398 m_strHelpFile = pwzHelpFile; WCHAR szCLSID[40]; StringFromGUID2 (clsid, szCLSID, countof(szCLSID)); m_strCLSID.erase(); // see KB Q172398 m_strCLSID = szCLSID; } // ---------------------------------------------------------------------- // CNodeCallack method implementation // ---------------------------------------------------------------------- //------------------------------------------------------------------------ // Get the pathname of the help doc for an MMC console doc. If the current // help doc is valid and there are no snap-in changes, return the current // doc. Otherwise, create a new help doc and return it. //------------------------------------------------------------------------ HRESULT CNodeCallback::OnGetHelpDoc(HELPDOCINFO* pHelpInfo, LPOLESTR* ppszHelpFile) { CHelpDoc HelpDoc; HRESULT hr = HelpDoc.Initialize(pHelpInfo); if (FAILED(hr)) return hr; CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache(); ASSERT(pSnapInsCache != NULL); hr = S_OK; // Rebuild file if snap-in set changed or current file is not up to date if (pSnapInsCache->IsHelpCollectionDirty() || !HelpDoc.IsHelpFileValid()) { hr = HelpDoc.CreateHelpFile(); } // if ok, allocate and return file path string (OLESTR) if (SUCCEEDED(hr)) { *ppszHelpFile = reinterpret_cast (CoTaskMemAlloc((lstrlen(HelpDoc.GetFilePath()) + 1) * sizeof(wchar_t))); if (*ppszHelpFile == NULL) return E_OUTOFMEMORY; USES_CONVERSION; wcscpy(*ppszHelpFile, T2COLE(HelpDoc.GetFilePath())); } return hr; } //+------------------------------------------------------------------- // // Member: CNodeCallback::DoesStandardSnapinHelpExist // // Synopsis: Given the selection context, see if Standard MMC style help // exists (snapin implements ISnapinHelp[2] interface. // If not we wantto put "Help On which is MMC1.0 legacy // help mechanism. // // Arguments: [hNode] - [in] the node selection context. // [bStandardHelpExists] - [out] Does standard help exists or not? // // Returns: HRESULT // //-------------------------------------------------------------------- HRESULT CNodeCallback::DoesStandardSnapinHelpExist(HNODE hNode, bool& bStandardHelpExists) { DECLARE_SC(sc, TEXT("CNodeCallback::OnHasHelpDoc")); sc = ScCheckPointers( (void*) hNode); if (sc) return sc.ToHr(); CNode *pNode = CNode::FromHandle(hNode); sc = ScCheckPointers(pNode, E_UNEXPECTED); if (sc) return sc.ToHr(); bStandardHelpExists = false; // QI ComponentData for ISnapinHelp CMTNode* pMTNode = pNode->GetMTNode(); sc = ScCheckPointers(pMTNode, E_UNEXPECTED); if(sc) return sc.ToHr(); CComponentData* pCD = pMTNode->GetPrimaryComponentData(); sc = ScCheckPointers(pCD, E_UNEXPECTED); if(sc) return sc.ToHr(); IComponentData *pIComponentData = pCD->GetIComponentData(); sc = ScCheckPointers(pIComponentData, E_UNEXPECTED); if (sc) return sc.ToHr(); ISnapinHelp* pIHelp = NULL; sc = pIComponentData->QueryInterface(IID_ISnapinHelp, (void**)&pIHelp); // if no ISnapinHelp, try ISnapinHelp2 if (sc) { sc = pIComponentData->QueryInterface(IID_ISnapinHelp2, (void**)&pIHelp); if (sc) { // no ISnapinHelp2 either sc.Clear(); // not an error. return sc.ToHr(); } } // make sure we got a valid pointer sc = ScCheckPointers(pIHelp, E_UNEXPECTED); if(sc) { sc.Clear(); return sc.ToHr(); } bStandardHelpExists = true; pIHelp->Release(); return (sc).ToHr(); } //----------------------------------------------------------------------- // Update the current help doc file to match the new MMC console doc //----------------------------------------------------------------------- HRESULT CNodeCallback::OnUpdateHelpDoc(HELPDOCINFO* pCurDocInfo, HELPDOCINFO* pNewDocInfo) { CHelpDoc HelpDoc; HRESULT hr = HelpDoc.Initialize(pCurDocInfo); if (FAILED(hr)) return hr; return HelpDoc.UpdateHelpFile(pNewDocInfo); } BOOL GetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cBaseName) { ASSERT(pszFilePath != NULL && pszBaseName != NULL); // Find last '\' LPCWSTR pszTemp = wcsrchr(pszFilePath, L'\\'); // if no '\' found, find drive letter terminator':' if (pszTemp == NULL) pszTemp = wcsrchr(pszFilePath, L':'); // if neither found, there is no path // else skip over last char of path if (pszTemp == NULL) pszTemp = pszFilePath; else pszTemp++; // find last '.' (assume that extension follows) WCHAR *pchExtn = wcsrchr(pszTemp, L'.'); // How many chars excluding extension ? int cCnt = pchExtn ? (pchExtn - pszTemp) : wcslen(pszTemp); ASSERT(cBaseName > cCnt); if (cBaseName <= cCnt) return FALSE; // Copy to output buffer memcpy(pszBaseName, pszTemp, cCnt * sizeof(WCHAR)); pszBaseName[cCnt] = L'\0'; return TRUE; } // // Compare two file times. Two file times are a match if they // differ by no more than 2 seconds. This difference is allowed // because a FAT file system stores times with a 2 sec resolution. // inline BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2) { // file system time resolution (2 sec) in 100's of nanosecs const static LONGLONG FileTimeResolution = 20000000; LONGLONG& ll1 = *(LONGLONG*)&ftime1; LONGLONG& ll2 = *(LONGLONG*)&ftime2; return (abs(ll1 - ll2) <= FileTimeResolution); }