Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C#
Article

How to Pass Command Line Arguments to MSI Installer Custom Actions

Rate me:
Please Sign up or sign in to vote.
4.87/5 (33 votes)
14 Dec 20065 min read 392.4K   5.5K   102   30
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


Written By
Web Developer
United States United States
I have been programming for about 15 years, starting off with simple BASIC on the venerable C=64 and progressing all the way to C#.

I am currently for hire and contracting projects, so if you have opportunities in the Mid-West area, feel free to contact me! My strengths include C#/ASP.NET, MS-SQL Server, Client/Server/Distributed Architectures, C++/ATL7/MFC, and JAVA (Servlets/J2ME).

I am currently developing a live ranking system for Halo at www.haloranking.com, stop by and check it out!

Comments and Discussions

 
Questionno log file but pass direct to form1.cs Pin
Pax12518-Jan-18 3:41
Pax12518-Jan-18 3:41 
QuestionHow to get command line parameters values instead of default forms parameters? Pin
Francois M11-Aug-16 23:29
Francois M11-Aug-16 23:29 
AnswerRe: How to get command line parameters values instead of default forms parameters? Pin
Francois M12-Aug-16 10:16
Francois M12-Aug-16 10:16 
QuestionGreat article! Pin
Francois M13-Jul-16 4:08
Francois M13-Jul-16 4:08 
QuestionVisual studio settup and deployment Pin
link1111-Mar-16 8:40
link1111-Mar-16 8:40 
Hello,

Is it possible to have the canned U.I. elements for user input, then bypass them with command line arguments or do i need to build a custom action for silent install? example: I have a check box that says "enable autologon?" then asks for the name, password, and domain. If the checkbox is ticked, it writes the values to the registry and turns them on. Is it possible to use this same MSI and have a user pass example: "true,myname,mypass,mydomain" and have it skip the canned U.I. dialog?

thanks in advance...
QuestionGreat Article Pin
Member 1160428924-Apr-15 6:33
Member 1160428924-Apr-15 6:33 
Questioninno setup !how to set argument not get argument? Pin
forcj92829-Dec-13 20:03
forcj92829-Dec-13 20:03 
QuestionWindows 7 Pin
udaygreddy20-Nov-13 7:15
udaygreddy20-Nov-13 7:15 
SuggestionAdd Optional Features from the Command Line Pin
Member 1035260222-Oct-13 6:17
Member 1035260222-Oct-13 6:17 
Questioni want add different installation paths to msi while creating it in c# Pin
Muhammad Azym10-Sep-13 1:12
professionalMuhammad Azym10-Sep-13 1:12 
GeneralMy vote of 5 Pin
Renju Vinod12-Aug-12 21:27
professionalRenju Vinod12-Aug-12 21:27 
GeneralMy vote of 5 Pin
Abhinav S20-Mar-11 23:10
Abhinav S20-Mar-11 23:10 
GeneralMy vote of 5 Pin
Apogra17-Feb-11 20:20
Apogra17-Feb-11 20:20 
QuestionInterface and command line? Pin
Jacob D Dixon25-Nov-10 19:34
Jacob D Dixon25-Nov-10 19:34 
AnswerRe: Interface and command line? Pin
dpe17-Mar-11 5:06
dpe17-Mar-11 5:06 
GeneralRollback Pin
Mogyi17-Oct-08 2:34
Mogyi17-Oct-08 2:34 
GeneralChanging parameter name failed! Pin
Martin Welker3-Nov-07 3:37
Martin Welker3-Nov-07 3:37 
GeneralRe: Changing parameter name failed! Pin
john_mcp8-Feb-08 12:15
john_mcp8-Feb-08 12:15 
GeneralCreating MSI package for more than one package Pin
Manjunath S6-Mar-07 22:33
Manjunath S6-Mar-07 22:33 
GeneralRe: Creating MSI package for more than one package Pin
xugglybug19-Jun-07 22:59
xugglybug19-Jun-07 22:59 
AnswerRe: Creating MSI package for more than one package Pin
Sameer Jagdale27-Jun-07 23:44
Sameer Jagdale27-Jun-07 23:44 
GeneralEnsure you use Capital letters for parameter names Pin
Mike Liddell20-Dec-06 14:15
Mike Liddell20-Dec-06 14:15 
GeneralRe: Ensure you use Capital letters for parameter names Pin
Luke/7619-Apr-07 22:06
Luke/7619-Apr-07 22:06 
QuestionDefault parameters Pin
Salam Y. ELIAS19-Dec-06 2:51
professionalSalam Y. ELIAS19-Dec-06 2:51 
AnswerRe: Default parameters Pin
Nick Pirocanac19-Dec-06 4:12
Nick Pirocanac19-Dec-06 4:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.