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.
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.
Fig. 2: Open the Custom Actions view.
Fig. 3: Add the primary project output to the Install Custom Action.
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:
- Show a dialog window during installation that shows two sample command line parameters (
MyCustomParameter
and MyOtherCustomParameter
). - Persist the values of the sample parameters to the state dictionary.
- Show a dialog window during uninstallation, that shows the persisted values of the sample parameters.
public override void Install(System.Collections.IDictionary stateSaver)
{
CustomParameters customParameters = new CustomParameters(this.Context);
SaveCustomParametersInStateSaverDictionary(
stateSaver, customParameters);
PrintMessage("The application is being installed.",
customParameters);
base.Install(stateSaver);
}
private void SaveCustomParametersInStateSaverDictionary(
System.Collections.IDictionary stateSaver,
CustomParameters customParameters)
{
if (stateSaver.Contains(CustomParameters.Keys.MyCustomParameter) == true)
stateSaver[CustomParameters.Keys.MyCustomParameter] =
customParameters.MyCustomParameter;
else
stateSaver.Add(CustomParameters.Keys.MyCustomParameter,
customParameters.MyCustomParameter);
if (stateSaver.Contains(
CustomParameters.Keys.MyOtherCustomParameter) == true)
stateSaver[CustomParameters.Keys.MyOtherCustomParameter] =
customParameters.MyOtherCustomParameter;
else
stateSaver.Add(CustomParameters.Keys.MyOtherCustomParameter,
customParameters.MyOtherCustomParameter);
}
public override void Uninstall(
System.Collections.IDictionary savedState)
{
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.
public class CustomParameters
{
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; }
}
public CustomParameters(InstallContext installContext)
{
this._myCustomParameter =
installContext.Parameters[Keys.MyCustomParameter];
this._myOtherCustomParameter =
installContext.Parameters[Keys.MyOtherCustomParameter];
}
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.