To solve a problem listed here I've got to change the InstallExecuteSequence .RemoveExistingProducts record in an MSI.
I want to do this as part of the build process rather than mucking around with Orca
To solve a problem listed here I've got to change the InstallExecuteSequence .RemoveExistingProducts record in an MSI.
I want to do this as part of the build process rather than mucking around with Orca
Modifying the MSI_SetProperty.js script gives
// MSI_SetActionSequence.js <msi-file> <table> <action> <sequence>
// Performs a post-build fixup of an msi to set the specified table/action/sequence
// Constant values from Windows Installer SDK
var msiOpenDatabaseModeTransact = 1;
var msiViewModifyInsert = 1;
var msiViewModifyUpdate = 2;
var msiViewModifyAssign = 3;
var msiViewModifyReplace = 4;
var msiViewModifyDelete = 6;
if (WScript.Arguments.Length != 4)
{
WScript.StdErr.WriteLine("Usage: " + WScript.ScriptName + " file table action sequence");
WScript.Quit(1);
}
var filespec = WScript.Arguments(0);
var table = WScript.Arguments(1);
var action = WScript.Arguments(2);
var sequence = parseInt(WScript.Arguments(3));
var installer = WScript.CreateObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
WScript.StdOut.WriteLine("Looking for action:" + action);
try
{
var sql = "SELECT Action, Sequence FROM " + table + " WHERE Action = '" + action + "'";
var view = database.OpenView(sql);
view.Execute();
var record = view.Fetch();
if (record)
{
while (record)
{
WScript.StdOut.Write("Found: " + record.StringData(0) + ", " + record.StringData(1) + ", " + record.StringData(2));
if (record.IntegerData(2) != sequence)
{
WScript.StdOut.WriteLine(" - changing to " + sequence);
record.IntegerData(2) = sequence;
view.Modify(msiViewModifyUpdate,record);
}
else
WScript.StdOut.WriteLine(" - OK");
record = view.Fetch();
}
view.Close();
database.Commit();
}
else
{
view.Close();
throw("Warning - Could not find " + table + "." + action);
}
}
catch(e)
{
WScript.StdErr.WriteLine(e);
WScript.Quit(1);
}
To call this script to perform the change to the action sequence mentioned above you would put the following in a batch file and call that from the post build event e.g. PostBuildEvent = $(ProjectDir)PostBuild.bat
cscript.exe MSI_SetActionSequence.js YOURINSTALLER.MSI InstallExecuteSequence RemoveExistingProducts 1525
Some notes to others out there. I had the "Error 1001. The specified service already exists" problem, and tried the above and it didn't seem to work. Here's what I ran into:
* Make sure the RemovePreviousVersions property on your installer project is set to True. This seems obvious--obvious, that is, if you know about it. By default it is set to False. If False, the above procedure will not solve your problem. *
I have some assemblies installed in the GAC. It appears that when I moved the RemoveExistingProducts sequence that these files were removed from the GAC, but not reinstalled. To solve this I installed all assemblies in the Application Folder. FYI, I'm using VS2010.
Also, another nit-pick. If a user selects "Repair" when attempting to reinstall the same version of a product, they will still get "The specified service already exists" error. If I get time I'll try to fix this. If someone else out there knows how to fix it, could you post?
All that said, thanks for posting this!
The solution provided by Ryan addresses part of the issue I am facing. It does perform full uninstall, followed by install.
However, I have another issue, in my case some of the programs are running in the background. Before the installer can run, the installer complains that some of the files are in use. And gives standard dialog box asking for either to close the application, or restart to complete updating.
Is there a way, eg. a custom action or a setting, to kill the processes running in the background so that the installer goes smoothly?