Tag Archives: Installer

How to run application after VS MSI installer finish

Sometimes you need to do something that is not provided ‘out-of-the-box’ – as provided by the default tools you use. Take Visual Studio’s built in ‘Set-up’ and installation projects as an example.

I wanted to simply add the option to launch the executable that was installed during the install process once the installer ends. Sounds simple and there are ways of doing this with and without Visual Studio using custom installer classes plus ‘Custom Actions’ is possible but it has its drawbacks – like it suspend the installer or you have to write extra code inside your app just to cater for that.

Interestingly enough, MSI installers actually supports this functionality ‘natively’ i.e. it is a built-in feature of MSI technology to have the option for ‘Run my app after the installer ends’. The problem is just that Visual Studio doesn’t expose this functionality itself. The only way to ‘enable’ this feature is to modify the MSI package afterwards – through a ‘Post build action’ or manually (or you can use msbuild or any other build process tool).

To do this you can use a JavaScript script that simply makes the required changes to the MSI package (its internal database). See Aaron Stebner’s blog for more details. The example script to do this is located here.

I initially had trouble getting the working paths correct for my specific project but it turned out the normal ‘build’ command in VS does not always rebuild the solution. Instead you have to explicitly use the ‘Rebuild’ command on the Set-up project to make sure it recreate the MSI using the post build action script.

To summarize how to do it:

  1. First download a copy of the ‘EnableLaunchApplication.js’ script to the set-up projects directory – the directory where the .vdproj file is located.
  2. Then edit the js file to specify the actual exe name of your application you want to ‘launch’ after the MSI finish. You can of course customize it further.
  3. In the Visual Studio set up project’s project properties specify the following ‘PostBuildEvent’: cscript.exe “$(ProjectDir)EnableLaunchApplication.js” “$(BuiltOuputPath)”
  4. Take not that the spelling mistake (BuiltOuputPath) is deliberate since that is how the VS team implemented it (was probably a typo which they never fixed)
  5. Now you can use the ‘Rebuild’ command for the (set-up) project.

and thats how it works. Have fun!

QuickMon Version 2.3 and Installers

I’ve now updated the QuickMon installers to include some ‘new’ functionality that allows you to choose to install the Windows service as part of the MSI installation.

There are still a few possible quirks – like what happens when you choose not to install the service initially (and do it manually afterwards) or when uninstalling the MSI (without removing the service manually) and then installing a new version ‘and then ‘choosing to install the service. In that case you get an error message that the service is already installed (duh as if you don’t know), the MSI Installer fails and rolls back completely. The solution (one of them) is to simply install the MSI and not choose to install/register the service in Service Manager.

Installers

I’ve learned a whole bunch of new ‘things’ about the standard VS installer during all of this. Mostly how limited the build-in Installer technology is but also some tweaks you can do to still (despite) get something good out of it.

Here is a few tips I came across:

1. When you add a ‘Checkboxes’ custom dialog to the ‘User Interface’ of the installer and want to use the checkbox value as a condition in one of the Custom Action it must look like this e.g: ‘CHKINSTALLSERVICE=1’  (without the quotes). I tried all kind of weird combinations like =”True”, =”1″ etc. that didn’t work…

2. You can have the MSI Installer call the ‘Installer’ classes housed inside your service project (and automatically install it) by adding a ‘Custom Action’ – ‘Install’ action. The default for it is to look for any installer classes inside the assembly. Similarly you can add the same for ‘Uninstall’ but this requires that the service has actually been installed (which is a problem for me since the user can choose not to install it at all)

3. Adding the “-install” and “-uninstall” parameters is nice and all but please remember to run it under an Administrator account (elevated) otherwise it simply fails with an “Access is denied” error. If you understand why then it seems obvious but for people that do not know about this it could be a pain (the error message does not tell you why ‘Access is denied…’)

4. The whole ‘backwards compatibility ID generation thing still baffles me. Upgraded projects (from VS2008 to VS2010) does not show it and the default on VS2010 is false. If not set it seems each time you install a newer version any copy of your shortcuts (like pinned to the taskbar) becomes ‘invalid. This is really a big pain (yes, it is probably easy enough to just unpin/pin again to fix it…)

5. The whole ‘Target platform’ thing is a mess – especially when the solution is under source control. Why do you have to check out the whole project just to change the Target platform setting to rebuild the MSI installer? Why is there not an option to build ‘both’ x86 and x64 at the same time (separate directories or something)?

6. Yes, using a better Installer tool like Wix will probably help… still need to learn how to use it. Anyone have some good tutorials somewhere? 🙂

 

Creating a self installing Windows Service

Being in the business of creating several Windows Services myself (EventScavenger, Event Collector, QuickMon) I started thinking about how to make the whole install and set up process a bit easier and eliminating the manual steps to install a Windows Service.

So what if you can simply run the service ‘exe’ with a “-install” or “-uninstall” parameter and let it do the work for you? Additionally, what if it it can even prompt you for start up type and user account details during the process – all of this without even using installutil…?

Well after some research, testing, trail and error, losing some more hair and eventually finding a way to make it work I now have a way to do all this. Nice thing is it can be used together with an MSI installer as well or even an external app or script.

ProjectInstaller

The trick is to create a custom ProjectInstaller class (not use the wizards that comes with VS to add installers). I’ve added some properties to this class to specify things like Service name, display name, start up type, account type, user name and password.

