Writing an Installer Class for a Visual Studio Addin

Easy Visual Studio addin installation

Published on Friday, February 22, 2008

You've struggled through understanding commands and toolbars, pulled your hair out deciphering confusing API's, and scarred your neighborhood with red-faced screams of frustration as you debug unhandled exceptions that crash visual studio.  After weeks, months or yes even years polishing an addin you still aren't finished until you create an installer program.

The installer projects available in visual studio make creating an installer a trivial affair; however there are a few things to consider when making an installer for an addin project.

The .addin file

When you create a new addin project in visual studio 2005 or 2008 you will have an xml file in the root directory of your application named YourProjectName.addin.  Opening this file will show you the base structure of the xml that among other things defines the location of your addin assembly.  The trick of installing the addin via an installer project is placing this file in an appropriate location on the user's workstation as well as modifying the xml to point to the location where your addin is installed.

Addins for visual studio 2005 and 2008 are installed by placing the .addin file in one of several locations on a developer's workstation.  These locations are managed through visual studio under the Tools > Options > Environment > Add-in/Macro Security options page.  Here you will find a list of paths that visual studio will investigate when it loads to locate various addins that you may have installed.

I caution you on modifying this list; most addins that are written will be installed in one of the default paths specified by here.  If you change these default paths you could easily lose access to any addins that you already have installed.

Where to put your .addin file

For my addin, .netSavant, I use the following line of code during my installation process to generate a file path that the .addin file will be saved to:

Path.Combine(Environment.GetEnvironmentVariable("ALLUSERSPROFILE"), @"Application Data\Microsoft\MSEnvShared\Addins\DotNetSavant.AddIn");

This will generate a string value that looks like

> C:\Documents and Settings\All Users\Application Data\Microsoft\MSEnvShared\Addins\DotNetSavant.Addin 

(on my laptop).  Again, there are other locations that you could save the file to though I've found this one to be the most reliable as far as it's existence on developer workstations, and being configured in visual studio (its included in vs.net by default).

One complexity to think of; If your user is installing on a non-english version of windows, I'm honestly not sure how to best compose the file save path.  I suppose I'll have to deal with this eventuality eventually.  Until then I'm going to live in my quiet and warm happy place.

Creating the Installer Class

As with any installer project, if you want to perform some kind of custom installation action you'll need to create an Installer class somewhere in your application.  This is as simple as creating a new class file and having it inherit from System.Configuration.Installer.  You'll need to decorate your new class with the RunInstaller(true) attribute and override the Install, Uninstall, and Rollback methods.  These methods will be used to install your .addin file, remove your .addin file when your addin is uninstalled and roll back any installation tasks that you've performed if there is an error during the installation process.

Overriding Install

The most complex method of the installation process, this is where you'll create your .addin file and save it to the path that I've mentioned above.

For .netSavant I store my .addin file in the root directory of my project and set its build action to "Embedded Resource".  This way I can make simple adjustments to the file and it will always be available as a resource of the assembly.  This comes in handy during the installation process, specifically the Install method because I can access the embedded resource, modify the path defined in the Assembly nodes and write the xml contents to disk.

Since the release of vs.net 2008 I've updated my own installer to use a block of LINQ that looks something like this:

XDocument linqXmlDocument = null;
string installationPath = base.Context.Parameters["AssemblyPath"];
string addinResourceFile = Assembly.GetExecutingAssembly().GetName().Name + ".DotNetSavant.addin";

using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(addinResourceFile)) {
    linqXmlDocument = XDocument.Load(XmlReader.Create(resourceStream));
}
var query = from assemblyNode in linqXmlDocument.Descendants()
    where assemblyNode.Name.LocalName.ToLower().Equals("assembly")
    select assemblyNode;

foreach (XElement assemblyNode in query) {
    assemblyNode.SetValue(installationPath);
}
string addinXml = linqXmlDocument.ToString();

Note the use of base.Context.Parameters["assemblypath"].  The base installer object gives you access to these values to access form information collected during the installation process.  In this case I'm accessing the path that the user specified as the installation directory for the addin.

Overriding Uninstall

When uninstalling your addin, it would be prudent to delete the .addin file from the user's file system.  Its just common sense that when you uninstall something you should remove everything that was added to the users computer during the installation process.

Overriding Rollback

The rollback method is where you will implement any clean up code that will be run if the installation process needs to terminate before its completion.  Be aware that you'll not know at what point in the installation process this method might be invoked by your installer package, so any cleanup that you perform should be wrapped inside of try blocks so that the rollback method can execute cleanly.

Back to the Installer Project

Now that you have an installer class it's a simple matter to wire it up to your installer.  Make sure that your installer references either the build output of your project or the compiled assembly of your project directly.  Then, right click on the installer project and select View > Custom Actions.  You'll see four folder icons labeled Install, Commit, Rollback and Uninstall.  Simply right click on a folder, click "add custom action" and browse to the assembly that contains your installer class.  Do this once each for the Install, Rollback and Uninstall folders to end up with a configuration that looks something like this:

Installer - Custom Actions

At this point the installer project is configured to use the custom installation process defined by the installer class.  When the installer is run the .addin file will be successfully written to a legitimate addin path as part of the installation process.  Conversely it will be removed from the file system during uninstallation.

Potential Issues

I hinted earlier that non-english users might have issues installing the addin using my methodology.  This is because I hard-code part of the .addin installation path in English during my install process.  It will be impossible to guarantee that the paths specified by default in visual studio will always be in English, thus we have a potential localization problem to deal with.  Remember that if you don't see your addin in the addin manager in visual studio you most likely saved your .addin file to a path that is not recognized by the development environment.

Another common issue that is easy to overlook; make sure that you've specified a valid path to your assembly in your .addin files' Assembly node.  If this path is incorrect visual studio will not load your addin.