RSS Feed

Creating Useful Installers with Custom Actions

Posted on Wednesday, April 1, 2009 in Practical Programming

by Christian Jacob

christian-jacob 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.

clip_image002

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):

namespace MyCalcWin.Custom_Actions

  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:

clip_image004

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.

clip_image006

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.

clip_image008

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:

clip_image010

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:

clip_image012

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:

this.UserNameLabel.Text = global::MyCalcWin.Properties.Settings.Default.UserName;

Let the splash screen appear at startup

To make the splash screen appear, adjust the Load method of your main form like this:

private void Form1_Load(object sender, EventArgs e)

{

  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.

clip_image014

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:

namespace MyCalcWin.Custom_Actions

{

  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"] 

                    as ClientSettingsSection;

      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:

clip_image016

Figure 8 – User registration dialog

When starting the calculator, the splash screen welcomes you:

clip_image018

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.

Bring on the comments

  1. Pavan kumar says:

    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…

  2. Steven Elliott says:

    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.

  3. Derek says:

    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#.

  4. Steven Elliott says:

    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

  5. Steven Elliott says:

    More specifically, how do I get the install directory during Commit?

    Steve

  6. Steven Elliott says:

    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…

  7. Peter says:

    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?

  8. Aniruddha Loya says:

    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

`

Bad Behavior has blocked 389 access attempts in the last 7 days.