views:

3580

answers:

14

Using Visual Studio 2008 to create an msi to deploy my program with a setup project. I need to know how to make the msi run the exe it just installed. A custom action? If so please explain where/how. Thanks.

+1  A: 

Yes.. I would write a custom action, and stick it at the end of the InstallExecutionSequence table

Nestor
I don't see an InstallExecutionSequence table? Under custom actions I see Install, Commit, Rollback and uninstall. Would the custom action point to the output of the main program?
Shawn
The InstallExecutionSequence table is internal to the MSI and not exposed via Visual Studio. You will need an MSI editor like Orca to edit it. Hand-editing MSIs is not a trivial task.
Dour High Arch
I agree - hand-editing MSIs is not easy. Not is it easy or trivial using Orca. You can use the script I provided below to automate the task. Quick and easy, tested and proven.
Cheeso
+20  A: 

This is a common question. I don't do it with just a custom action. The only way I know, is to modify the .msi after it has been generated. I run a Javascript script as a post-build event to do exactly that. It inserts a new dialog in the installer wizard, with a checkbox that says "Launch Application Foo?". And then there is a custom action to run the app, if the checkbox is checked.

It appears as the last screen in the install Wizard sequence. Looks like this:

alt text


This is the script I use to modify the MSI:

// EnableLaunchApplication.js <msi-file>
// Performs a post-build fixup of an msi to launch a specific file when the install has completed


// Configurable values
var checkboxChecked = true;                     // Is the checkbox on the finished dialog checked by default?
var checkboxText = "Launch [ProductName]";      // Text for the checkbox on the finished dialog
var filename = "WindowsApplication1.exe";       // The name of the executable to launch - change this to match the file you want to launch at the end of your setup


// Constant values from Windows Installer
var msiOpenDatabaseModeTransact = 1;

var msiViewModifyInsert         = 1
var msiViewModifyUpdate         = 2
var msiViewModifyAssign         = 3
var msiViewModifyReplace        = 4
var msiViewModifyDelete         = 6



if (WScript.Arguments.Length != 1)
{
        WScript.StdErr.WriteLine(WScript.ScriptName + " file");
        WScript.Quit(1);
}

var filespec = WScript.Arguments(0);
var installer = WScript.CreateObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);

var sql
var view
var record

try
{
        var fileId = FindFileIdentifier(database, filename);
        if (!fileId)
                throw "Unable to find '" + filename + "' in File table";


        WScript.Echo("Updating the Control table...");
        // Modify the Control_Next of BannerBmp control to point to the new CheckBox
        sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BannerBmp'";
        view = database.OpenView(sql);
        view.Execute();
        record = view.Fetch();
        record.StringData(11) = "CheckboxLaunch";
        view.Modify(msiViewModifyReplace, record);
        view.Close();

        // Insert the new CheckBox control
        sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '201', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')";
        view = database.OpenView(sql);
        view.Execute();
        view.Close();


        WScript.Echo("Updating the ControlEvent table...");
        // Modify the Order of the EndDialog event of the FinishedForm to 1
        sql = "SELECT `Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering` FROM `ControlEvent` WHERE `Dialog_`='FinishedForm' AND `Event`='EndDialog'";
        view = database.OpenView(sql);
        view.Execute();
        record = view.Fetch();
        record.IntegerData(6) = 1;
        view.Modify(msiViewModifyReplace, record);
        view.Close();

        // Insert the Event to launch the application
        sql = "INSERT INTO `ControlEvent` (`Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering`) VALUES ('FinishedForm', 'CloseButton', 'DoAction', 'VSDCA_Launch', 'LAUNCHAPP=1', '0')";
        view = database.OpenView(sql);
        view.Execute();
        view.Close();



        WScript.Echo("Updating the CustomAction table...");
        // Insert the custom action to launch the application when finished
        sql = "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('VSDCA_Launch', '210', '" + fileId + "', '')";
        view = database.OpenView(sql);
        view.Execute();
        view.Close();

        if (checkboxChecked)
        {
                WScript.Echo("Updating the Property table...");
                // Set the default value of the CheckBox
                sql = "INSERT INTO `Property` (`Property`, `Value`) VALUES ('LAUNCHAPP', '1')";
                view = database.OpenView(sql);
                view.Execute();
                view.Close();
        }



        database.Commit();
}
catch(e)
{
        WScript.StdErr.WriteLine(e);
        WScript.Quit(1);
}



