Deploying your VSTO Add-In to All Users (Part II)

UPDATE March 11, 2010: Office 2010 does support deploying managed add-ins to HKLM which makes the below article a little bit outdated nowdays. There is also an optional download for Office 2007 containing the same fix. Check this article out for more details.

In this post I am going to discuss how the observations made in the previous post can be incorporated into a setup project for Office 2007 Add-Ins. As with any setup we will need to address installation, un-installation and repair procedures.

  • Upon the installation of Add-In we will write registry keys that are similar to what testpropagation_create.reg file from the previous post contained. This installation will contain the Create instruction telling Office propagation mechanism to copy the registry key into HKCU profile.
  • During the uninstall, we will replace the Create instruction with Delete instruction which will cause Office to delete the corresponding registry key in HKCU hive. We will also bump up Count registry value to tell Office that the instruction needs to be carried out.
  • During the repair we will need to increase Count value. This will cause Office to copy over the registry values from HKLM into HKCU.

Let’s first take care of the installation of the required registry keys. The easiest way to achieve this is by slightly modifying the default setup project that is created alongside the regular VSTO 2005 SE project. Let’s create a new Excel 2007 Add-In called “MyAddIn”. You will notice that in addition VSTO has also created a companion MyAddInSetup project. Let’s open the Registry view and clear out all the registry entries that have been created for you (i.e. completely clear the contents under HKEY_CURRENT_USER). Now we are going to import the registry information as presented below. To achieve this you will need to copy the below lines into a .reg file, then right-click on “Registry on Target Machine” node, select “Import …” and import the .reg file you have just created (notice that first you might want to adjust the highlighted strings for your particular situation).

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MyCompany.MyAddIn\Create\Software\Microsoft\Office\Excel\Addins\MyCompany.MyAddIn]

"Description"="MyAddIn -- an addin created with VSTO technology"
"Manifest"="[TARGETDIR]MyAddIn.dll.manifest"
"FriendlyName"="MyAddIn"
"LoadBehavior"=dword:00000003
"CommandLineSafe"=dword:00000001

After you complete this operation your Registry View should be very similar to this screenshot :  

image

By adding above entries into Registry View we ensure that corresponding keys will be removed when Add-In is uninstalled. I would also suggest to set Create's key “DeleteOnUninstall” property in the Properties Window to True to make sureit is always deleted on uninstall .

Notice that now we need to add a Count value under the MyCompany.MyAddIn key. Using the Registry View to add this value would not work because any registry keys and values added through this view will be removed on uninstall. On opposite, we need Count value not just stay when Add-In is uninstalled, but its value should be incremented.

To achieve the desired effect we will create a Custom Action that will add Count value or, in case it already exists, we will simply increment its value.

Similarly, Custom Action will create a "Delete" instruction when Add-In is uninstalled.

Below I am showing code that Darryn Lavery (who put tremendous amount of effort to design and validate all I am talking about right now) has already written for his future MSDN article on this (yeah, there will be an MSDN article with the full how-to instructions) and he kindly shared it with me and I shamelessly sharing it you, but credit sitll belongs with Darryn.

Now let's move close and see how the particular Custom Action code could look like. First, let's start defining a class RegisterOffice2007AddIn with a couple of private methods:

class RegisterOffice2007AddIn {

    #region private methods

    private const string userSettingsLocation = @"Software\Microsoft\Office\12.0\User Settings";

    public void IncrementCounter(RegistryKey instructionKey) {

        int count = 1;

        object value = instructionKey.GetValue("Count");

        if (value != null) {

            if ((int)value != Int32.MaxValue)

                count = (int)value + 1;

        }

        instructionKey.SetValue("Count", count);

    }

    private string GetApplicationPath(string applicationName) {

        switch (applicationName.ToLower()) {

            case "excel":

                return @"Software\Microsoft\Office\Excel\Addins\";

            case "infopath":

                return @"Software\Microsoft\Office\InfoPath\Addins\";

            case "outlook":

                return @"Software\Microsoft\Office\Outlook\Addins\";

            case "powerpoint":

                return @"Software\Microsoft\Office\PowerPoint\Addins\";

            case "word":

                return @"Software\Microsoft\Office\Word\Addins\";

            case "visio":

                return @"Software\Microsoft\Visio\Addins\";

            case "project":

                return @"Software\Microsoft\Office\MS Project\Addins\";

            default:

                throw new Exception(applicationName + " is not a supported application", null);

        }

    }

    # endregion

 

The code above contains helper method IncrementCounter that is responsible for correctly updating the Count registry value. GetApplicationPath helper method returns application-specific path Add-Ins registration key. Now, we are ready to move to the top-level function that will be called during Install and Repair:

 

public void RegisterAddIn(string addInName) {

    RegistryKey userSettingsKey = null;

    RegistryKey instructionKey = null;

    try {

        userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);

        if (userSettingsKey == null) {

            throw new Exception("Internal error: Office User Settings key does not exist", null);

        }

        instructionKey = userSettingsKey.OpenSubKey(addInName, true);

        if (instructionKey == null) {

            instructionKey = userSettingsKey.CreateSubKey(addInName);

        } else {

            // Remove the Delete instruction

            try {

                instructionKey.DeleteSubKeyTree("DELETE");

            } catch (ArgumentException) { } // Delete instruction did not exist but that is ok.

        }

        IncrementCounter(instructionKey);

    } finally {

        if (instructionKey != null)

            instructionKey.Close();

        if (userSettingsKey != null)

            userSettingsKey.Close();

    }

}

In the above method, we first make sure the "Delete" instruction is gone and then we increment the Counter value. Notice that "Create" instruction is not explicitly installed by the Custom Action - this is handled by the installer automatically because of our previous work with the Register View.

And, finally, the function that will be called during uninstall:

public void UnRegisterAddIn(string applicationName, string addInName) {

    RegistryKey userSettingsKey = null;

    RegistryKey instructionKey = null;

    RegistryKey deleteKey = null;

    try {

        userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);

        if (userSettingsKey == null) {

            throw new Exception("Internal error: Office User Settings key does not exist", null);

        }

        instructionKey = userSettingsKey.OpenSubKey(addInName, true);

        if (instructionKey == null) {

            instructionKey = userSettingsKey.CreateSubKey(addInName);

        } else {

            // Make sure there is no Create instruction

    try {

                instructionKey.DeleteSubKeyTree("CREATE");

            } catch (ArgumentException) { } // Create instruction did not exist but that is ok.

        }

        string instructionString =

                        @"DELETE\" +

                        GetApplicationPath(applicationName) +

                        @"\" +

                        addInName;

        deleteKey = instructionKey.CreateSubKey(instructionString);

        IncrementCounter(instructionKey);

    } finally {

        if (deleteKey != null)

            deleteKey.Close();

        if (instructionKey != null)

            instructionKey.Close();

        if (userSettingsKey != null)

            userSettingsKey.Close();

    }

}

This is pretty much it. Assuming you have already experimented with SetSecurity Custom Action you should be able to wrap this code into a Custom Action DLL, set CustomActionData properties as /addinName="MyCompany.MyAddIn" /application="Excel" (again choose the value that fit your particular case), and invoke the above methods on Install, Uninstall and Rollback. I will try to sum it all up in another post though.