//=============================================================================
// The CMSInfoTool class encapsulates a tool (which can appear on the Tools
// menu or as part of a context sensitive menu).
//=============================================================================

#include "stdafx.h"
#include "msinfotool.h"
#include "wmiabstraction.h"

// Trick the resource.h include file into defining the _APS_NEXT_COMMAND_VALUE
// symbol. We can use this to add menu items dynamically.

#ifndef APSTUDIO_INVOKED
	#define APSTUDIO_INVOKED 1
	#include "resource.h"
	#undef APSTUDIO_INVOKED
#else
	#include "resource.h"
#endif

// An array of tools to be included (in addition to the registry tools).

MSITOOLINFO aInitialToolset[] = 
{
	{ IDS_CABCONTENTSNAME, 0, NULL, NULL, _T("explorer"), NULL, _T("%2") },
	{ IDS_DRWATSONNAME, 0, _T("%windir%\\system32\\drwtsn32.exe"), NULL, NULL, NULL, NULL },
	{ IDS_DXDIAGNAME, 0, _T("%windir%\\system32\\dxdiag.exe"), NULL, NULL, NULL, NULL },
	{ IDS_SIGVERIFNAME, 0, _T("%windir%\\system32\\sigverif.exe"), NULL, NULL, NULL, NULL },
	{ IDS_SYSTEMRESTNAME, 0, _T("%windir%\\system32\\restore\\rstrui.exe"), NULL, NULL, NULL, NULL },
	{ IDS_NETDIAGNAME, 0, _T("hcp://system/netdiag/dglogs.htm"), NULL, NULL, NULL, NULL },
	{ 0, 0, NULL, NULL, NULL, NULL, NULL }
};

//-----------------------------------------------------------------------------
// Check to see if the specified file (with path information) exists on
// the machine.
//-----------------------------------------------------------------------------

BOOL FileExists(const CString & strFile)
{
	WIN32_FIND_DATA finddata;
	HANDLE			h = FindFirstFile(strFile, &finddata);

	if (INVALID_HANDLE_VALUE != h)
	{
		FindClose(h);
		return TRUE;
	}

	return FALSE;
}

//-----------------------------------------------------------------------------
// Delete the map of tools.
//-----------------------------------------------------------------------------

void RemoveToolset(CMapWordToPtr & map)
{
	WORD			wCommand;
	CMSInfoTool *	pTool;

	for (POSITION pos = map.GetStartPosition(); pos != NULL; )
	{
		map.GetNextAssoc(pos, wCommand, (void * &) pTool);
		ASSERT(pTool);
		if (pTool)
			delete pTool;
	}

	map.RemoveAll();
}

//-----------------------------------------------------------------------------
// Load the map of tools from the specified registry location. This will be
// called in the case when there is no CAB file open.
//
// If an HKEY is passed in, it should be open, and it will be closed when
// the function is complete.
//-----------------------------------------------------------------------------

