//	stubexe.cpp		A command line program which runs the appropriate version
//		of MSInfo, based on the registry settings
//
// History:	a-jsari		10/13/97
//
// Copyright (c) 1998-1999 Microsoft Corporation

#include <afx.h>
#include <afxwin.h>
#include <io.h>
#include <process.h>
#include <errno.h>
#include "StdAfx.h"
#include "Resource.h"

#include "StubExe.h"

#include "MSInfo.h"
#include "MSInfo_i.c"

//-----------------------------------------------------------------------------
// These global variables hold arguments passed as command line parameters.
//-----------------------------------------------------------------------------

CString strComputerParam(_T(""));
CString strCategoryParam(_T(""));
CString strNFOFileParam(_T(""));
CString strReportFileParam(_T(""));

#ifndef HRESULT
typedef long HRESULT;
#endif

//	For Windows 95, the maximum length of a command line is 1024 characters.
//	Not sure what it is for NT.
const int MAX_COMMAND_LINE	= 1024;

LPCTSTR		cszDefaultDirectory = _T("\\Microsoft Shared\\MSInfo\\");

LPCTSTR		cszRegistryRoot = _T("Software\\Microsoft\\Shared Tools\\MSInfo");
LPCTSTR		cszDirectoryKey = _T("Path");

LPCTSTR		cszWindowsRoot = _T("Software\\Microsoft\\Windows\\CurrentVersion");
LPCTSTR		cszCommonFilesKey  = _T("CommonFilesDir");

CException *g_pException = NULL;

//		Microsoft Management Console is the program that hosts MSInfo.
//		This is a definition so that we can take its size.
#define		cszProgram	_T("mmc.exe")

/*
 * ThrowErrorException -
 *
 * History:	a-jsari		10/14/97		Initial version.
 */
inline void ThrowErrorException()
{
	::g_pException = new CException;
	if (::g_pException == NULL) ::AfxThrowMemoryException();
	throw ::g_pException;
}

/*
 * CMSInfo - A class to encapsulate using the DLL's COM interface.
 *
 * History:	a-jsari		3/26/98		Initial version
 */
class CMSInfo {
public:
	CMSInfo();
	~CMSInfo();

	HRESULT nfo(LPCTSTR lpszParams);
    HRESULT report(LPCTSTR lpszParams);
	HRESULT s(LPCTSTR lpszParams);
	HRESULT SaveNFO();
	HRESULT SaveReport();

private:
	ISystemInfo		*m_pISystemInfo;
	HRESULT			m_hr;
};

/* 
 * CSystemExecutable - The class that implements finding and running an
 *		executable.
 *
 * History:	a-jsari		10/14/97		Initial version.
 */
class CSystemExecutable {
public:
	CSystemExecutable(LPTSTR szProgram);
	~CSystemExecutable() { DeleteStrings(); }
	void	Run();
	void	Find();
	void	ProcessCommandLine();

	//	Helper methods.
protected:

	void	DeleteStrings();
	void	FindFileOnSystem(CString &szFileName, CString &szDestination);

	//	Instance variables.
protected:
	CString		*m_pszPath;
	CString		*m_pszProgramName;
	CString		*m_pszCommandLine;
};

/*
 * CMSInfoExecutable - MSInfo-specific functions.
 *
 * History: a-jsari		10/15/97		Initial version
 */
class CMSInfoExecutable : public CSystemExecutable {
public:
	CMSInfoExecutable(LPTSTR szProgram);
	~CMSInfoExecutable() {}

	void	ProcessCommandLine();

private:
	void	DisplayHelp();
	void	DeleteStrings();
	void	FindSavedConsole();
	LPCTSTR	GetMSIParameter(LPCTSTR szCommand, CString & strParam);

	//	Instance variables
private:
	static const LPCTSTR	cszSavedConsole;
	CString					*m_pszSavedConsole;
};

const LPCTSTR CMSInfoExecutable::cszSavedConsole = _T("MSInfo32.msc");

/*
 * CMSInfo - Initialize COM and create our ISystemInfo object.
 *
 * History:	a-jsari		3/26/98		Initial version.
 */
CMSInfo::CMSInfo()
:m_pISystemInfo(NULL)
{
	HRESULT hr = CoInitialize(NULL);
	if (FAILED(hr)) ::AfxThrowUserException();

	m_hr = CoCreateInstance(CLSID_SystemInfo, NULL, CLSCTX_ALL, IID_ISystemInfo, (void **)&m_pISystemInfo);
}

LPCTSTR cszSeparators = _T(" \t,");

//-----------------------------------------------------------------------------
// Call the msinfo32.dll using COM to save an NFO file. Use the parameters
// parsed from the command line (categories, computer).
//-----------------------------------------------------------------------------

HRESULT CMSInfo::SaveNFO()
{
	CString strFilename(strNFOFileParam);
	CString strComputer(strComputerParam);
	CString strCategory(strCategoryParam);

	BSTR filename = strFilename.AllocSysString();
	BSTR computer = strComputer.AllocSysString();
	BSTR category = strCategory.AllocSysString();

	if (m_pISystemInfo)
		return m_pISystemInfo->MakeNFO(filename, computer, category);

	return E_NOTIMPL;
}

//-----------------------------------------------------------------------------
// Call the msinfo32.dll using COM to save a report. Use the parameters
// parsed from the command line (categories, computer).
//-----------------------------------------------------------------------------

HRESULT CMSInfo::SaveReport()
{
	CString strFilename(strReportFileParam);
	CString strComputer(strComputerParam);
	CString strCategory(strCategoryParam);

	BSTR filename = strFilename.AllocSysString();
	BSTR computer = strComputer.AllocSysString();
	BSTR category = strCategory.AllocSysString();

	if (m_pISystemInfo)
		return m_pISystemInfo->MakeReport(filename, computer, category);

	return E_NOTIMPL;
}

/*
 * make_nfo - Process our parameters and call our ISystemInfo pointer's make_nfo function.
 *
 * History:	a-jsari		3/26/98		Initial version.
 */

HRESULT CMSInfo::nfo(LPCTSTR lpszParams)
{
	CString	strBuffer = lpszParams;
	CString	strFilename;
	LPCTSTR	szComputer = NULL;
	int		iSpace;

	strBuffer.TrimLeft();
	iSpace = strBuffer.FindOneOf(cszSeparators);
	do {
		if (iSpace == -1) {
			if (strBuffer.IsEmpty())
				return E_INVALIDARG;
			strFilename = strBuffer;
		} else {
			strFilename = strBuffer.Left(iSpace);
			strBuffer = strBuffer.Mid(iSpace + 1);
			strBuffer.TrimLeft();
			if (strBuffer[0] == (TCHAR)',')
				strBuffer = strBuffer.Mid(1);
			strBuffer.TrimLeft();
			iSpace = strBuffer.FindOneOf(cszSeparators);
			if (iSpace != -1) {
				strBuffer.Left(iSpace);
				break;
			}
			szComputer = (LPCTSTR)strBuffer;
		}
	} while (FALSE);
	if (m_pISystemInfo != NULL)
		return m_pISystemInfo->make_nfo(const_cast<LPTSTR>((LPCTSTR)strFilename),
				const_cast<LPTSTR>(szComputer));
	return m_hr;
}

/*
 * report - Process our parameters and call our ISystemInfo's make_report function.
 *
 * History:	a-jsari		3/26/98		Initial version.
 */
HRESULT CMSInfo::report(LPCTSTR lpszParams)
{
	CString	strBuffer = lpszParams;
	CString	strFilename;
	CString	strComputer;
	LPCTSTR szComputer = NULL;
	LPCTSTR szCategory = NULL;
	int		iSpace;

	strBuffer.TrimLeft();
	iSpace = strBuffer.FindOneOf(cszSeparators);
	do {
		if (iSpace == -1) {
			if (strBuffer.IsEmpty())
				return E_INVALIDARG;
			strFilename = strBuffer;
		} else {
			strFilename = strBuffer.Left(iSpace);
			strBuffer = strBuffer.Mid(iSpace + 1);
			strBuffer.TrimLeft();
			if (strBuffer[0] == (TCHAR)',')
				strBuffer = strBuffer.Mid(1);
			strBuffer.TrimLeft();
			iSpace = strBuffer.FindOneOf(cszSeparators);
			if (iSpace == -1) {
				strComputer = strBuffer;
				szComputer = (LPCTSTR)strComputer;
				break;
			}
			strComputer = strBuffer.Left(iSpace);
			szComputer = (LPCTSTR)strComputer;
			strBuffer = strBuffer.Mid(iSpace + 1);
			strBuffer.TrimLeft();
			if (strBuffer[0] == (TCHAR)',')
				strBuffer = strBuffer.Mid(1);
			strBuffer.TrimLeft();
			iSpace = strBuffer.FindOneOf(cszSeparators);
			if (iSpace != -1) {
				strBuffer = strBuffer.Left(iSpace);
			}
			szCategory = (LPCTSTR)strBuffer;
		}
	} while (FALSE);
	if (m_pISystemInfo != NULL)
		return m_pISystemInfo->make_report(const_cast<LPTSTR>((LPCTSTR)strFilename),
				const_cast<LPTSTR>(szComputer), const_cast<LPTSTR>(szCategory));
	return m_hr;
}

/*
 * s - Process our parameters and call our ISystemInfo's make_nfo function.
 *
 * History: a-jsari		3/26/98		Initial version.
 */
HRESULT CMSInfo::s(LPCTSTR lpszParams)
{
	CString	strBuffer = lpszParams;
	int		iSpace;

	strBuffer.TrimLeft();
	iSpace = strBuffer.FindOneOf(_T(" \t"));
	if (iSpace != -1)
		strBuffer = strBuffer.Left(iSpace);
	if (!strBuffer.IsEmpty())
		m_hr = E_INVALIDARG;
	else {
		if (m_pISystemInfo != NULL)
			m_hr = m_pISystemInfo->make_nfo(const_cast<LPTSTR>((LPCTSTR)strBuffer), NULL);
	}
	return m_hr;
}

/*
 * ~CMSInfo - Uninitialize COM and delete our ISystemInfo object.
 *
 * History:	a-jsari		3/26/98		Initial version.
 */
CMSInfo::~CMSInfo()
{
	if (SUCCEEDED(m_hr))
		m_pISystemInfo->Release();
	CoUninitialize();
}

/*
 * CExecutable - Constructor which determines the type of the executable to
 *		be executed.
 *
 * History:	a-jsari		10/14/97		Initial version.
 */
CSystemExecutable::CSystemExecutable(LPTSTR szProgram)
:m_pszProgramName(new CString), m_pszPath(new CString), m_pszCommandLine(new CString)
{
	if (!(m_pszProgramName && m_pszPath && m_pszCommandLine)) AfxThrowMemoryException();
	*m_pszProgramName = szProgram;
}

/*
 * DeleteStrings - Delete all of the strings used by the object.  Used to free
 *		our memory before calling exec.
 *
 * History: a-jsari		10/15/97		Initial version
 */
void CSystemExecutable::DeleteStrings()
{
	delete m_pszPath;
	m_pszPath = NULL;
	delete m_pszProgramName;
	m_pszProgramName = NULL;
	delete m_pszCommandLine;
	m_pszCommandLine = NULL;
}

/*
 * FindFileOnSystem - We may eventually put code here to test multiple
 *		found copies and use the right one.  But probably not.
 *
 * History:	a-jsari		10/15/97		Stub version
 */
void CSystemExecutable::FindFileOnSystem(CString &szFileName,
		CString &szDestination)
{
	//	Not reached.
	CFileFind		FileFinder;
	BOOL			bFindResult;

	bFindResult = FileFinder.FindFile(szFileName);
	if (!bFindResult) ThrowErrorException();
	szDestination = FileFinder.GetFilePath();
#if 0
	//	Choose among all versions of the file?
	while (bFindResult) {
		FileFinder.FindNextFile();
	}
#endif
}

/* 
 * Find - Return a pointer to a string containing the full path
 *		to the MMC executable.
 *
 * History:	a-jsari		10/13/97		Initial version
 */
void CSystemExecutable::Find()
{
	UINT		uReturnSize;
	TCHAR		szSystemDirectory[MAX_PATH + 1];

	uReturnSize = GetSystemDirectory(szSystemDirectory, MAX_PATH);
	if (uReturnSize == 0) ThrowErrorException();
	if (uReturnSize > MAX_PATH) {
		//	Our buffer isn't big enough.  This code will never get called.
		AfxThrowResourceException();
	}
	*m_pszPath += szSystemDirectory;
	*m_pszPath += _T("\\") + *m_pszProgramName;
	if (_taccess(*m_pszPath, A_READ) < 0) {
		//	These may eventually want to be distinct exceptions.
		if (errno == ENOENT) {
			ThrowErrorException();
		} else {
			ASSERT(errno == EACCES);
			ThrowErrorException();
		}
	}
}

/*
 * Run - Call exec with the parameters we so meticulously collected.
 *
 * History:	a-jsari		10/15/97		Initial version.
 */
void CSystemExecutable::Run()
{
#if !defined(UNICODE)
	TCHAR	szPath[MAX_PATH + 1];
	TCHAR	szProgramName[MAX_PATH + 1];
	TCHAR	szCommandLine[MAX_COMMAND_LINE + 1];

	_tcscpy(szPath, (LPCTSTR)*m_pszPath);
	_tcscpy(szProgramName, (LPCTSTR)*m_pszProgramName);
	_tcscpy(szCommandLine, (LPCTSTR)*m_pszCommandLine);
	DeleteStrings();
	::_execlp(szPath, szProgramName, szCommandLine, 0);
	ThrowErrorException();
#else
	char	szPath[MAX_PATH + 1];
	char	szProgramName[MAX_PATH + 1];
	char	szCommandLine[MAX_COMMAND_LINE + 1];

	wcstombs(szPath, (LPCTSTR) *m_pszPath, MAX_PATH);
	wcstombs(szProgramName, (LPCTSTR) *m_pszProgramName, MAX_PATH);
	wcstombs(szCommandLine, (LPCTSTR) *m_pszCommandLine, MAX_COMMAND_LINE);

	DeleteStrings();
	::_execlp(szPath, szProgramName, szCommandLine, 0);
	ThrowErrorException();
#endif
}

/*
 * ProcessCommandLine - Pass all command line parameters to the called
 *		executable.
 *
 * History: a-jsari		10/14/97		Initial version
 */
void CSystemExecutable::ProcessCommandLine()
{
	*m_pszCommandLine = GetCommandLine();

	//	Skip over the first element in the line, which is the path to
	//	the current executable.  Preserve everything else.
	const int	FIND_NO_MATCH = -1;
	int			wIndex;

	m_pszCommandLine->TrimLeft();
	wIndex = m_pszCommandLine->FindOneOf(_T("\" \t\n"));
	if ((*m_pszCommandLine)[wIndex] == '"') {
		//	This is the primary, if not guaranteed method.
		*m_pszCommandLine = m_pszCommandLine->Right(m_pszCommandLine->GetLength() - (wIndex + 1));
		wIndex = m_pszCommandLine->Find('"');
		*m_pszCommandLine = m_pszCommandLine->Right(m_pszCommandLine->GetLength() - (wIndex + 1));
	} else if (wIndex == FIND_NO_MATCH) {
		*m_pszCommandLine = _T("");
	} else {
		*m_pszCommandLine = m_pszCommandLine->Right(m_pszCommandLine->GetLength() - (wIndex + 1));
	}
}

/*
 * CMSInfoExecutable - Just pass all parameters to the base constructor.
 *
 * History: a-jsari		10/15/97		Initial version
 */
CMSInfoExecutable::CMSInfoExecutable(LPTSTR szProgram)
:CSystemExecutable(szProgram), m_pszSavedConsole(new CString)
{
	if (m_pszSavedConsole == NULL) AfxThrowMemoryException();
}

//-----------------------------------------------------------------------------
// This function is used to get a parameter from the command line. The first
// character may be one or more whitespace, or a ":" or a "=". After that, the
// string is read until a whitespace character is read outside of quotes.
//
// Return the pointer to the character location where we stopped reading.
//-----------------------------------------------------------------------------

LPCTSTR CMSInfoExecutable::GetMSIParameter(LPCTSTR szCommand, CString & strParam)
{
	strParam.Empty();
	if (!szCommand)
		return NULL;

	// Advance past any leading whitespace, ':' or '='.

	while (*szCommand && (*szCommand == _T(' ') || *szCommand == _T('\t') || *szCommand == _T(':') || *szCommand == _T('=')))
		szCommand++;

	if (*szCommand == _T('\0'))
		return szCommand;

	// Now read the parameter in until the end of the string or a non-quoted
	// whitespace is found. Remove the quote marks.

	BOOL fInQuote = FALSE;
	while (*szCommand)
	{
		if (!fInQuote && (*szCommand == _T(' ') || *szCommand == _T('\t')))
			break;

		if (*szCommand == _T('"'))
		{
			fInQuote = !fInQuote;
			szCommand++;
			continue;
		}

		strParam += *szCommand++;
	}

	return szCommand;
}

//-----------------------------------------------------------------------------
// This function reads the command line, looking for parameters we know how to
// handle. Anything we don't know about, we assemble into a new command line
// to pass to MMC when we launch MSInfo (if we launch it).
//-----------------------------------------------------------------------------

void CMSInfoExecutable::ProcessCommandLine()
{
	CSystemExecutable::ProcessCommandLine();
	FindSavedConsole();
	
	CString strNewCommandLine;
	int iLine = 0;

	// Process the items on the command line. If we recognize the flag,
	// save the value for use later. Otherwise add it to the command line
	// we'll pass to the snapin.

	while (m_pszCommandLine->GetLength() > 0) 
	{
		// Remove the part of the command line we just processed.

		*m_pszCommandLine = m_pszCommandLine->Mid(iLine);
		iLine = m_pszCommandLine->FindOneOf(_T(" \t")) + 1;

		if (iLine == 0)
			iLine = m_pszCommandLine->GetLength();

		// See if the first char is a command line switch.

		TCHAR tcFirst = (*m_pszCommandLine)[0];
		if (tcFirst == (TCHAR)'/' || tcFirst == (TCHAR)'-') 
		{
			LPCTSTR pString = *m_pszCommandLine;
			++pString;

			// This is a command line switch - see if we recognize it.

			if (::_tcsnicmp(pString, _T("?"), 1) == 0) 
			{
				DisplayHelp();
				exit(0);
			}
			else if (::_tcsnicmp(pString, _T("report"), 6) == 0)
			{
				LPCTSTR szEnd = GetMSIParameter(pString + 6, strReportFileParam);
				szEnd++;
				*m_pszCommandLine = szEnd;
				iLine = 0;
				continue;
			}
			else if (::_tcsnicmp(pString, _T("s"), 1) == 0) 
			{
				LPCTSTR szEnd = GetMSIParameter(pString + 1, strNFOFileParam);
				szEnd++;
				*m_pszCommandLine = szEnd;
				iLine = 0;
				continue;
			} 
			else if (::_tcsnicmp(pString, _T("nfo"), 3) == 0) 
			{
				LPCTSTR szEnd = GetMSIParameter(pString + 3, strNFOFileParam);
				szEnd++;
				*m_pszCommandLine = szEnd;
				iLine = 0;
				continue;
			}
			else if (::_tcsnicmp(pString, _T("computer"), 8) == 0)
			{
				LPCTSTR szEnd = GetMSIParameter(pString + 8, strComputerParam);
				szEnd++;
				*m_pszCommandLine = szEnd;
				iLine = 0;
				continue;
			}
			else if (::_tcsnicmp(pString, _T("categories"), 10) == 0)
			{
				LPCTSTR szEnd = GetMSIParameter(pString + 10, strCategoryParam);
				szEnd++;
				*m_pszCommandLine = szEnd;
				iLine = 0;
				continue;
			}
		}

		//	If we don't match one of our internal switches, pass it on.

		strNewCommandLine += m_pszCommandLine->Left(iLine);
	}

	// Now, check the parameters we parsed from the command line to decide what
	// to do. If we are to save an NFO or report, we should just exit after
	// doing so.

	if (!strNFOFileParam.IsEmpty() || !strReportFileParam.IsEmpty())
	{
		CMSInfo sysInfo;

		if (!strNFOFileParam.IsEmpty())
			sysInfo.SaveNFO();
		else if (!strReportFileParam.IsEmpty())
			sysInfo.SaveReport();
		exit(0);
	}

	// Construct the command line for MMC.

	*m_pszCommandLine = _T("/s \"") + *m_pszSavedConsole + _T("\" ") + strNewCommandLine;

	if (!strComputerParam.IsEmpty())
		*m_pszCommandLine += _T(" /computer \"") + strComputerParam + _T("\" ");

	if (!strCategoryParam.IsEmpty())
		*m_pszCommandLine += _T(" /msinfo_showcategories=\"") + strCategoryParam + _T("\" ");

	delete m_pszSavedConsole;
	m_pszSavedConsole = NULL;
}

/*
 * FindSavedConsole - Finds SysInfo.msc using the registry, or the
 *		default directory.
 *
 * History:	a-jsari		10/13/97		Initial version
 */
void CMSInfoExecutable::FindSavedConsole()
{
	HKEY		keyMSInfoRoot;
	long		lResult;
	DWORD		dwKeyLength = MAX_PATH;
	DWORD		dwType;
	TCHAR		szDirectory[MAX_PATH+1];


	*m_pszSavedConsole = _T("");
	do {
		//	Check the current directory.
		if (::_taccess(cszSavedConsole, A_READ) == 0) {
			*m_pszSavedConsole = cszSavedConsole;
			return;
		}

		//	Check the MSInfo Path key.
		lResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, cszRegistryRoot, 0, KEY_READ,
				&keyMSInfoRoot);
		if (lResult == ERROR_SUCCESS) {
			lResult = ::RegQueryValueEx(keyMSInfoRoot, cszDirectoryKey, 0, &dwType,
					reinterpret_cast<BYTE *>(szDirectory), &dwKeyLength);
			if (lResult == ERROR_SUCCESS) {
				LPTSTR pszPath = ::_tcsrchr(szDirectory, (TCHAR)'\\');
				if (pszPath) *pszPath = 0;
				*m_pszSavedConsole = szDirectory;
				*m_pszSavedConsole += _T("\\");
				*m_pszSavedConsole += cszSavedConsole;
				if (::_taccess(*m_pszSavedConsole, A_READ) == 0)
					return;
			}
		}

		//	Use the hard-coded path %ProgramFilesRoot%\Common Files\MSInfo
		lResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, cszWindowsRoot, 0, KEY_READ, &keyMSInfoRoot);
		ASSERT(lResult == ERROR_SUCCESS);
		if (lResult != ERROR_SUCCESS) break;
		dwKeyLength = sizeof( szDirectory);
		lResult = ::RegQueryValueEx(keyMSInfoRoot, cszCommonFilesKey, 0, &dwType,
				reinterpret_cast<BYTE *>(szDirectory), &dwKeyLength);
		ASSERT(lResult == ERROR_SUCCESS);
		if (lResult != ERROR_SUCCESS) break;

		*m_pszSavedConsole = szDirectory;
		*m_pszSavedConsole += cszDefaultDirectory;
		*m_pszSavedConsole += cszSavedConsole;

		if (::_taccess(*m_pszSavedConsole, A_READ) == 0)
			return;
		
	} while (0);
//	AFX_MANAGE_STATE(::AfxGetStaticModuleState());
	CString	szNoMSCFile;
	szNoMSCFile.LoadString(IDS_NOMSCFILE);
	::AfxMessageBox(szNoMSCFile);
	::ThrowErrorException();
}

/*
 * DisplayHelp - Print the command line help for the executable.
 *
 * History:	a-jsari		10/13/97		Initial version.
 *			a-adaml		03/15/99		Modified to display help in message box
 *			
 */

void CMSInfoExecutable::DisplayHelp()
{
	CString strMsg, strTitle;

	strTitle.LoadString(IDS_DESCRIPTION);
	strMsg.LoadString(IDS_USAGE);

	::MessageBox( NULL, strMsg, strTitle, MB_ICONINFORMATION | MB_OK);
}

/*
 * InitInstance - The main entry point for the stub executable, the subclass InitInstance
 *		function 
 *
 * History:	a-jsari		10/13/97		Initial version
 */
BOOL CMSInfoApp::InitInstance()
{
#if FALSE	// just run the helpctr version now
	CString		szResText;
	CString		szResTitle;

	do {
		try {
			//	FIX:	Pre-load the memory resource in case memory problems develop.

			CMSInfoExecutable		exeMSInfo(cszProgram);

			exeMSInfo.Find();
			exeMSInfo.ProcessCommandLine();
			exeMSInfo.Run();
			//	We never get past this on successful completion.
		}
		catch (CMemoryException e_Mem) {
			AFX_MANAGE_STATE(AfxGetStaticModuleState());
			VERIFY(szResText.LoadString(IDS_MEMORY));
			VERIFY(szResTitle.LoadString(IDS_DESCRIPTION));
			if (::MessageBox(NULL, szResText, szResTitle, MB_RETRYCANCEL | MB_ICONERROR) == IDCANCEL)
				break;
			continue;
		}
		catch (CException e_Generic) {
			AFX_MANAGE_STATE(AfxGetStaticModuleState());
			VERIFY(szResText.LoadString(IDS_UNEXPECTED));
			::MessageBox(NULL, szResText, szResTitle, MB_OK | MB_ICONERROR);
			delete ::g_pException;
			break;
		}
		catch (...) {
			ASSERT(FALSE);
			break;
		}
		break;
	} while (TRUE);
#endif

	if (!RunMSInfoInHelpCtr())
	{
		CDialog help(IDD_MSICMDLINE);
		help.DoModal();
	}

	return FALSE;
}

//-----------------------------------------------------------------------------
// Required to use the new MSInfo DLL in HelpCtr.
//-----------------------------------------------------------------------------

typedef class MSInfo MSInfo;

EXTERN_C const IID IID_IMSInfo;

struct IMSInfo : public IDispatch
{
public:
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_AutoSize( 
        /* [in] */ VARIANT_BOOL vbool) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_AutoSize( 
        /* [retval][out] */ VARIANT_BOOL __RPC_FAR *pbool) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BackColor( 
        /* [in] */ OLE_COLOR clr) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BackColor( 
        /* [retval][out] */ OLE_COLOR __RPC_FAR *pclr) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BackStyle( 
        /* [in] */ long style) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BackStyle( 
        /* [retval][out] */ long __RPC_FAR *pstyle) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BorderColor( 
        /* [in] */ OLE_COLOR clr) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BorderColor( 
        /* [retval][out] */ OLE_COLOR __RPC_FAR *pclr) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BorderStyle( 
        /* [in] */ long style) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BorderStyle( 
        /* [retval][out] */ long __RPC_FAR *pstyle) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BorderWidth( 
        /* [in] */ long width) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BorderWidth( 
        /* [retval][out] */ long __RPC_FAR *width) = 0;
    
    virtual /* [id][propputref] */ HRESULT STDMETHODCALLTYPE putref_Font( 
        /* [in] */ IFontDisp __RPC_FAR *pFont) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_Font( 
        /* [in] */ IFontDisp __RPC_FAR *pFont) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_Font( 
        /* [retval][out] */ IFontDisp __RPC_FAR *__RPC_FAR *ppFont) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_ForeColor( 
        /* [in] */ OLE_COLOR clr) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_ForeColor( 
        /* [retval][out] */ OLE_COLOR __RPC_FAR *pclr) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_Window( 
        /* [retval][out] */ long __RPC_FAR *phwnd) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_BorderVisible( 
        /* [in] */ VARIANT_BOOL vbool) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_BorderVisible( 
        /* [retval][out] */ VARIANT_BOOL __RPC_FAR *pbool) = 0;
    
    virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_Appearance( 
        /* [in] */ short appearance) = 0;
    
    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_Appearance( 
        /* [retval][out] */ short __RPC_FAR *pappearance) = 0;
    
    virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE SetHistoryStream( 
        IStream __RPC_FAR *pStream) = 0;
    
    virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_DCO_IUnknown( 
        /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pVal) = 0;
    
    virtual /* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE put_DCO_IUnknown( 
        /* [in] */ IUnknown __RPC_FAR *newVal) = 0;
    
    virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE SaveFile( 
        BSTR filename,
        BSTR computer,
        BSTR category) = 0;
    
};

#include "msinfo32_i.c"

//-----------------------------------------------------------------------------
// This function encapsulates the functionality to run the new MSInfo in
// HelpCtr. If this function returns false, the help should be displayed.
//-----------------------------------------------------------------------------