function FindFileIdentifier(database, fileName)
{
        var sql
        var view
        var record

        // First, try to find the exact file name
        sql = "SELECT `File` FROM `File` WHERE `FileName`='" + fileName + "'";
        view = database.OpenView(sql);
        view.Execute();
        record = view.Fetch();
        if (record)
        {
                var value = record.StringData(1);
                view.Close();
                return value;
        }
        view.Close();

        // The file may be in SFN|LFN format.  Look for a filename in this case next
        sql = "SELECT `File`, `FileName` FROM `File`";
        view = database.OpenView(sql);
        view.Execute();
        record = view.Fetch();
        while (record)
        {
                if (StringEndsWith(record.StringData(2), "|" + fileName))
                {
                        var value = record.StringData(1);
                        view.Close();
                        return value;
                }

                record = view.Fetch();
        }
        view.Close();

}

function StringEndsWith(str, value)
{
        if (str.length < value.length)
                return false;

        return (str.indexOf(value, str.length - value.length) != -1);
}

Save that Javascript file to the project directory (same dir as contains .vdproj), name it ModifyMsiToEnableLaunchApplication.js . For each unique setup project, you need to modify that script and put the proper exe name into it. And then, you need to set the post-build event in the Setup project to be this:

cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"

That oughtta do it.

Cheeso
Wow Cheeso... nice script
Nestor
like a little bit of magic. I don't know if MS added the capability into the setup project for VS2010. I think it's also possible to do this in WiX, but I've never used Wix, so this works for me.
Cheeso
If the interface is hidden (msi being pushed with silent install commands) will this still work?
Shawn
Sure, it will still work. Just keep the checkboxChecked var as true in the script.
Cheeso
I'm getting an Error: 'PostBuildEvent' failed with error code '1' 'Unspecified error'
Shawn
you have to look in the build output log, to discern the problem. First things to check: did you modify the name of the EXE at the top of the Javascript file? did you name the file properly and does the post-build event reference the proper name?
Cheeso
Not enough storage is available?
Shawn
And yes I've named the file correctly and have modified it to the proper exe.
Shawn
@Cheeso: Yes, one can do this with Wix.
Brian
Credits to the original post.. http://blogs.msdn.com/b/astebner/archive/2006/08/12/696833.aspx
Trainee4Life
Will this work with .bat? If so then i think i have the answer to two of my ongoing threads
Lily
A: 

Thanks for that great script, but i got 2 questions/problems with it:

  1. Is this approach also valid for launching a msi after the setup has completed?
  2. I can't manage to make any file run after setup has completed, simply nothing happens, in Process Mon I see my .exe is tried to be executed, but a "File Locked with only readers" occurs and the .exe is closed afterwards, is there any solution to this? (i use windows vista/windows 7)
Joe
You might be better off starting a new question.
Shawn
A: 

Hi cheeso, Thanks for the scripts.I facing one problem,after successfull install of application,in the final wizard,checkbox gets enabled only after i click on form.it doesnot get enabled automatically @ the end(final wizard).

is ther any thing i m missing.

With regards, mahens

start a new question
Cheeso
A: 

While after using the js, the Checkbox was shown only after i move mouse onto it, why? I'm using VC++, but I don't think it will matter.

another question, how to change to the TargetDIR to run the application? now i do it in the application initialize routine.

Brent Jiang
start a new question
Cheeso
A: 

Fab script.

I too am having the problem where the checkbox is not visible until the mouse is moved over the control, which is weird. I'm using VS2008. Apart from the control not being visible it works perfectly.

A: 

Greetings all

Regarding the 'PostBuildEvent' failed with error code '1' 'Unspecified error' error, change the PostBuildEvent from

