Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

How to Pass Command Line Arguments to MSI Installer Custom Actions

4.87/5 (33 votes)
14 Dec 20065 min read 1   5.6K  
When writing unattended installs, it's very handy to be able to send command line parameters to your custom actions that run during your application's setup.

Article Image

Introduction

This article will illustrate how to pass command line parameters to Install Custom Actions so that you can enable silent setups that can take advantage of command line supplied parameters without having to recompile the setups. Some uses of this ability might include:

  • An unattended install that is being deployed into multiple environments that would need to access web services at environment specific URLs.
  • An unattended install that would record a product key into the registry.
  • An install that is called from a batch file or a package that needs to supply data that the user would normally enter.

After spending some time Googling in the internet, I was finally able to guess my way into a method of passing this data that would work in my target setup situations:

  • Running the setup.exe file, and passing command line parameters.
  • Running the MSI file, and passing command line parameters.
  • Running MSIEXEC, and passing command line parameters.

There were several stumpers that I encountered while working on this solution, which instantly got my documentation juices flowing, so now it's article time!

The Solution

There are loads of articles out on the internet that show you how to create custom actions that your setup package can run to show custom dialogs, and perform other types of actions during an installation. I'm going to lightly cover adding the custom action, but I'm going to focus on passing the command line parameters to the custom action, and managing the data through to the uninstallation of the application.

The trick to the solution is that the command line parameters are added to a special collection inside the guts of the setup framework. This collection also houses the well known parameters such as TARGETDIR, and others. You can get a list of the contents of this collection by running any MSI file, and specifying the /l* <logfile> parameter.

Image 2

Fig. 1: Some of the properties that you have access to from a custom action.

To generate the data in the above screen, I used this command line:

msiexec /i setup.msi /qb /l* out.txt 
        MyCustomParameter=one MyOtherCustomParameter=two

While I was pretty excited to see that the command line parameters were parsed and stored into this internal collection, I had no idea how to get at the data after that point. The problem was that the Installer Custom Actions didn't seem to have any visibility to the command line arguments when the installation was run from MSIEXEC. It turns out, that you have to pass the values from the Property internal collection (Fig. 1) through more arguments that you specify in the custom actions screen in the setup project! Some pictures here are worth about a million words. Let's go ahead and add the primary project output for your application to the Custom Actions, and setup the CustomActionData property to map the command line arguments to your Custom Action's InstallContext.

Image 3

Fig. 2: Open the Custom Actions view.

Image 4

Fig. 3: Add the primary project output to the Install Custom Action.

Image 5

Fig. 4: Attach the values for each command line argument to new arguments that will be attached to the installer's InstallContext property when your custom action is run.

OK, I want to spend a little time with Fig. 4, as that's where a lot of the magic is happening. First, notice the quotes around the values. These are necessary to enable values with spaces to be passed into the Install Context from the command line. Next, notice that the values are surrounded by square brackets, and are upper case. The square brackets indicate that the value is to be filled in from the Properties collection. The value names must be uppercase, or they will not be found in the Properties collection from the installer's internal property dictionary. If you refer back to the log file captured in Fig. 1, you'll see that when the command line was parsed, the value names were all converted to uppercase. As you might have guessed, the CustomActionData property is case sensitive when it lines up the value names to the actual value in the installer's internal property directory.

So far so good, we've got the data from the command line to the installer's InstallContext property. Now, let's go to some code so that I can show you how to read it from the context into some strong type variables, and then persist it into the state dictionary so that the command line parameters can also be used during an uninstall.

First, let's take a look at the InstallerCustomActions class. This class is derived from the System.Configuration.Install.Installer class, and is marked with the RunInstaller attribute. When this combination occurs, and the project output is added to the Custom Actions screen (Fig. 4 again), then the various methods in System.Configuration.Install.Installer can be overloaded and will give you the ability to slide in your own code at key locations in the install process.

The goal of this class is to:

  1. Show a dialog window during installation that shows two sample command line parameters (MyCustomParameter and MyOtherCustomParameter).
  2. Persist the values of the sample parameters to the state dictionary.
  3. Show a dialog window during uninstallation, that shows the persisted values of the sample parameters.
C#
/// <summary>
/// To cause this method to be invoked, I added the primary project output to the 
/// setup project's custom actions, under the "Install" folder.
/// </summary>
/// <param name="stateSaver">A dictionary object
/// that will be retrievable during the uninstall process.</param>
public override void Install(System.Collections.IDictionary stateSaver)
{
    // Get the custom parameters from the install context.
    CustomParameters customParameters = new CustomParameters(this.Context);

    SaveCustomParametersInStateSaverDictionary(
                    stateSaver, customParameters);
    
    PrintMessage("The application is being installed.", 
                 customParameters);

    base.Install(stateSaver);
}

