Posts Tagged ‘Installation’

Detection of product failed during request for component

April 17, 2008

If your MSI Installation package is bringing that error message I might have a solution for you. It applies only if you are authoring the installation, your product doesn’t use the resiliency feature and you know what are you doing.

First the theory – each and every COM component, that your installation is registering becomes a little feature with it – every time it is used Windows Installer is going to check if something has gone wrong with it, if so repair installation is started. This brings a lot more problems than it solves, especially if you know, that the updated COM component, is still valid.

So how to “Stop Installation Idiocy”?
My solution was to remove the extra information registered with each COM component, so the Windows Installer has no way to monitor it and as end result can’t bring up an error message if something has changed with the component.  This resiliency feature may be useful in some occasions, but our product does not rely on it in any way and it is just a source of endless problems when we ship updated component.

Here is sample code for a custom action in InstallScript, you can do the same in C++ no matter how are you building your MSI package.
The idea is to find out which COM components are you registering and to remove the hash values, that are inserted automatically from Windows Installer.

function RemoveRepairKeys(hMSI)
	STRING key, guid, context;
	LONG hDB, hView, hRec, r;
	NUMBER pos, num;
begin
	RegDBSetDefaultRoot(HKEY_CLASSES_ROOT);

	hDB = MsiGetActiveDatabase(hMSI);

	if(hDB = 0) then
		SprintfMsiLog("Error MsiGetActiveDatabase");
		return 0;
	endif;

	PrintStatus(
             MsiDatabaseOpenView(hDB, "SELECT `Key` FROM `Registry` WHERE `Root`=0", hView),
             "MsiDatabaseOpenView"
        );
	PrintStatus(MsiViewExecute(hView, 0), "MsiViewExecute");
	r = MsiViewFetch(hView, hRec);
	PrintStatus(r, "MsiViewFetch");
	RegDBSetDefaultRoot(HKEY_CLASSES_ROOT);
	num = 260;
	while(r == 0)
		r = MsiRecordGetString(hRec, 1, key, num);
		pos = StrFindEx(key, "CLSID\\", 0);
		if(pos >= 0) then
			SprintfMsiLog("GUID %s", guid);
			StrSub(guid, key, pos + 6, 38);
			LogResult(RegDBDeleteValue(
				"\\CLSID\\" + guid + "\\InprocServer32", "InprocServer32"),"\\CLSID\\"
                                + guid + "\\InprocServer32.InprocServer32"
			);
			LogResult(
				RegDBDeleteValue("\\CLSID\\" + guid + "\\LocalServer32", "LocalServer32"),
				"\\CLSID\\" + guid + "\\LocalServer32.LocalServer32"
			);
		endif;

		MsiCloseHandle(hRec);
		r = MsiViewFetch(hView, hRec);
		//PrintStatus(r, "MsiViewFetch");
	endwhile;

	PrintStatus(
              MsiDatabaseOpenView(hDB, "SELECT `CLSID`, `Context` FROM `Class`", hView),
             "MsiDatabaseOpenView");
	PrintStatus(MsiViewExecute(hView, 0), "MsiViewExecute");
	r = MsiViewFetch(hView, hRec);
	PrintStatus(r, "MsiViewFetch");
	RegDBSetDefaultRoot(HKEY_CLASSES_ROOT);
	num = 260;
	while(r == 0)
		r = MsiRecordGetString(hRec, 1, guid, num);
		r = MsiRecordGetString(hRec, 2, context, num);
		LogResult(
			RegDBDeleteValue("\\CLSID\\" + guid + "\\" + context, context),
			"\\CLSID\\" + guid + "\\" + context  + "." + context
		);
		MsiCloseHandle(hRec);
		r = MsiViewFetch(hView, hRec);
		//PrintStatus(r, "MsiViewFetch");
	endwhile;

	PrintStatus(MsiCloseHandle(hView), "MsiCloseHandle");

	DeleteRepairKeysFixed(hMSI);
end;

Use this at your own risk, it did work for us pretty well. Questions and comments are welcome.

Some links regarding the resiliency feature:

http://blogs.msdn.com/astebner/archive/2005/01/13/352649.aspx
http://blogs.msdn.com/astebner/archive/2004/08/24/219764.aspx
http://blogs.msdn.com/windows_installer_team/archive/2005/11/01/486587.aspx
http://www.installsite.org/pages/en/msifaq/a/1037.htm

Advertisements