cscript.exe \"$(ProjectDir)ModifyMsiToEnableLaunchApplication.js\" \"$(BuiltOuputPath)\"

to

cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"

Regarding the hidden checkbox bug you can edit line 54 of the script to become:

sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '201', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')";

Happy coding

Muleskinner
Thanks for the updates.
Cheeso
A: 

The dialog is created successfully but the program I want to run needs to be run as the currently logged on user and not as the "msisetupuser"...any ideas?

Kai
start a new question
Cheeso
A: 

You can use a little-known program to create these install packages that have the the install forms built in. When you create an .SED self-extracting directive file (installer) it provides a way to choose "run the program after installing" as an option for the user.

To get this thing going you just run the Windows XP cmd line program : iexpress Just type that at your cmd line and follow the screens to create your install pkg. I actually used this program to create a self-extracting .exe that when run extracts an msi and starts installing it. Good luck.

daylight
A: 

I successfully applied this script in my code. Setup is working fine in XP and launching exe but it is not launching exe in Vista or Win7. Where I need to make changes? I also created one new question for this, you can also give an answer here My Question

Rahul Kulshreshtha
+1  A: 

Hello,

Concerning the "hidden checkbox bug" I figured out the following which is not explained by Cheeso's and Muleskinner's answers above:

The change of the script (provided by Muleskinner) places the Y position of the checkbox to 201 (I guess top Y pixel for the control). If you change Y to, say, 151 (in order to kind of align it in the center vertically), the bug "suddenly" appears. The reason for that is that there is another control in the Control table of the msi, namely the 'BodyText' ('Dialog' field = 'FinishedForm') which its Y is set to 63 and its height to 138. That is 138 + 63 = 201. Therefore, if you change the Y value for the checkbox, the 'BodyText' control overlaps the newly added control and that's why the user needs to put their mouse over in order to show the checkbox. If you have no 'BodyText' or its number of characters is small enough you could change (by using Orca msi editor as I do, or by altering the script above) the Ys and Heights of these 2 controls in order to be able and accomodate a different Y position for the newly added checkbox. The same applies for the control: 'BodyTextRemove' in which again we should alter its height value (which appears during uninstall)

Hope that this helps all the users that had the same question as I had about this "bug"

Nevertheless, the script does a really good job!

Another question was how to make invisible the Checkbox during unistallation procedure. Using the Orca msi editor I added the following 2 rows in the ControlCondition table of the msi:

Row 1 (When control should be shown):

(Dialog)FinishedForm (Control)CheckboxLaunch (Action)Show (Condition)REMOVE=""

Row 2 (When control should be invisible):

(Dialog)FinishedForm (Control)CheckboxLaunch (Action)Hide (Condition)REMOVE<>""

P.S. I am using VS 2010, on windows 7 (x64), but I believe these should work with previous versions too.

akarkoulis
+1  A: 
akarkoulis
A: 

Hello guys,

Is there a way to execute the application even in /quiet or /passive mode?

I logged the difference between a normal installation, a /quiet and a /silent, but I can't see why it is not launching my application. The property LAUNCHAPP is always set to 1. But the CustomAction is not triggered. Is it because there is no FinishedForm to trigger it or something like that?

I also noticed that even when uninstalling, the setup tries to launch my application by returns an error code of:

Action ended 17:08:17: VSDCA_Launch. Return value 1631.

Action ended 17:08:17: FinishedForm. Return value 1.

Jeff

Jeff Caron
I managed to get something to work, but it's not really optimal:sql = "INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`,`Sequence`) VALUES ('VSDCA_Launch', 'CLIENTUILEVEL=3', '9999')";I guess there should be a better way of doing this.
Jeff Caron
A: 

Thanks Cheeso! The jscript works for me. The only problem is that when I load the msi from 'Change' in 'Add or Remove Programs' in Control Panel, check 'Remove XXX', click 'finish' and the last dialog is remove finished that the 'Launch XXX' should not appear on that dialog.

Is there any way to detect whether it's an remove finished dialog and hide the checkbox?

shiny