//+-------------------------------------------------------------------------
//
// 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);
}