Creating Useful Installers with Custom Actions
So you are almost done with implementing a ground breaking, state of the art, super duper plugin-enabled calculator application with syntax highlighting and now you are wondering how to get it deployed? In case you are already trying to put some arguments together to convince your boss for buying a suite to building Microsoft Installer packages worth several thousand dollars: Stop! Don’t do that. Well, at least not if your application is not as complex as let’s say your development environment.
Using Microsoft Visual Studio you should be able to create an MSI in literally no time by using the predefined Setup Project template and if you need to accomplish additional tasks that Windows Installer does not support out of the box, read on to find out how to extend your installer with own custom actions.
The Vision
A couple of years ago market researcher Evans Data noticed a massive drop of C++ developers and also forecasted a continuous decrease. Bjarne Stroustrup on the other hand said that the number of C++ developers is higher than ever (he’s the inventor of C++ by the way). Whoever you trust, you are most probably reading this because you want to know how to use C# or VB.NET for writing custom actions in Visual Studio instead of using good old C++. So let’s dive into that topic by creating a not so fictional case scenario: The first task you would want to accomplish is creating an eventlog source for your application (because you read that the installation of an application is the suggested moment for doing just that)
Note: We will do a lot more than that, so I have included a small sample solution for you to easily reproduce what I am talking about. You can download it from https://devshaped.com/files/MyCalcSln.zip.
The Solution
As I said earlier, you could use C++ to implement a native custom action. However, the easiest solution for getting this done using C# or VB.NET is deriving a class from the Installer Class and overriding the Install method. Then you would add your custom action to the Custom Action Table of the installer.
So let’s start!
Create the Custom Action
Add an Installer by right clicking your project and select Add > New Item > Installer Class. Name it CreateEventSource.cs for now.
Figure 1- Adding an Installer Class
Next right click the CreateEventSource.cs file in the solution explorer and select View Code. Replace it with the following (you may want to adjust the namespace and the actual event source name):
{
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
[RunInstaller(true)]
public partial class CreateEventSource : Installer
{
const string EventSourceName = "MyCalc";
public CreateEventSource()
{
InitializeComponent();
}
public override void Install(System.Collections.IDictionary stateSaver)
{
try
{
base.Install(stateSaver);
EventLog.CreateEventSource(EventSourceName, "Application");
EventLog.WriteEntry(EventSourceName, "Eventsource created");
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(
string.Format("Error performing installer tasks: {0}", ex.Message));
}
}
public override void Uninstall(System.Collections.IDictionary savedState)
{
try
{
base.Uninstall(savedState);
EventLog.DeleteEventSource(EventSourceName);
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(
string.Format("Error permorming uninstaller task: {0}", ex.Message));
}
}
}
}
As you can see, deriving from the Installer base class is not enough. You need to add the attribute RunInstaller to the class and initialize it with true as shown above. This basically marks the CreateEventSource class as an Installer class and makes it visible to reflection.
Create the setup project
Simply add a new Setup Project to your solution by right clicking your solution in the solution explorer and then Add > New Project > Other Project Types > Setup Project as shown in the following figure:
Figure 2 – Adding a Setup Project
To let the Setup Project know what to install, right click the project and select Add > Project Output… Make sure your application project is selected as Project and choose Primary Output. Click OK.
Figure 3 – Adding Project Output to Setup Project
With these simple steps you just created a setup that is able to install your application. Now you would make sure your custom actions are called and configure some simple settings. Right click the Setup Project in the solution explorer and select View > Custom Actions.
Now right click the Custom Actions node and select Add Custom Action. Look in File System on Target Machine and double click Application Folder. Select the Primary Output from your application project and click OK.
Figure 4 – Custom Action View
Easy. This will create the eventlog source for you by making use of the Installer Class you created earlier. If you would build your setup now and install it, the application will be defaulted to [ProgramFilesFolder][Manufacturer]\[ProductName]. Of course it is up to you to change the default target path. In my case, C:\Program Files\TOP TECHNOLOGIES CONSULTING GmbH\MyCalc would be a little like breaking a fly on the wheel. So I right click the Setup Project in the solution explorer and select View > File System and choose Application Folder. In the properties pane, I can configure the DefaultLocation to something like [ProgramFilesFolder][ProductName]. That’s better.
Install your application
Now let’s finally make a real job of it. Right click the Setup Project in the solution explorer and select Build. After being built successfully, right click the project again and select Install.
You should be familiar with the dialogs that follow. However, after your setup successfully installed your top notch calculator, have a look at the event viewer by clicking Start and entering eventvwr.
You will find the following entry in the application log:
Figure 5 – Eventlog Entry
Observations
When using a fully featured environment for creating and using Custom Actions such as InstallShield from Acresso or Windows Installer Xml from Microsoft, you have absolute control over when and how your custom action is actually called. You can start it during the interview phase of your setup (aka UI sequence), during the immediate phase of the execution sequence when the actual installation script is generated or use it as a deferred custom action that is run in within the system context while the system changes are really applied.
Custom Actions implemented as Installer Classes are only run deferred in the system context. That means that your application is already successfully installed at that time. You have full access to all your files and also the configuration. You could implement a Custom Action for instance that alters configuration settings of your application based upon properties set by the user during the UI sequence (I will show you how later).
If you want to dive into the details of the Microsoft Windows Installer installation process, go ahead and read Alex Shevchuck’s TechNet entry here. This is a great resource for a decent understanding about what’s going on under the hood.
Potentialities (or: What the heck is CustomActionData?)
When looking at the choices in the View context menu of your Setup Project, you already noticed the following entries:
- File System
- Registry
- File Type
- User Interface
- Custom Actions
- Launch Conditions
What most certainly caught your attention is the User Interface entry. Yes! You can really add custom UI dialogs to a Visual Studio Setup Project. Although it is possible creating real custom dialogs, you can instantly start using own dialogs by choosing from the provided templates. Since we already agreed that we are talking about relatively simple applications here, in most cases that will get you started immediately (if you really really need more than that, look here).
Changing Application Settings During Installation
Let’s have some fun with custom dialogs to do something really useful here you most probably won’t instantly find by poking keywords into a search engine. Looking into the functional specification of your calculator you notice that one of your bosses (the one who usually has the most intriguing ideas of all) demanded that the calculator should welcome the user with his/her name. You heard him mumbling something about “Unique Selling Point” while he left the conference room…
So here is what you do:
Create a splash screen
Right click your calculator application and select Add > Windows Form. Name the form Welcome.cs and set its properties to the following values:
Property |
Value |
FormBorderStyle |
None |
ShowInTaskbar |
False |
TopMost |
True |
StartPosition |
CenterScreen |
Drag and drop three label controls into the form like this:
Figure 6 – Borderless splash screen
Create and bind an application setting to a label
Now configure the UserNameLabel label. In the property pane expand the ApplicationSettings node and click the ellipsis button (…) to the right of the Property Binding setting. Select the Text property, expand the drop down list and select New… Enter UserName as Name and Application as Scope (we’ll assume that the user who installs the calculator won’t let anyone else start it).
At this moment, Visual Studio automatically creates an app.config file for you that should basically look like this:
xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="MyCalcWin.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> </configSections> <applicationSettings> <MyCalcWin.Properties.Settings> <setting name="UserName" serializeAs="String"> <value /> </setting> </MyCalcWin.Properties.Settings> </applicationSettings> </configuration>
If you have a close look at the Welcome.Designer.cs file you will find the following line that does the binding for you:
Let the splash screen appear at startup
To make the splash screen appear, adjust the Load method of your main form like this:
{
this.Hide();
var splashScreen = new Welcome();
splashScreen.Show();
splashScreen.Update();
System.Threading.Thread.Sleep(5000);
splashScreen.Close();
this.Visible = true;
}
Now let’s finally wire things up with the setup project.
Create a custom dialog
Right click the Setup Project in the solution explorer and select View > User Interface. Right click the Start node in the Install tree and select Add dialog. Choose Textboxes (A) and click OK.
Figure 7 – Adding a Custom Dialog
Drag and drop the new dialog in front of the Confirm Installation node. Select it and change its properties to the following values:
Property |
Value |
BannerText |
Enter your name |
BodyText |
MyCalc is the only calculator that will welcome you personally on each startup! Just tell it who you are. |
Edit1Label |
Your name: |
Edit1Property |
USERNAME |
Edit2Visible |
False |
Edit3Visible |
False |
Edit4Visible |
False |
Now right click again the Setup Project in the solution explorer and select View > Custom Actions. Select Primary Output from MyCalcWin (Active) right under the Install node and change its CustomActionData property value to /userName="[USERNAME]".
Create a new Custom Action
Now comes the tricky part. We are talking here of at least a .NET 2.0 application. As we already saw, we are working with real type safe and supposedly easy to use settings utilizing the data bound Settings.settings file. Fortunately, .NET already generated a type safe wrapper for us. Unfortunately you cannot access the settings as easily as from within the application due to the fact that the installer simply does not have a mapper for it (and we won’t supply it with one).
What you can do though is this:
Create another custom action as you already did before, call it RegisterUser and replace the generated code with the following:
{
using System.Collections;
using System.ComponentModel;
using System.Configuration;
using System.Configuration.Install;
using System.Xml;
[RunInstaller(true)]
public partial class RegisterUser : Installer
{
public RegisterUser()
{
InitializeComponent();
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
// Get username from context
var userName = this.Context.Parameters["userName"];
// Context contains assemblypath by default
var assemblyPath = this.Context.Parameters["assemblypath"];
// Get UserName setting in app.config
var config = ConfigurationManager.OpenExeConfiguration(assemblyPath);
var sectionGroup = config.GetSectionGroup("applicationSettings");
var section = sectionGroup.Sections["MyCalcWin.Properties.Settings"]
var settingElement = section.Settings.Get("UserName");
// Create new value node
var doc = new XmlDocument();
var newValue = doc.CreateElement("value");
newValue.InnerText = userName;
// Set new UserName value
settingElement.Value.ValueXml = newValue;
// Save changes
section.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Modified);
}
}
}
As you can see, we can access the Custom Action Data we prepared earlier through the InstallContext Parameters collection. Also the InstallContext already contains a couple of default parameters such as the assemblypath (which we need to access the applications’ configuration file).
What we are doing here is loading the .exe.config file of our application, digging through to the UserName setting, creating a new Xml node containing the username value and finally putting the value into the configuration before we are saving the changes. It is really as easy as that. Wasn’t that obvious?
Now compile and install your application.
The setup will ask you for your username:
Figure 8 – User registration dialog
When starting the calculator, the splash screen welcomes you:
Figure 9 – Actual splash screen
Alternatives
Over and above the samples you saw so far there are several alternatives to using the Installer Class provided by Visual Studio. One is using native C++ (the languageWindows Installer is actually developed in, which is why it is the most powerful way of implementing custom actions). Explaining how that works is out of the scope of this article, but you can read about it for example on Steven Bone’s Blog who published three absolutely awesome articles about it here.
A native custom action that accesses the MSI log for instance basically looks like this:
extern "C" UINT __stdcall Install(MSIHANDLE hInstall) { PMSIHANDLE hRecord = MsiCreateRecord(0); MsiRecordSetString(hRecord, 0, TEXT("Native C++ is better than Installer Class!")); MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_INFO), hRecord); return ERROR_SUCCESS; }
Using DTF (Deployment Tools Foundation), which is part of the Windows Installer Xml Toolset, enables you to implement Custom Actions with managed code. However, you need to make sure .NET Framework is already installed on the computer you are running the setup on and also have a couple of prerequisites to fulfill.
Action |
Installer Class |
Native C++ DLL |
WiX DTF |
Run in UI sequence |
X |
X |
|
Run immediate in execute sequence |
X |
X |
|
Run deferred in execute sequence |
X |
X |
X |
Read MSI properties |
X[1] |
X |
X |
Write MSI properties |
X |
X |
|
Debuggable |
X |
X |
X [2] |
Access MSI Logfile |
X |
X |
|
Access Installstate log |
X |
||
.NET Framework must be installed |
X |
X |
|
Windows Installer Xml must be installed |
X |
Conclusion
In many cases, installation needs are straightforward enough that it is not worth the effort to implement anything more complex than the Custom Actions that run deferred in system context. Although you gain more control over the installation process when you choose to implement Windows Installer Xml based Custom Actions or native CA’s, Installer class based Custom Actions can be a great way to extend your Visual Studio based Setup Project without the need to invest in additional installer solutions.
[1] Through stateSaver dictionary collection
[2] Simple unit testing possibility via wrapping the MSI session
—–
Christian Jacob is an IT Consultant with TOP TECHNOLOGIES CONSULTING, a Microsoft Gold Partner with focus on infrastructures, security and business solutions. He primarily uses C# and VB.NET in nowadays development and has his roots in C++ based game development. He spends a lot of time in researching and creating innovative solutions around team development, Windows Installer, and TOP TECHNOLOGIES’ main software products. He currently resides nearby Hamburg in North Germany.
Hi Sir,
This is very nice article. It helped me a lot. This is more explanatory than all the other articles which I read.
Many thanks…
This seems to be a helpful article, except for one thing: You initially implied this was for both C# and VB users. Yet the entire article is centered around C# code. No VB to be found here. Some of us don’t know anything about C# (and maybe don’t want to know) and are unable to do the conversion in our heads. Shame.
Steven,
You can use a converter such as http://www.developerfusion.com/tools/convert/csharp-to-vb/ to convert the C# to VB.
The techniques are applicable in either language even if the samples are only in C#.
Thanks for the reply. Perhaps you can help me.
What I am trying to figure out how to do is this: At the end of the application installation, I want to run a program that writes to a text file that resides in the installation directory.
I have determined that custom actions are the way to go, however I am new to .Net programming and all the examples I have found through Google do this or that, but not what I need. My inexperience is showing that I can’t figure it out from the samples I mentioned. This used to be such a simple thing in VB6!
Many thanks if you are able to help.
Steve
More specifically, how do I get the install directory during Commit?
Steve
Ok, that was simpler than expected.
Set the ‘CustomActionData’ property of a custom action to /INSTALLDIR=”[TARGETDIR]\”
and then access it by Me.Context.Parameters(”INSTALLDIR”).ToString.
Simple enough. Now, to figure out how to get the path that the installer was run from…
I would like the install to wait until a certain process has terminated before starting the install. Ie. warning the user to shutdown the app before we upgrade it. I tried the custom actions but then found out that the install has occurred before my code gets hit. I know how to check for running processes but can’t figure out how to integrate this in the MSI/install. Any suggestions?
Your example has been extremely helpful. Prior to this, I’d spent 1 full day struggling to include the custom actions with my application setup.
Thanks a lot