Setup Project: Custom Actions

Sometimes you need to add some custom actions to your setup project to complete the installation by running tools like regasm, ngen or caspol. The last time I was forced to do this was when my Inventor Add-in (COM based) did not get registered properly. The problem was that the Register=vsdrpCOM setting of the project output did not get the assembly codebase set. I needed to get my setup to include the codebase in the assembly registration to get my add-in installed.

The first step was that I added a new class library project to my solution and added there a new class called RegasmInstaller.cs:

[RunInstaller(true)]
public partial class RegasmInstaller : Installer
{
    public RegasmInstaller()
    {
        InitializeComponent();
    }

    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);

        Regasm(true);
    }

    public override void Rollback(IDictionary savedState)
    {
        base.Rollback(savedState);

        Regasm(false);
    }

    public override void Uninstall(IDictionary savedState)
    {
        base.Rollback(savedState);

        Regasm(false);
    }

    private void Regasm(bool register)
    {
        string file = base.Context.Parameters["Assembly"];
        if (string.IsNullOrEmpty(file))
            throw new InstallException("Assembly not defined");
        if (!File.Exists(file))
            return;

        RegistrationServices regsrv = new RegistrationServices();
        Assembly assembly = Assembly.LoadFrom(file);

        if (register)
        {
            regsrv.RegisterAssembly(assembly, AssemblyRegistrationFlags.SetCodeBase);
        }
        else
        {
            try
            {
                regsrv.UnregisterAssembly(assembly);
            }
            catch
            {
                //Exceptions are ignored: even if the unregistering failes
                //it should notprevent the user from uninstalling
            }
        }
    }
}

The basic idea in writing a installer class is to override the methods you need. Override the install method to perform whatever actions you need and override the rollback and uninstall methods to undo these changes. You usually only need to override the commit method if you need to cleanup any temporary files. Don’t forget to start every override by calling the base function first!

The CustomActionData property of each custom action can be used to pass parameters to the installer class. In the installer class these parameter values can be read from the Context.Parameters dictionary. In my installer I used the property to define the assembly I wanted to get registered.

To get my installer class to actually register an assembly I had two options:  to use the Process class to call regasm.exe or to use the RegistrationServices class. My first version used the regasm.exe, but I later changed it to use the service class, both options are fine.

The second step was to add the project output of the installer class library to the setup project and then add the actions I needed.  For each action I selected the project output containing my installer class.

Setup Actions

Finally I set the CustomActionData property of each action to define the path to the target assembly.

CustomActionData Property

Advertisements

16 thoughts on “Setup Project: Custom Actions

  1. One more reason to put the installer class in a separate project is that any unsatisfied dependencies of the assembly during the installation may prevent the installer from performing your custom actions.

    Users might encounter the following error during installation:
    “Error 1001: Unable to get installer types in the XXXX.dll assembly. –> Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information”

    In a case like this, you might want to try fuslogvw.exe to figure out the missing dependencies.

  2. I would love to hear more about registering more than one assembly with the Installer class.

    I am in a situation where I would like my installer to be able to register 2 more assemblies that come from other solutions.

    You hinted that this could be done with the CustomActionData property?

    • Just add a separate library project to your solution with the setup project and add your installer class there. Then add the project output from you class library to to setup project and select that output when adding the custom actions.

      To read parameters from the CustomActionData, just add calls to the Context.Parameters collection to your installer class. Here is a small example on how to read a “Files” parameter:

      public override void Install(IDictionary stateSaver) {

      string filesData = this.Context.Parameters[“Files”];

      string[] files filesData.Split(“;”);
      foreach(string file in files) {
      // Run regasm or whatever
      }

      }

      To define the files, write this in the the CustomActionData property of the Install custom action:

      /Files=”assembly1.dll;assembly2.dll;assembly3.dll”

      Repeat these steps for each custom action (Uninstall, Rollback…). You should be able to use standard folder tags like [ProgramFilesFolder] in the CustomActionData.

      Hope this helps!

  3. Good article! It’s very clear. But it didn’t worked for me 😦 I have a problem, I had a NullReferenceException and debugging I discovered that Installer.Context is null. Do you have any idea of why this can happen?

    • I discovered that Installer.Context is null. Do you have any idea of why this can happen?

      Do you remember to always call the base function first?

      • Yes, you are right. I tried to get the Installer.Context in the constructor! Thanks

  4. Hi,

    It install successfully. Also Uninstall Inventor Add-In successfully from Add Remove programs. But when I restart my Inventor 2009, My Inventor crashed.

    what is problem in Uninstall() method?
    even i added custom Action for uninstall.

    -Komal

  5. I used
    RegistrationServices regsrv = new RegistrationServices();
    regsrv.RegisterAssembly(this.GetType().Assembly,
    AssemblyRegistrationFlags.SetCodeBase);
    regsrv.UnregisterAssembly(this.GetType().Assembly));

    In a custom action and this work ok for my BHO

    Bye

  6. Excellent – I have been tearing my hair out working out a way to get my automation add-in registered correctly and this has helped no end!

    Thanks.

  7. Thank you very much for this article, it greatly helped me. But, unfortunately, it does not work on the x64 systems. Don’t you have some considerations why?

    • The x64 systems have different system folders (_64) and registry keys compared to the old 32bit. Have you tried making a 64 bit build?

      For what I’ve read, .NET 4 will bring some improvement to the x64 system support.

      • Thank you for the answer!

        If you’re talking about ‘TargetPlatform’ property of the Setup project then my answer is ‘Yes’: I’ve tried both ‘x86’ and ‘x64’ values. The only difference I see is that in the first case installer proposes ‘Program Files (x86)’ as the default installation path.

        Is there a way to debug the ‘RegasmInstaller’ class? I’ve tried to attach debugger to the ‘setup.exe’ and ‘msiexec.exe’ processes, but unsuccessfully: they don’t stop on breakpoints.

      • I have not found any easy way for testing custom actions of an setup. I usually try to write some unit tests for my installer classes.

        I found the following tip that might work (have not tested it myself):

        You can start the debugger manually once your .NET Framework method has halted. To halt the method at run-time, you need to add a temporary MessageBox.Show() before the point in your code that you want to debug.

        Make sure the dll you are debugging has been compiled in debug mode. When the Windows install/uninstall runs and displays the MessageBox, do the following:

        1) Start Visual Studio
        2) Open the Debug menu
        3) Click on Attach to Process
        4) Locate msiexec.exe with type .NET and select it
        5) Click on Attach
        6) Ensure Common Language Runtime is ticked then click on OK
        7) Check that that msiexec.exe appears in the list of Debugged Processes
        8 ) Click Close
        9) Set a breakpoint in your code
        10) Return to the MessageBox and click OK to start the debugging. It should stop at your breakpoint ready to step through.

        Try also using the Debugger.Break() function.

  8. Tom,

    Don’t I need to add the main Com.Interop project as Custom Action to the Custom Actions setup dialog as well?

    Thank you,
    Paul

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s