[RunInstaller(true)]
public class ProjectInstaller : Installer
{

private ServiceProcessInstaller processInstaller;
private System.ServiceProcess.ServiceInstaller serviceInstaller;

#region Added properties
public string ServiceName
{
get { return serviceInstaller.ServiceName; }
set { serviceInstaller.ServiceName = value; }
}
public string DisplayName
{
get { return serviceInstaller.DisplayName; }
set { serviceInstaller.DisplayName = value; }
}
public string Description
{
get { return serviceInstaller.Description; }
set { serviceInstaller.Description = value; }
}
public ServiceStartMode StartType
{
get { return serviceInstaller.StartType; }
set { serviceInstaller.StartType = value; }
}
public ServiceAccount Account
{
get { return processInstaller.Account; }
set { processInstaller.Account = value; }
}
public string ServiceUsername
{
get { return processInstaller.Username; }
set { processInstaller.Username = value; }
}
public string ServicePassword
{
get { return processInstaller.Password; }
set { processInstaller.Password = value; }
}
#endregion

public ProjectInstaller()
{

processInstaller = new ServiceProcessInstaller();
serviceInstaller = new System.ServiceProcess.ServiceInstaller();

Installers.AddRange(new Installer[] {
processInstaller,
serviceInstaller});

}

protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
{

SetContextParameter(“name”, ServiceName);
SetContextParameter(“display”, DisplayName);
SetContextParameter(“desc”, Description);
SetContextParameter(“start”, StartType.ToString());
SetContextParameter(“account”, Account.ToString());

if (Account == ServiceAccount.User)
{

SetContextParameter(“user”, ServiceUsername);
SetContextParameter(“password”, ServicePassword);

}
base.OnBeforeInstall(savedState);

}

public void SetContextParameter(string key, string value)
{

if (!this.Context.Parameters.ContainsKey(key))

this.Context.Parameters.Add(key, value);

else

this.Context.Parameters[key] = value;

}

}

Calling the Installer/uninstaller

In order to get the service executable to handle the -“install” or “-uninstall” parameters I modified the Main method in Program.cs to accept a string parameter array and then call the appropriate method if either of those two parameters are passed.

Example code:

static class Program
{

static void Main(string[] args)
{

if (args.Length > 0)
{

if (args[0].ToUpper() == “-INSTALL”)
{

InstallService();
return;

}
else if (args[0].ToUpper() == “-UNINSTALL”)
{

UnInstallService();
return;

}

}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SelfInstallService() };
ServiceBase.Run(ServicesToRun);

}

private static bool InstallService()
{

bool success = false;
try
{

string exeFullPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string workingPath = System.IO.Path.GetDirectoryName(exeFullPath);
string logPath = System.IO.Path.Combine(workingPath, “Install.log”);
ServiceStartMode startmode = ServiceStartMode.Automatic;
ServiceAccount account = ServiceAccount.LocalService;
string username = “”;
string password = “”;

InstallerForm installerForm = new InstallerForm();
installerForm.StartType = ServiceStartMode.Automatic;
installerForm.AccountType = ServiceAccount.User;
if (installerForm.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{

startmode = installerForm.StartType;
account = installerForm.AccountType;
if (installerForm.AccountType == ServiceAccount.User)
{

username = installerForm.UserName;
password = installerForm.Password;

}

}

Hashtable savedState = new Hashtable();
ProjectInstaller myProjectInstaller = new ProjectInstaller();
InstallContext myInstallContext = new InstallContext(logPath, new string[] { });
myProjectInstaller.Context = myInstallContext;
myProjectInstaller.ServiceName = “SelfInstallService”;
myProjectInstaller.DisplayName = “Self Install Service”;
myProjectInstaller.Description = “Self Install Service test”;
myProjectInstaller.StartType = startmode;
myProjectInstaller.Account = account;
if (account == ServiceAccount.User)
{

myProjectInstaller.ServiceUsername = username;
myProjectInstaller.ServicePassword = password;

}
myProjectInstaller.Context.Parameters[“AssemblyPath”] = exeFullPath;

myProjectInstaller.Install(savedState);
success = true;

}
catch (Exception ex)
{

System.Windows.Forms.MessageBox.Show(ex.Message, “Install service”, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);

}
return success;

}

private static bool UnInstallService()
{

bool success = false;
try
{

string exeFullPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string workingPath = System.IO.Path.GetDirectoryName(exeFullPath);
string logPath = System.IO.Path.Combine(workingPath, “Install.log”);

ServiceInstaller myServiceInstaller = new ServiceInstaller();
InstallContext Context = new InstallContext(logPath, null);
myServiceInstaller.Context = Context;
myServiceInstaller.ServiceName = “SelfInstallService”;
myServiceInstaller.Uninstall(null);
success = true;

}
catch (Exception ex)
{

System.Windows.Forms.MessageBox.Show(ex.Message, “Uninstall service”, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);

}
return success;

}

}

You might notice I use a custom form ‘InstallerForm’ here which I’m not showing the code for. All it is is a simple Windows Form that allows you to enter or choose some settings that this code is using. I’ll upload some code example a bit later.

Now, you can simple go to the command line (or create a batch file) that calls the service exe with either the “-install” or “-uninstall” parameters.

Update: I’ve added some sample code here. There have been some improvements since the article. It turns out the overridden method OnBeforeInstall is not necessary at all.

In a new blog post I’ve enhanced the functionality to include multi instance functionality. See this.