Thursday, September 12, 2013

Regasm COM Interop Assembly With .Net Installer Project

COM Interop allows COM developers to access managed objects as easily as they access other COM objects. COM Interop provides a specialized utility (RegAsm.exe) that exports the managed types into a type library and registers the managed component as a traditional COM component.

To manually register a COM Interop assembly using RegAsm, we typically would do the following (assuming MyCom.COM.dll is an assembly that implements COM Interop for the managed MyCom.dll assembly):
  • Copy MyCom.COM.dll and MyCom.dll (and any other local assemblies) to a folder.
  • Run in VS command prompt: regasm [yourpath]\MyCom.COM.dll /tlb /codebase

However, it's also possible to include the Regasm process into a .Net Installer. Obviously this is the most ideal solution for easy distribution.

To implement this, I've largely benefited from Leon Mayne's blog Regasm /codebase for an installer project.

Below is a direct extract from the blog article about things to take note:

  • Making sure you do not use project references when adding your files to the installer. Use direct file references instead
  • Do the same for referenced files that are included automatically, as they do not get dropped in the obj folder and cause problems. Just exclude them from the project and then add them manually to the installer output
  • Make sure you have a type library for your dll and that this is included in the installer and set to register for COM
  • Make sure your dll is set to register for COM (duh)

Note: The first and second points may differ for some situations, as from my experience, I could use project references without any problem.

And another direct extract from the blog article that clearly stated the steps to perform:

  1. In your main project (the one containing the class you want to register), right click the project file and select Add / New Item and select Installer Class. Call it something like clsRegisterDll.cs
  2. In the designer that appears, click 'Click here to switch to code view' or right click the clsRegisterDll.cs file in solution explorer and select View Code
  3. Replace the code in the window with the code listed below, and make sure you change 'YourNamespace'. This has to match with the namespace in the clsRegisterDll.Designer.cs file.
  4. Compile your project
  5. In your installer, make sure you have added your dll to the Application Folder, and then right-click the installer project and select View / Custom Actions
  6. Right-click Install, and then click Add Custom Action
  7. Double click on Application Folder, and then on your dll
  8. Do the same for the Commit action
  9. Build and test your installer

Ok, now for the clsRegisterDll.cs class. In my implementation, I've renamed this class to RegasmTlbInstaller.cs, made some code changes, and also added overrides for Rollback and Uninstall.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Collections;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

namespace YourNamespace
{
    /// <summary>
    /// An installer class to run regasm /tlb /codebase on the COM DLL (register and un-register).
    /// </summary>
    [RunInstaller(true)]
    public partial class RegasmTlbInstaller : Installer
    {
        public RegasmTlbInstaller()
        {
            InitializeComponent();
        }

        [SecurityPermission(SecurityAction.Demand)]
        public override void Install(IDictionary savedState)
        {
            base.Install(savedState);
        }

        /// <summary>
        /// Runs regasm /tlb /codebase to create and register the TLB file.
        /// </summary>
        [SecurityPermission(SecurityAction.Demand)]
        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            // Get the location of regasm.
            string regasmPath       = RuntimeEnvironment.GetRuntimeDirectory() + @"regasm.exe";

            // Get the location of our DLL.
            string componentPath    = typeof(RegasmTlbInstaller).Assembly.Location;

            // Execute regasm.
            Process p               = new Process();
            p.StartInfo.FileName    = regasmPath;
            p.StartInfo.Arguments   = "\"" + componentPath + "\" /tlb /codebase";
            p.StartInfo.Verb        = "runas";          // To run as administrator.
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.Start();
            p.WaitForExit();
        }

        [SecurityPermission(SecurityAction.Demand)]
        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
        }

        /// <summary>
        /// Runs regasm /u /tlb /codebase to un-register and delete the TLB file.
        /// </summary>
        [SecurityPermission(SecurityAction.Demand)]
        public override void Uninstall(IDictionary savedState)
        {
            // Get the location of regasm.
            string regasmPath       = RuntimeEnvironment.GetRuntimeDirectory() + @"regasm.exe";

            // Get the location of our DLL.
            string componentPath    = typeof(RegasmTlbInstaller).Assembly.Location;

            // Execute regasm.
            Process p               = new Process();

            p.StartInfo.FileName    = regasmPath;
            p.StartInfo.Arguments   = "/u \"" + componentPath + "\" /tlb /codebase";
            p.StartInfo.Verb        = "runas";          // To run as administrator.
            p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            p.Start();
            p.WaitForExit();

            // Delete the TLB file.
            File.Delete(Path.Combine(Path.GetDirectoryName(componentPath), Path.GetFileNameWithoutExtension(componentPath) + ".tlb"));

            base.Uninstall(savedState);
        }
    }
}

Since this class also implements Rollback and Uninstall overloads, we need to add Custom Action for Rollback and Uninstall in the installer project as well (as in steps 6 and 7 mentioned above).

Additionally, the installer project properties should typically set as follows:
  • Project properties:
    • DetectNewerInstallation: True
    • InstallAllUsers: True
    • RemovePreviousVersions: True
  • Primary Output properties:
    • Register: vsdrpCOM
    • SharedLegacy: True
    • (User's Desktop and User's Programs Menu leave empty)

Finally, after building the installer project, please make sure the TLB file generated (e.g. MyCom.COM.tlb) is excluded from the "Detected Dependencies" folder.


Additional Resources:
C# COM Interop And Events


If you find this post helpful, would you buy me a coffee?


1 comment: