Click here to Skip to main content
15,895,709 members
Articles / Programming Languages / C++/CLI

Bootstrapper for the VC++ 2005 Redists (with MSI 3.1)

Rate me:
Please Sign up or sign in to vote.
4.97/5 (67 votes)
24 Feb 200624 min read 556.9K   2K   159  
A discussion on deployment in Visual C++ 2005, and an amended version of the vcredist_x86.exe that includes MSI 3.1.
#include "vcbootstrap.h"
/**
*	Note: All Strings are in ANSI. Unicows won't help with a bootstrap.
**/

/**
*	Generic Thunk to the Scatchcode.
**/
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE, LPSTR lpCmdLine, int nCmdShow)
{

	int dwErr = 0;
#if defined (_MSC_VER) && defined (_DEBUG) && (_MSC_VER >= 1400)
	/** Like every substandard programmer. I am prone to creating programs with Memory leaks. Hopefully, this
	*	call should help detect those memory leaks before they get released.
	**/
	::_CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF |
		_CRTDBG_ALLOC_MEM_DF | ::_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
#endif /* _MSC_VER */
	try {
		::SetLastError(ERROR_SUCCESS);
		::InitCommonControls();

		std::vector<TCHAR> curDir(FILENAME_MAX);
		_getcwd(&curDir.at(0), FILENAME_MAX);
		{/* Change the current directory to the same as the application */
			std::vector<TCHAR> appName(FILENAME_MAX);
			::GetModuleFileNameA(NULL, &appName.at(0), FILENAME_MAX);
			std::basic_string<TCHAR> appDir(&appName.at(0));
			appDir.erase(appDir.find_last_of(_T("\\")));
			_chdir(appDir.c_str());
		}

		std::basic_string<TCHAR> sCmdLine(lpCmdLine);
		dwErr = ScratchCode(hInst, sCmdLine, nCmdShow);
		_chdir(&curDir.at(0));
	} catch(std::exception &ex) {
		std::basic_string<TCHAR> ErrStr("An error occurred during Setup initialization. The error was: ");
		ErrStr += ex.what();

		::MessageBoxA(NULL, ErrStr.c_str(), _T("Application Error"), MB_OK | MB_ICONEXCLAMATION);
		dwErr = 3;
	}

	return dwErr;
}




/// <summary>
/// My customised entry point to WinMain
/// </summary>
/// <param name="hInst">Contains the HINSTANCE of this app</param>
/// <param name="sCmdLine">The command line to echo to the user</param>
/// <param name="nCmdShow">whether to show the window minimised, maximised etc.</param>
int ScratchCode(const HINSTANCE hInst, const std::basic_string<TCHAR> &sCmdLine, const int nCmdShow)
{
	bool needsMSI3 = true;
	int reqdSoftware = 0;
	int Result = 0;

#ifdef _DEBUG
	reqdSoftware |= WINDOWS_INSTALLER_3_1_INSTALL | INTERNET_EXPLORER_INSTALL;
#endif /* Code to test the dialog box on dev machines (ie. fully patched windows). */

	if(CheckIfCanLoadDLL(reqdSoftware) && reqdSoftware == 0)
	{/* If this succeeded, then the CRTs are installed and we don't even need this bootstrapper. */
		return 0;
	}


#ifndef EXCLUDE_IE6
	try {/* Internet Explorer is signified by checking for IE5.0 */
		std::vector<BYTE> IeVersionNum;
		read_registry_key IEReg("SOFTWARE\\Microsoft\\Internet Explorer");
		IeVersionNum = IEReg.get_value("Version");
		if(IeVersionNum.at(0) < '5')/* The first character should be 5 or greater. */
			throw std::runtime_error("Internet Explorer is an old version");
	} catch (std::exception &) {/* Internet Explorer is not installed */
		reqdSoftware |= INTERNET_EXPLORER_INSTALL;
	}
#endif /* EXCLUDE_IE6 */

	try {/* Check if we are on the unsupported Windows NT or 95. */
		OSVERSIONINFOEXA osVer = {sizeof(OSVERSIONINFO)};
		::GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&osVer));

		if(osVer.dwMajorVersion < 4)
		{/* Very old Windows. Never reaches here */
			throw std::runtime_error("This application requires Windows 98 or later");
		}
		if(osVer.dwMajorVersion == 4)
		{
			needsMSI3 = false;
			if(osVer.dwPlatformId & VER_PLATFORM_WIN32_NT)
			{/* Windows NT version 4.0. Not Supported. */
				throw std::runtime_error("This application does not run on Windows NT 4.0");
			}
			/* Win9x */
			if(osVer.dwMinorVersion < 10)
			{/* Before Windows 98, ie Windows 95. */
				throw std::runtime_error("This application requires Windows 98 or later");
				/** BUGBUG: The VC2005 compiler produces code inherently incompatible with Windows 95.
				*	Therefore, this is stale code. No way round except to recompile the CRT library or
				*	obtain an older compiler.
				**/
			}
		}
		if(osVer.dwMajorVersion == 5 && osVer.dwMinorVersion == 0)
		{/* Windows 2000 */
			needsMSI3 = false;
			osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
			::GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&osVer));
			if(osVer.wServicePackMajor < 3)
			{/* TODO: Service pack is done interactively, and this app needs to be run upon restart. */
				if(::MessageBoxA(NULL, "Before continuing, please install Service Pack 4 for Windows 2000, "
					"would you like to do that now?", "Operating System Error", MB_YESNO | MB_ICONERROR) != IDYES)
				{
					return ERROR_INSTALL_PACKAGE_VERSION;
				}
				return ExtractAndLoadExecutable(NULL, "SP4Express_EN.exe", "", SW_SHOWNORMAL);
			}
		}
		/* Otherwise, everything looks normal. */
	} catch (std::runtime_error &ex) {
		::MessageBoxA(NULL, ex.what(), "Operating System Error", MB_OK | MB_ICONERROR);
		return ERROR_EXE_MARKED_INVALID;
	}


	try {/* Windows Installer Version Check. MSI2 is required on Win98/Me/2k and MSI31 is required on XP/2k3/Vista */
		AutoLibrary hMsi(::LoadLibraryA("MSI"));
		HRESULT (CALLBACK *pfnDllGetVersion) (DLLVERSIONINFO *) =
			reinterpret_cast<HRESULT (CALLBACK *)(DLLVERSIONINFO *)>(::GetProcAddress(hMsi, "DllGetVersion"));
		if(!pfnDllGetVersion)
			throw std::runtime_error("Msi.dll is corrupt or missing a required export");
		DLLVERSIONINFO msiVer = {sizeof(DLLVERSIONINFO), 0};
		if(FAILED(pfnDllGetVersion(&msiVer)))
		{
			throw std::runtime_error("Error occurred determining version of Windows Installer");
		}
		if(msiVer.dwMajorVersion < 3)
		{
			if(needsMSI3)
				reqdSoftware |= WINDOWS_INSTALLER_3_1_INSTALL;
		}
	} catch(std::exception &) {
		reqdSoftware |= WINDOWS_INSTALLER_2_0_INSTALL;
	}

	if(reqdSoftware != 0 && reqdSoftware != VISUAL_C80_INSTALL)
	{/* Installation is required */
		if(sCmdLine.find("silent") < sCmdLine.size())
		{/* Silent installation requested. Just install. */
			Result = InstallRequiredApps(NULL, reqdSoftware);
		}
		else
		{/* Show the UI */
			Result = static_cast<int>(::DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, MsgDlgProc, reqdSoftware));
		}
	}
	if(reqdSoftware & VISUAL_C80_INSTALL && Result != ERROR_CANCELLED)
	{/* Everything is okay for deployment.. run Vcredist_x86.exe. */
		const std::basic_string<char> appName = VCREDIST_NAME;

		Result = ExtractAndLoadExecutable(NULL, appName, sCmdLine, nCmdShow/*IDB_RESOURCE1*/);
	}
	

	return Result;
}



BOOL CheckIfCanLoadDLL(int &reqdSoftware)
{/* Reference type is deliberate (out param) */
	::SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
	::SetLastError(ERROR_SUCCESS);

	HMODULE hVC8DLL = ::LoadLibraryA("VC80Test.dll");





	if(!hVC8DLL)
	{/* If this fails, alter the reqdSoftware. Most other GetLastErrors cannot be handled by us. */
		reqdSoftware |= VISUAL_C80_INSTALL;
		/* BUGBUG: But what if the DLL Load failed for some other reason, like access denied? */
	}
	::FreeLibrary(hVC8DLL);

	::SetErrorMode(0);
	return TRUE;
}



DWORD ExtractAndLoadExecutable(HWND TheirhWnd, const std::basic_string<char> &exeName,
	const std::basic_string<char> &sCmdLine, int nCmdShow)
{
	/* The IExpress installer has plopped the executable into our directory */
	std::basic_string<char> lpCmdString(exeName);

	std::basic_string<char> lpCmd2 = lpCmdString;
	lpCmd2.append(" ");
	lpCmd2.append(sCmdLine);

	std::vector<char> lpCmdLine(lpCmd2.size() + 1);
	std::copy<const std::basic_string<char>::const_iterator, std::vector<char>::iterator>
		(lpCmd2.begin(), lpCmd2.end(), lpCmdLine.begin());

	STARTUPINFOA lpStartupInfo = {0};
	lpStartupInfo.cb = sizeof(STARTUPINFOA);
	lpStartupInfo.dwFlags = STARTF_USESHOWWINDOW;
	lpStartupInfo.wShowWindow = static_cast<WORD>(nCmdShow);
	PROCESS_INFORMATION lpProcessInformation = {0};

	DWORD hr = ::CreateProcessA(lpCmdString.c_str(), &lpCmdLine.at(0), NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS,
		NULL, NULL, &lpStartupInfo, &lpProcessInformation);
	if(TheirhWnd != NULL)
	{/* Since we're in a UI, also process any message. */
		WaitWithMessageLoop(lpProcessInformation.hProcess);
	}
	else
	{/* We're not in a UI */
		::WaitForSingleObject(lpProcessInformation.hProcess, INFINITE);
	}
	::GetExitCodeProcess(lpProcessInformation.hProcess, &hr);
	::CloseHandle(lpProcessInformation.hThread);
	::CloseHandle(lpProcessInformation.hProcess);
	return hr;
}


BOOL WaitWithMessageLoop(HANDLE hHandleToWaitOn, DWORD dwIterateTimeOutMilliseconds /*= INFINITE*/)
{/* Bah, must use a Waitwithmessageloop. Because we don't have threads in /ML */
	DWORD dwRet=0;
	MSG msg={0};

	dwRet = ::WaitForSingleObject(hHandleToWaitOn, 0);
	if(dwRet == WAIT_OBJECT_0)
	{
		// The object is already signalled.
		return TRUE;
	}

	for(;;)
	{
		// There are one or more window message available. Dispatch them.
		while(::PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE) > 0)
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
			dwRet = ::WaitForSingleObject(hHandleToWaitOn, 0);
			if(dwRet == WAIT_OBJECT_0)
			{
				// The object is already signalled.
				return TRUE;
			}
		}

		/** Now we've dispatched all the messages in the queue. Use MsgWaitForMultipleObjects() to either tell us
		*	there are more messages to dispatch, or that our object has been signalled.
		**/

		dwRet = ::MsgWaitForMultipleObjects(1, &hHandleToWaitOn, FALSE,
			dwIterateTimeOutMilliseconds, QS_ALLEVENTS);

		if(dwRet == WAIT_OBJECT_0)
		{/* The event was signaled. */
			return TRUE;
		}
		else if(dwRet == WAIT_OBJECT_0 + 1)
		{/* New messages have come that need to be dispatched. */
			continue;
		}
		else if(dwRet == WAIT_TIMEOUT)
		{/* We hit our infinite time limit, Exit. */
			break;
		}
		else
		{/* Something else happened. */
			return FALSE;
		}
	}
	return FALSE;
}


int InstallRequiredApps(HWND TheirhWnd, const int reqdSoftware)
{
	std::map<const std::basic_string<char>, std::basic_string<char> > exeCommand;
	/* This section is very much WIP. */
	#ifndef EXCLUDE_IE6
	if(reqdSoftware & INTERNET_EXPLORER_INSTALL)
	{
		/** I wonder if the /q:a switch would be better? I personally prefer /q:u, since the /q:a switch
		*	makes it feel like you've got something to hide.
		**/
		exeCommand["ie6setup.exe"] = " /q:u /r:n";
	}
	#endif /* EXCLUDE_IE6 */
	if(reqdSoftware & WINDOWS_INSTALLER_2_0_INSTALL)
	{
		exeCommand["InstMSIA.exe"] = " /q:u /r:n";
	}
	if(reqdSoftware & WINDOWS_INSTALLER_3_1_INSTALL)
	{
		exeCommand["WindowsInstaller-KB893803-v2-x86.exe"] = " /norestart";
	}

	DWORD TotalResult = 0;
	for(std::map<const std::basic_string<char>, std::basic_string<char> >::const_iterator Iter1 = exeCommand.begin();
		Iter1 != exeCommand.end(); ++Iter1)
	{
		TotalResult = ExtractAndLoadExecutable(TheirhWnd, Iter1->first, Iter1->second, SW_SHOWNORMAL);
		if(TotalResult != 0)
		{/* Panic at the first sign of error */
			return TotalResult;
		}
	}
	return TotalResult;
}

#if 0
const std::basic_string<char> GetTemporaryName()
{
	char *tmpDirPtr = NULL;
	std::basic_string<char> Result;
	try {
		tmpDirPtr = _tempnam(NULL, "VCRT");
		if(tmpDirPtr == NULL) throw std::runtime_error("Error generating temp name");


		Result = tmpDirPtr;
	} catch (...) {/* finally */
		free(tmpDirPtr);
		throw;
	}
	free(tmpDirPtr);
	return Result;
}
#endif /* STALE CODE */


INT_PTR CALLBACK MsgDlgProc(HWND TheirhWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	switch(Msg)
	{
		case WM_INITDIALOG:
		{
#ifndef EXCLUDE_IE6
			::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKIE), BM_SETCHECK, BST_CHECKED, 0);
#endif /* EXCLUDE_IE6 */
			::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKMSI2), BM_SETCHECK, BST_CHECKED, 0);
			::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKMSI31), BM_SETCHECK, BST_CHECKED, 0);
			::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECK2K4), BM_SETCHECK, BST_CHECKED, 0);
			int lpCmdLine = static_cast<DWORD>(lParam);
#ifndef EXCLUDE_IE6
			if(!(lpCmdLine & INTERNET_EXPLORER_INSTALL))
			{
				::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKIE), BM_SETCHECK, BST_UNCHECKED, 0);
				::ShowWindow(::GetDlgItem(TheirhWnd, IDC_CHECKIE), SW_HIDE);
			}
#endif /* EXCLUDE_IE6 */
			if(!(lpCmdLine & WINDOWS_INSTALLER_2_0_INSTALL))
			{
				::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKMSI2), BM_SETCHECK, BST_UNCHECKED, 0);
				::ShowWindow(::GetDlgItem(TheirhWnd, IDC_CHECKMSI2), SW_HIDE);
			}
			if(!(lpCmdLine & WINDOWS_SERVICE_PACK4_INSTALL))
			{
				::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECK2K4), BM_SETCHECK, BST_UNCHECKED, 0);
				::ShowWindow(::GetDlgItem(TheirhWnd, IDC_CHECK2K4), SW_HIDE);
			}
			if(!(lpCmdLine & WINDOWS_INSTALLER_3_1_INSTALL))
			{
				::PostMessageA(::GetDlgItem(TheirhWnd, IDC_CHECKMSI31), BM_SETCHECK, BST_UNCHECKED, 0);
				::ShowWindow(::GetDlgItem(TheirhWnd, IDC_CHECKMSI31), SW_HIDE);
			}

			::SetLastError(ERROR_SUCCESS);
			return TRUE;
		}

		case WM_CLOSE:
		{
			::EndDialog(TheirhWnd, ERROR_CANCELLED);
			return TRUE;
		}

		case WM_COMMAND:
		{
			switch(LOWORD(wParam))
			{
				case IDOK:
				{
					int reqdSoftware = 0, Result = ERROR_CANCELLED;
					::EnableWindow(TheirhWnd, FALSE);
#ifndef EXCLUDE_IE6
					if(::IsDlgButtonChecked(TheirhWnd, IDC_CHECKIE))
					{
						reqdSoftware |= INTERNET_EXPLORER_INSTALL;
					}
#endif /* EXCLUDE_IE6 */
					if(::IsDlgButtonChecked(TheirhWnd, IDC_CHECKMSI2))
					{
						reqdSoftware |= WINDOWS_INSTALLER_2_0_INSTALL;
					}
					if(::IsDlgButtonChecked(TheirhWnd, IDC_CHECKMSI31))
					{
						reqdSoftware |= WINDOWS_INSTALLER_3_1_INSTALL;
					}
					Result = InstallRequiredApps(TheirhWnd, reqdSoftware);

					::EndDialog(TheirhWnd, Result);
					return TRUE;
				}
				case IDCANCEL:
				{
					::EndDialog(TheirhWnd, ERROR_CANCELLED);
					return TRUE;
				}
				default:
					break;
			}
			return FALSE;
		}
		default:
			break;
	}
	return FALSE;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Mr. Shah is a reclusive C++/C# developer lurking somewhere in the depths of the city of London. He learnt physics at Kings' College London and obtained a Master in Science there. Having earned an MCAD, he teeters on the brink of transitioning from C++ to C#, unsure of which language to jump to. Fortunately, he also knows how to use .NET interop to merge code between the two languages (which means he won't have to make the choice anytime soon).

His interests (apart from programming) are walking, football (the real one!), philosophy, history, retro-gaming, strategy gaming, and any good game in general.

He maintains a website / blog / FAQ / junk at shexec32.serveftp.net, where he places the best answers he's written to the questions you've asked. If you can find him, maybe you can hire Mr. Shah to help you with anything C++[/CLI]/C#/.NET related Smile | :) .

Comments and Discussions