void LoadGlobalToolset(CMapWordToPtr & map, HKEY hkeyTools)
{
	RemoveToolset(map);

	// This should automatically put us out of the range of any menu IDs
	// stored in the resources.

	CMSInfoTool * pTool;
	DWORD dwID = _APS_NEXT_COMMAND_VALUE;
	DWORD dwIndex = 0;

	// Load the tools out of the array built into the code.

	for (MSITOOLINFO * pInitialTool = aInitialToolset; pInitialTool->m_szCommand || pInitialTool->m_szCABCommand; pInitialTool++)
	{
		pTool = new CMSInfoTool;
		if (pTool)
		{
			if (pTool->LoadGlobalFromMSITOOLINFO(dwID, pInitialTool, FALSE))
			{
				map.SetAt((WORD) dwID, (void *) pTool);
				dwID++;
			}
			else
				delete pTool;
		}
	}

	// Make sure we have an open handle for the tools section of the registry.

	HKEY hkeyBase = hkeyTools;
	if (hkeyBase == NULL)
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Shared Tools\\MSInfo\\Toolsets\\MSInfo"), 0, KEY_READ, &hkeyBase) != ERROR_SUCCESS)
			return;

	// Enumerate the subkeys of the tools key.

	HKEY	hkeySub;
	DWORD	dwChild = MAX_PATH;
	TCHAR	szChild[MAX_PATH];

	while (RegEnumKeyEx(hkeyBase, dwIndex++, szChild, &dwChild, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
	{
		if (RegOpenKeyEx(hkeyBase, szChild, 0, KEY_READ, &hkeySub) == ERROR_SUCCESS)
		{
			pTool = new CMSInfoTool;
			if (pTool)
			{
				if (pTool->LoadGlobalFromRegistry(hkeySub, dwID, FALSE, map))
				{
					map.SetAt((WORD) dwID, (void *) pTool);
					dwID++;
				}
				else
					delete pTool;
			}
			RegCloseKey(hkeySub);
		}

		dwChild = MAX_PATH;
	}

	RegCloseKey(hkeyBase);
}

//-----------------------------------------------------------------------------
// Load the map of tools from the specified registry location. This will be
// called in the case when there IS a CAB file open.
//
// If an HKEY is passed in, it should be open, and it will be closed when
// the function is complete.
//-----------------------------------------------------------------------------

void LoadGlobalToolsetWithOpenCAB(CMapWordToPtr & map, LPCTSTR szCABDir, HKEY hkeyTools)
{
	// This should automatically put us out of the range of any menu IDs
	// stored in the resources.

	RemoveToolset(map);

	CMSInfoTool *	pTool;
	DWORD			dwID = _APS_NEXT_COMMAND_VALUE;
	DWORD			dwIndex = 0;

	// Load the tools out of the array built into the code.

	for (MSITOOLINFO * pInitialTool = aInitialToolset; pInitialTool->m_szCommand || pInitialTool->m_szCABCommand; pInitialTool++)
	{
		pTool = new CMSInfoTool;
		if (pTool)
		{
			if (pTool->LoadGlobalFromMSITOOLINFO(dwID, pInitialTool, TRUE))
			{
				pTool->Replace(_T("%2"), szCABDir);
				map.SetAt((WORD) dwID, (void *) pTool);
				dwID++;
			}
			else
				delete pTool;
		}
	}

	// Make sure we have an open handle for the tools section of the registry.

	HKEY hkeyBase = hkeyTools;
	if (hkeyBase == NULL)
		if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Shared Tools\\MSInfo\\Tools"), 0, KEY_READ, &hkeyBase) != ERROR_SUCCESS)
			return;

	// Enumerate the subkeys of the tools key.

	HKEY	hkeySub;
	DWORD	dwChild = MAX_PATH;
	TCHAR	szChild[MAX_PATH];

	while (RegEnumKeyEx(hkeyBase, dwIndex++, szChild, &dwChild, NULL, NULL, NULL, NULL) == ERROR_SUCCESS)
	{
		if (RegOpenKeyEx(hkeyBase, szChild, 0, KEY_READ, &hkeySub) == ERROR_SUCCESS)
		{
			pTool = new CMSInfoTool;
			if (pTool)
			{
				if (pTool->LoadGlobalFromRegistry(hkeySub, dwID, TRUE, map))
				{
					pTool->Replace(_T("%2"), szCABDir);
					map.SetAt((WORD) dwID, (void *) pTool);
					dwID++;

					// If this tool is for CAB contents, and there is a cabextensions
					// string, then we want to look in the contents of the CAB for all
					// of the files with that extension. For each file we find, we should
					// insert a submenu item with the file name.

					CString strExtensions = pTool->GetCABExtensions();
					if (!strExtensions.IsEmpty())
					{
						CString strExtension = strExtensions; // TBD - allow for more than one
						
						CString strSearch(szCABDir);
						if (strSearch.Right(1) != CString(_T("\\")))
							strSearch += _T("\\");
						strSearch += CString(_T("*.")) + strExtension;

						WIN32_FIND_DATA finddata;
						HANDLE			hFindFile = FindFirstFile(strSearch, &finddata);

						if (INVALID_HANDLE_VALUE != hFindFile)
						{
							do
							{
								CMSInfoTool * pSubTool = pTool->CloneTool(dwID, finddata.cFileName);
								if (pSubTool)
								{
									pSubTool->Replace(_T("%1"), finddata.cFileName);
									map.SetAt((WORD) dwID, (void *) pSubTool);
									dwID++;
								}

							} while (FindNextFile(hFindFile, &finddata));

							FindClose(hFindFile);
						}
					}
				}
				else
					delete pTool;
			}
			RegCloseKey(hkeySub);
		}

		dwChild = MAX_PATH;
	}

	RegCloseKey(hkeyBase);
}

//-----------------------------------------------------------------------------
// Check to see if the specified tool exists on this machine.
//-----------------------------------------------------------------------------

BOOL ToolExists(const CString & strTool, const CString & strParameters)
{
	CString strWorking(strTool);

	// If the tool is MMC, we really want to look for the existence of
	// the parameter (the MSC file).

	CString strCheck(strTool);
	strCheck.MakeLower();
	if (strCheck.Find(_T("\\mmc.exe")) != -1)
		strWorking = strParameters;

	// If the tool is actually a HSC page (it starts with "hcp:") then we need
	// to change it into a file path (converting forward slashes to backslashes)
	// and prepend the helpctr path.

	if (strCheck.Find(_T("hcp:")) == 0)
	{
		TCHAR szHelpCtrPath[MAX_PATH];
		if (0 != ::ExpandEnvironmentStrings(_T("%windir%\\pchealth\\helpctr"), szHelpCtrPath, MAX_PATH))
		{
			CString strHelpCtrPath(szHelpCtrPath);
			strWorking.Replace(_T("hcp://"), _T("\\"));
			strWorking.Replace(_T("/"), _T("\\"));
			strWorking = strHelpCtrPath + strWorking;
		}
	}

	if (strWorking.Find(_T("\\")) != -1)
		return (FileExists(strWorking));

	// The command for the tool doesn't have path information in it. That
	// means we'll need to check all the directories in the path to see
	// if it exists.

	const DWORD dwBufferSize = MAX_PATH * 10;	// TBD - figure out the actual max
	LPTSTR		szPath = new TCHAR[dwBufferSize];
	BOOL		fFound = TRUE;	// better to show the tool incorrectly if there's an error
	CString		strCandidate;

	if (szPath && dwBufferSize > ExpandEnvironmentStrings(_T("%path%"), szPath, dwBufferSize))
	{
		CString strPath(szPath);

		fFound = FALSE;
		while (!strPath.IsEmpty())
		{
			strCandidate = strPath.SpanExcluding(_T(";"));
			if (strPath.GetLength() != strCandidate.GetLength())
				strPath = strPath.Right(strPath.GetLength() - strCandidate.GetLength() - 1);
			else
				strPath.Empty();

			if (strCandidate.Right(1) != CString(_T("\\")))
				strCandidate += _T("\\");
			strCandidate += strWorking;

			if (FileExists(strCandidate))
			{
				fFound = TRUE;
				break;
			}
		}
	}

	if (szPath)
		delete [] szPath;

	return fFound;
}

//=============================================================================
// CMSInfoTool Methods
//=============================================================================

//-----------------------------------------------------------------------------
// Load this tool from the specified registry key.
//-----------------------------------------------------------------------------

BOOL CMSInfoTool::LoadGlobalFromRegistry(HKEY hkeyTool, DWORD dwID, BOOL fCABOpen, CMapWordToPtr & map)
{
	TCHAR	szBuffer[MAX_PATH];
	DWORD	dwType, dwSize;

	// Read in the values from the specified registry key.

	LPCTSTR aszValueNames[] = { _T(""), _T("command"), _T("description"), _T("parameters"), _T("cabcommand"), _T("cabextensions"),  _T("cabparameters"), NULL };
	CString * apstrValues[] = { &m_strName, &m_strCommand, &m_strDescription, &m_strParameters, &m_strCABCommand, &m_strCABExtension, &m_strCABParameters, NULL };

	for (int i = 0; aszValueNames[i] && apstrValues[i]; i++)
	{
		dwSize = sizeof(TCHAR) * MAX_PATH;
		if (ERROR_SUCCESS == RegQueryValueEx(hkeyTool, aszValueNames[i], NULL, &dwType, (LPBYTE) szBuffer, &dwSize))
			*(apstrValues[i]) = szBuffer;
		else
		{
			if (_tcscmp(aszValueNames[i], _T("parameters")) == 0)
				if (ERROR_SUCCESS == RegQueryValueEx(hkeyTool, _T("param"), NULL, &dwType, (LPBYTE) szBuffer, &dwSize))
					*(apstrValues[i]) = szBuffer;
		}
	}

	m_dwID = dwID;

	if (m_strName.IsEmpty())
		return FALSE;

	// Look for the name of this tool in the map (don't want to add it twice).

	CMSInfoTool *	pTool;
	WORD			wCommand;

	for (POSITION pos = map.GetStartPosition(); pos != NULL; )
	{
		map.GetNextAssoc(pos, wCommand, (void * &) pTool);
		if (pTool && m_strName.CompareNoCase(pTool->m_strName) == 0)
			return FALSE;
	}

	// Special hack - don't include help center in the list of tools.

	CString strCommand(m_strCommand);
	strCommand.MakeLower();
	if (strCommand.Find(_T("helpctr.exe")) != -1)
		return FALSE;

	// Another special hack - need explorer.exe, not just explorer.
	
	if (m_strCABCommand.CompareNoCase(_T("explorer")) == 0)
		m_strCABCommand = _T("explorer.exe");

	// If a CAB has been opened, and there is a specific command for that
	// case, AND the command exists, then set the flag so we use that
	// command.
	//
	// Otherwise, check to see if the default command exists.

	m_fCABOpen = FALSE;
	if (fCABOpen && !m_strCABCommand.IsEmpty() && ToolExists(m_strCABCommand, m_strCABParameters))
		m_fCABOpen = TRUE;
	else if (m_strCommand.IsEmpty() || !ToolExists(m_strCommand, m_strParameters))
		return FALSE;

	return TRUE;
}

//-----------------------------------------------------------------------------
// Load this tool from the specified registry key.
//-----------------------------------------------------------------------------

BOOL CMSInfoTool::LoadGlobalFromMSITOOLINFO(DWORD dwID, MSITOOLINFO * pTool, BOOL fCABOpen)
{
	ASSERT(pTool);
	if (pTool == NULL)
		return FALSE;

	if (pTool->m_uiNameID != 0)
		m_strName.LoadString(pTool->m_uiNameID);
	else
		m_strName = pTool->m_szCommand;

	if (pTool->m_uiDescriptionID != 0)
		m_strDescription.LoadString(pTool->m_uiDescriptionID);
	else
		m_strDescription = pTool->m_szCommand;

	m_strCommand = pTool->m_szCommand;
	m_strParameters = pTool->m_szParams;
	m_strCABCommand = pTool->m_szCABCommand;
	m_strCABExtension = pTool->m_szCABExtension;
	m_strCABParameters = pTool->m_szCABParams;

	CString strCommand(m_strCommand);
	strCommand.MakeLower();
	if (strCommand.Find(_T("%windir%")) != -1)
	{
		TCHAR szBuffer[MAX_PATH];
		if (::ExpandEnvironmentStrings(m_strCommand, szBuffer, MAX_PATH))
			m_strCommand = szBuffer;
	}

	m_dwID = dwID;

	if (m_strName.IsEmpty())
		return FALSE;

	// Special hack - don't include help center in the list of tools.

	strCommand = m_strCommand;
	strCommand.MakeLower();
	if (strCommand.Find(_T("helpctr.exe")) != -1)
		return FALSE;

	// Another special hack - need explorer.exe, not just explorer.
	
	if (m_strCABCommand.CompareNoCase(_T("explorer")) == 0)
		m_strCABCommand = _T("explorer.exe");

	// If a CAB has been opened, and there is a specific command for that
	// case, AND the command exists, then set the flag so we use that
	// command.
	//
	// Otherwise, check to see if the default command exists.

	m_fCABOpen = FALSE;
	if (fCABOpen && !m_strCABCommand.IsEmpty() && ToolExists(m_strCABCommand, m_strCABParameters))
		m_fCABOpen = TRUE;
	else if (m_strCommand.IsEmpty() || !ToolExists(m_strCommand, m_strParameters))
		return FALSE;

	return TRUE;
}

//-----------------------------------------------------------------------------
// Execute should actually launch this tool.
//-----------------------------------------------------------------------------

void CMSInfoTool::Execute()
{
	if (m_fCABOpen)
		ShellExecute(NULL, NULL, m_strCABCommand, m_strCABParameters, NULL, SW_SHOWNORMAL);
	else
		ShellExecute(NULL, NULL, m_strCommand, m_strParameters, NULL, SW_SHOWNORMAL);
}

//-----------------------------------------------------------------------------
// Replace is used to convert fields in the command and parameters to actual
// values which make sense (i.e. "%2" is replaced with the CAB directory).
//-----------------------------------------------------------------------------

void CMSInfoTool::Replace(LPCTSTR szReplace, LPCTSTR szWith)
{
	if (m_fCABOpen)
	{
		StringReplace(m_strCABCommand, szReplace, szWith);
		StringReplace(m_strCABParameters, szReplace, szWith);
	}
}

//-----------------------------------------------------------------------------
// Make a copy of this tool, with the new ID.
//-----------------------------------------------------------------------------

CMSInfoTool * CMSInfoTool::CloneTool(DWORD dwID, LPCTSTR szName)
{
	CMSInfoTool * pNewTool = new CMSInfoTool;
	if (pNewTool)
	{
		this->m_fHasSubitems = TRUE;

		pNewTool->m_dwID = dwID;
		pNewTool->m_dwParentID = this->GetID();

		pNewTool->m_fCABOpen			= this->m_fCABOpen;
		pNewTool->m_strName				= szName;
		pNewTool->m_strCommand			= this->m_strCommand;
		pNewTool->m_strDescription		= this->m_strDescription;
		pNewTool->m_strParameters		= this->m_strParameters;
		pNewTool->m_strCABCommand		= this->m_strCABCommand;
		pNewTool->m_strCABExtension		= this->m_strCABExtension;
		pNewTool->m_strCABParameters	= this->m_strCABParameters;
	}

	return (pNewTool);
}

//-----------------------------------------------------------------------------
// Create the tool explicitly (note - this has very limited use at this point).
//-----------------------------------------------------------------------------

void CMSInfoTool::Create(DWORD dwID, BOOL fCABOnly, LPCTSTR szName, LPCTSTR szCommand, LPCTSTR szDesc, LPCTSTR szParam, LPCTSTR szCABCommand, LPCTSTR szCABExt, LPCTSTR szCABParam)
{
	m_dwID				= dwID;
	m_fCABOpen			= fCABOnly;
	m_strName			= szName;
	m_strCommand		= szCommand;
	m_strDescription	= szDesc;
	m_strParameters		= szParam;
	m_strCABCommand		= szCABCommand;
	m_strCABExtension	= szCABExt;
	m_strCABParameters	= szCABParam;
}