void StringReplace(CString & str, LPCTSTR szLookFor, LPCTSTR szReplaceWith);
BOOL CMSInfoApp::RunMSInfoInHelpCtr()
{
	//-------------------------------------------------------------------------
	// Parse the command line parameters into one big string to pass to the
	// ActiveX control. There are a few which would keep us from launching
	// HelpCtr.
	//-------------------------------------------------------------------------

	CString		strCommandLine(CWinApp::m_lpCmdLine);

	CString		strLastFlag;
	CString		strCategory;
	CString		strCategories;
	CString		strComputer;
	CString		strOpenFile;
	CString		strPrintFile;
	CString		strSilentNFO;
	CString		strSilentExport;
	CString		strTemp;
	BOOL		fShowPCH = FALSE;
	BOOL		fShowHelp = FALSE;
	BOOL		fShowCategories = FALSE;

	while (!strCommandLine.IsEmpty())
	{
		// Remove the leading whitespace from the string.
		
		strTemp = strCommandLine.SpanIncluding(_T(" \t=:"));
		strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - strTemp.GetLength());

		// If the first character is a / or a -, then this is a flag.

		if (strCommandLine[0] == _T('/') || strCommandLine[0] == _T('-'))
		{
			strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - 1);
			strLastFlag = strCommandLine.SpanExcluding(_T(" \t=:"));
			strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - strLastFlag.GetLength());
			strLastFlag.MakeLower();

			if (strLastFlag == CString(_T("pch")))
			{
				fShowPCH = TRUE;
				strLastFlag.Empty();
			}
			else if (strLastFlag == CString(_T("?")) || strLastFlag == CString(_T("h")))
			{
				fShowHelp = TRUE;
				strLastFlag.Empty();
			}
			else if (strLastFlag == CString(_T("showcategories")))
			{
				fShowCategories = TRUE;
				strLastFlag.Empty();
			}

			continue;
		}

		// Otherwise, this is either a filename to open, or a parameter from the
		// previous command line flag. This might have quotes around it.

		if (strCommandLine[0] != _T('"'))
		{
			strTemp = strCommandLine.SpanExcluding(_T(" \t"));
			strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - strTemp.GetLength());
		}
		else
		{
			strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - 1);
			strTemp = strCommandLine.SpanExcluding(_T("\""));
			strCommandLine = strCommandLine.Right(strCommandLine.GetLength() - strTemp.GetLength() - 1);
		}

		if (strLastFlag.IsEmpty() || strLastFlag == CString(_T("msinfo_file")))
			strOpenFile = strTemp;
		else if (strLastFlag == CString(_T("p")))
			strPrintFile = strTemp;
		else if (strLastFlag == CString(_T("category")))
			strCategory = strTemp;
		else if (strLastFlag == CString(_T("categories")))
			strCategories = strTemp;
		else if (strLastFlag == CString(_T("computer")))
			strComputer = strTemp;
		else if (strLastFlag == CString(_T("report")))
			strSilentExport = strTemp;
		else if (strLastFlag == CString(_T("nfo")) || strLastFlag == CString(_T("s")))
			strSilentNFO = strTemp;

		strLastFlag.Empty();
	}

	if (fShowHelp)
		return FALSE;

	TCHAR szCurrent[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, szCurrent);
	CString strCurrent(szCurrent);
	if (strCurrent.Right(1) != CString(_T("\\")))
		strCurrent += CString(_T("\\"));

	HRESULT hrInitialize = CoInitialize(NULL);

	if (!strSilentNFO.IsEmpty() || !strSilentExport.IsEmpty())
	{
		IMSInfo * pMSInfo = NULL;

		if (SUCCEEDED(CoCreateInstance(CLSID_MSInfo, NULL, CLSCTX_ALL, IID_IMSInfo, (void **)&pMSInfo)) && pMSInfo != NULL)
		{
			BSTR computer = strComputer.AllocSysString();
			BSTR category = strCategories.AllocSysString();

			if (!strSilentNFO.IsEmpty())
			{
				if (strSilentNFO.Find(_T('\\')) == -1)
					strSilentNFO = strCurrent + strSilentNFO;

				if (strSilentNFO.Right(4).CompareNoCase(CString(_T(".nfo"))) != 0)
					strSilentNFO += CString(_T(".nfo"));

				BSTR filename = strSilentNFO.AllocSysString();
				pMSInfo->SaveFile(filename, computer, category);
				SysFreeString(filename);
			}

			if (!strSilentExport.IsEmpty())
			{
				if (strSilentExport.Find(_T('\\')) == -1)
					strSilentExport = strCurrent + strSilentExport;

				BSTR filename = strSilentExport.AllocSysString();
				pMSInfo->SaveFile(filename, computer, category);
				SysFreeString(filename);
			}

			SysFreeString(computer);
			SysFreeString(category);
			pMSInfo->Release();
		}

		if (SUCCEEDED(hrInitialize))
			CoUninitialize();

		return TRUE;
	}

	CString strURLParam;

	if (fShowPCH)
		strURLParam += _T("pch");

	if (fShowCategories)
		strURLParam += _T(",showcategories");

	if (!strComputer.IsEmpty())
		strURLParam += _T(",computer=") + strComputer;

	if (!strCategory.IsEmpty())
		strURLParam += _T(",category=") + strCategory;

	if (!strCategories.IsEmpty())
		strURLParam += _T(",categories=") + strCategories;

	if (!strPrintFile.IsEmpty())
	{
		if (strPrintFile.Find(_T('\\')) == -1)
			strPrintFile = strCurrent + strPrintFile;

		strURLParam += _T(",print=") + strPrintFile;
	}

	if (!strOpenFile.IsEmpty())
	{
		if (strOpenFile.Find(_T('\\')) == -1)
			strOpenFile = strCurrent + strOpenFile;
		
		strURLParam += _T(",open=") + strOpenFile;
	}

	if (!strURLParam.IsEmpty())
	{
		strURLParam.TrimLeft(_T(","));
		strURLParam = CString(_T("?")) + strURLParam;
	}

	CString strURLAddress(_T("hcp://system/sysinfo/msinfo.htm"));
	CString strURL = strURLAddress + strURLParam;

	//-------------------------------------------------------------------------
	// Check to see if we can run MSInfo in HelpCtr. We need the HTM file
	// to be present.
	//-------------------------------------------------------------------------

	BOOL fRunVersion6 = TRUE;

	TCHAR szPath[MAX_PATH];
	if (ExpandEnvironmentStrings(_T("%windir%\\pchealth\\helpctr\\system\\sysinfo\\msinfo.htm"), szPath, MAX_PATH))
	{
		WIN32_FIND_DATA finddata;
		HANDLE			h = FindFirstFile(szPath, &finddata);

		if (INVALID_HANDLE_VALUE != h)
			FindClose(h);
		else
			fRunVersion6 = FALSE;
	}

	// This would be used to check if the control is registered. Turns out we want to run anyway.
	//
	// IUnknown * pUnknown;
	// if (fRunVersion6 && SUCCEEDED(CoCreateInstance(CLSID_MSInfo, NULL, CLSCTX_ALL, IID_IUnknown, (void **) &pUnknown)))
	//		pUnknown->Release();
	// else
	//		fRunVersion6 = FALSE;

	StringReplace(strURL, _T(" "), _T("%20"));

	if (fRunVersion6)
	{
		// HelpCtr now supports running MSInfo in its own window. We need to
		// execute the following:
		//
		//		helpctr -mode hcp://system/sysinfo/msinfo.xml
		//
		// Additionally, we can pass parameters in the URL using the
		// following flag:
		//
		//		-url hcp://system/sysinfo/msinfo.htm?open=c:\savedfile.nfo
		//
		// First, find out of the XML file is present.

		BOOL fXMLPresent = TRUE;
		if (ExpandEnvironmentStrings(_T("%windir%\\pchealth\\helpctr\\system\\sysinfo\\msinfo.xml"), szPath, MAX_PATH))
		{
			WIN32_FIND_DATA finddata;
			HANDLE			h = FindFirstFile(szPath, &finddata);

			if (INVALID_HANDLE_VALUE != h)
				FindClose(h);
			else
				fXMLPresent = FALSE;
		}

		// If the XML file is present and we can get the path for helpctr.exe, we
		// should launch it the new way.

		TCHAR szHelpCtrPath[MAX_PATH];
		if (fXMLPresent && ExpandEnvironmentStrings(_T("%windir%\\pchealth\\helpctr\\binaries\\helpctr.exe"), szHelpCtrPath, MAX_PATH))
		{
			CString strParams(_T("-mode hcp://system/sysinfo/msinfo.xml"));
			if (!strURLParam.IsEmpty())
				strParams += CString(_T(" -url ")) + strURL;

			ShellExecute(NULL, NULL, szHelpCtrPath, strParams, NULL, SW_SHOWNORMAL);
		}
		else
			ShellExecute(NULL, NULL, strURL, NULL, NULL, SW_SHOWNORMAL);
	}
	else
		ShellExecute(NULL, NULL, _T("hcp://system"), NULL, NULL, SW_SHOWNORMAL);

	if (SUCCEEDED(hrInitialize))
		CoUninitialize();

	return TRUE;
}

//-----------------------------------------------------------------------------
// This was used originally to replace some MFC functionality not in the ME
// build tree.
//-----------------------------------------------------------------------------

void StringReplace(CString & str, LPCTSTR szLookFor, LPCTSTR szReplaceWith)
{
	CString strWorking(str);
	CString strReturn;
	CString strLookFor(szLookFor);
	CString strReplaceWith(szReplaceWith);

	int iLookFor = strLookFor.GetLength();
	int iNext;

	while (!strWorking.IsEmpty())
	{
		iNext = strWorking.Find(strLookFor);
		if (iNext == -1)
		{
			strReturn += strWorking;
			strWorking.Empty();
		}
		else
		{
			strReturn += strWorking.Left(iNext);
			strReturn += strReplaceWith;
			strWorking = strWorking.Right(strWorking.GetLength() - (iNext + iLookFor));
		}
	}

	str = strReturn;
}