/// <summary>
/// Adds or updates the state dictionary so that custom
/// parameter values can be retrieved when 
/// the application is uninstalled.
/// </summary>
/// <param name="stateSaver">An IDictionary object
/// that will contain all the objects who's state
/// is to be persisted across installations.</param>
/// <param name="customParameters">A strong typed
/// object of custom parameters that will be saved.</param>
private void SaveCustomParametersInStateSaverDictionary(
        System.Collections.IDictionary stateSaver, 
        CustomParameters customParameters)
{
    // Add/update the "MyCustomParameter" entry in the
    // state saver so that it may be accessed on uninstall.
    if (stateSaver.Contains(CustomParameters.Keys.MyCustomParameter) == true)
        stateSaver[CustomParameters.Keys.MyCustomParameter] = 
                          customParameters.MyCustomParameter;
    else
        stateSaver.Add(CustomParameters.Keys.MyCustomParameter, 
                       customParameters.MyCustomParameter);

    // Add/update the "MyOtherCustomParameter" entry in the
    // state saver so that it may be accessed on uninstall.
    if (stateSaver.Contains(
             CustomParameters.Keys.MyOtherCustomParameter) == true)
        stateSaver[CustomParameters.Keys.MyOtherCustomParameter] = 
                   customParameters.MyOtherCustomParameter;
    else
        stateSaver.Add(CustomParameters.Keys.MyOtherCustomParameter, 
                       customParameters.MyOtherCustomParameter);
}

/// <summary>
/// To cause this method to be invoked,
/// I added the primary project output to the 
/// setup project's custom actions, under the "Uninstall" folder.
/// </summary>
/// <param name="savedState">An IDictionary
/// object that will contain objects that were set as 
/// part of the installation process.</param>
public override void Uninstall(
       System.Collections.IDictionary savedState)
{
    // Get the custom parameters from the saved state.
    CustomParameters customParameters = 
            new CustomParameters(savedState);

    PrintMessage("The application is being uninstalled.", 
                 customParameters);

    base.Uninstall(savedState);
}

Next, let's take a quick look at the CustomParameters class. This class works as a loader for the custom parameter values from either the installation context or from the installation state. The implementation is pretty straightforward, have a look at the comments for more information.

C#
public class CustomParameters
{
    /// <summary>
    /// This inner class maintains the key names
    /// for the parameter values that may be passed on the 
    /// command line.
    /// </summary>
    public class Keys
    {
        public const string MyCustomParameter = 
                           "MyCustomParameter";
        public const string MyOtherCustomParameter = 
                           "MyOtherCustomParameter";
    }

    private string _myCustomParameter = null;
    public string MyCustomParameter
    {
        get { return _myCustomParameter; }
    }

    private string _myOtherCustomParameter = null;
    public string MyOtherCustomParameter
    {
        get { return _myOtherCustomParameter; }
    }

    /// <summary>
    /// This constructor is invoked by Install class
    /// methods that have an Install Context built from 
    /// parameters specified in the command line.
    /// Rollback, Install, Commit, and intermediate methods like
    /// OnAfterInstall will all be able to use this constructor.
    /// </summary>
    /// <param name="installContext">The install context
    /// containing the command line parameters to set
    /// the strong types variables to.</param>
    public CustomParameters(InstallContext installContext)
    {
        this._myCustomParameter = 
          installContext.Parameters[Keys.MyCustomParameter];
        this._myOtherCustomParameter = 
              installContext.Parameters[Keys.MyOtherCustomParameter];
    }

    /// <summary>
    /// This constructor is used by the Install class
    /// methods that don't have an Install Context built
    /// from the command line. This method is primarily
    /// used by the Uninstall method.
    /// </summary>
    /// <param name="savedState">An IDictionary object
    /// that contains the parameters that were
    /// saved from a prior installation.</param>
    public CustomParameters(IDictionary savedState)
    {
        if(savedState.Contains(Keys.MyCustomParameter) == true)
            this._myCustomParameter = 
              (string) savedState[Keys.MyCustomParameter];

        if (savedState.Contains(Keys.MyOtherCustomParameter) == true)
            this._myOtherCustomParameter = 
              (string) savedState[Keys.MyOtherCustomParameter];
    }
}

Conclusion

Well, I hope you find this short article helpful. I found it interesting that it took so many hops to get this data from the command line to some actual code that I could control, but the flexibility was worth the effort. If you intend to install and uninstall the sample application several times, you'll find the following command lines useful.

Install the application without user intervention and full logging:

setup.msi /qb /l* log.txt MyCustomParameter=one MyOtherCustomParameter=two

Uninstall the application without user intervention:

msiexec /x {13F62DF0-E078-45C8-B0FB-185D307DB500} /qb

History

  • 12/14/2006 - Initial release to CodeProject.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here