Registering an Assembly for COM Interop in a MSI file with the Windows Installer XML toolset.


On an internal mailing list, Omar Shahine just posted a link to his blog entry about how to register an assembly for COM Interop via an MSI.  After getting about halfway through the entry I started getting concerned.  Omar was suggesting that your assembly should be registered by calling into a custom System.Configuration.Install.Installer class via a CustomAction.  I firmly believe that CustomActions should be used only as a last resort, but a full body shiver hit me when I read the part where Omar said:


Now you can safely rely on MSI to register your component for COM Interop.

This statement is false.  While Omar does mention that you need to create CustomActions for both install and uninstall, he has missed the ever elusive but extremely important rollback CustomAction.  Rollback CustomActions are important to ensure that if anything goes wrong during the installation (or uninstallation) of your product that each of your custom processing gets appropriately cleaned up.  Rollback CustomActions are vital to ensure you end up with a proper compensating transaction for your install.


Currently, Omar’s suggested solution for registering an assembly for COM Interop has the potential to leave the registration for your assembly on the machine even though an error occurred during installation and MSI removed the assembly itself from the machine.  That scenario is obviously undesirable.  You’ve not only dirtied the user’s machine with invalid configuration, other applications may attempt to activate your assembly via COM and fail in mysterious ways.  An even more insidious scenario is the fact that it is possible for the uninstall to fail and rollback in such a way that the assembly and product remain on the machine but the COM Interop registration has been removed.  I would not want to be the support engineer trying to debug that problem.


Now a lot of people like to argue with me that failures during install don’t happen in “the wrong places” all that often and that failures during uninstall “hardly ever happen”.  Well, I cut my teeth doing deployment in Office and when you have hundreds of thousands of users you find that installation and uninstallation will fail in every which way it can.  But, I don’t usually bring that up first.  Instead, I usually point out that a user canceling your setup is essentially the same as rollback.  Sometimes they try to argue that they will just hide the Cancel button, but then I point how lousy a user experience that is.  Imagine not being able to go, “Oops, I didn’t mean to install/uninstall that!”  When I get someone who doesn’t care about the user experience (I mentally note to send a message to their manager <grin/>) and point out how it is impossible to get the Windows Installer to completely prevent the user from canceling setup in all cases.


Okay, so hopefully you now agree there is a good reason to have a rollback CustomAction.  I bet someone well versed in Visual Studio deployment projects could build on Omar’s blog to add the necessary rollback CustomActions (yes, plural). So let me bring out the real problem (I know, I know, you probably thought I was done).  The installation or uninstallation of the registration is not tied to a Component.


“So what?” you might ask.  Well, let’s imagine a scenario where your assembly is reusable and you want to use it in multiple products.  In other words, the assembly file and registration CustomActions show up in multiple MSI files.  Now let’s say someone thinks you write really cool stuff and installs a couple of your products on their machine.  Interestingly enough, the registration for your assembly’s COM Interop is written twice (once for each install), but that’s no big deal.  Then let’s say the user decides one of your products isn’t as useful as advertised and removes the product.  When the registration CustomAction executes it will remove all the registration for your assembly’s COM Interop.  This wouldn’t be a problem except that you still have a product on the machine that needs the COM Interop.


There are lots of different ways to hit the problems described in the scenario above that you wouldn’t necessarily think of right away (unless you’ve been twisted by years of fighting with lazy developers who think “setup is just xcopy” <grin/>).  Anyway, the key point to remember is that the Windows Installer controls the installation and uninstallation of Resources (like the assembly file and each of its COM Interop registry keys) via Components.  If you want more reading, I have an old blog entry about the topic.


So now you might be thinking, “Thanks, Rob.  You’ve ruined my day.”  Well, there are a few things that we can do.  First, you could track down all of the registry keys that the CustomAction would have written and just author them in the Component.  I think this is the part that Omar said frustrated him the most, so that might not be really enticing even if it a “safer solution”.  Second, you could try to condition the execution of all of your registration CustomActions based on the install state of the Component that contains the assembly being registered.  This option can be very error prone (getting the condition just right is tricky) and difficult to reuse.  Which brings me to my final option, use the Windows Installer XML toolset to automatically populate your MSI (or MSM) with the appropriate COM Interop registration.


Using the WiX toolset to populate your MSI is like getting the first option but without all the work of finding the registry keys.  The authoring to do something like this would be, notice the use of the “AssemblyRegisterComInterop” attribute:

<?xml version=’1.0′?>
<Wix xmlns=’http://schemas.microsoft.com/wix/2003/01/wi’>
<Product Id=’G-U-I-D-Here’ Name=’TestAssemblyProduct’ Language=’1033′ Version=’0.0.0.0′ Manufacturer=’Your name goes here’>
<Package Description=’Test Assembly in a Product’ Comments=’Demonstrate Assembly COM Interop Regsitration’ InstallerVersion=’200′ Compressed=’yes’ />
<Media Id=’1′ Cabinet=’product.cab’ EmbedCab=’yes’ />
<Directory Id=’TARGETDIR’ Name=’SourceDir’>
<Directory Id=’ProgramFilesFolder’ Name=’PFiles’>
<Directory Id=’TestAssemblyProductDirectory’ Name=’testassm’ LongName=’Test Assembly’>
<Component Id=’TestAssemblyProductComponent’ Guid=’G-U-I-D-Here’>
<File Id=’TestAssemblyProductFile’ Name=’assembly.dll’ Assembly=’.net’ AssemblyRegisterComInterop=’yes’ KeyPath=’yes’ DiskId=’1′ />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id=’TestAssemblyProductFeature’ Title=’Test “ssembly Product Feature’ Level=’1′>
<ComponentRef Id=’TestAssemblyProductComponent’ />
</Feature>
</Product>
</Wix>

(note: I did not try compiling/linking this, but it should definitely point you in the right direction).


Since this blog entry turned out pretty long, let me see if I can sum up the points:



  1. CustomActions should be avoided unless there is absolutely no other means to accomplish the task.

  2. CustomActions that modify the user’s machine should always have associated rollback CustomActions.

  3. CustomActions that install/uninstall Resources should always have their execution tied to a Component’s install state.

  4. Being lazy when creating your setup package will create grief for your customers and your product support team.

  5. Setup isn’t just xcopy.

  6. The WiX toolset has a helper attribute on the File element for registering COM Interop for assemblies.

Comments (18)

  1. Omar Shahine says:

    Hey,

    The custom action thing was something I did as a work around when I didn’t know what else to do, but I’m not doing that any more. I’m assing the assembly file and setting that to be registered and not using the Custom Action any longer. Does that make sense? Is that still problematic?

  2. Heh, Omar, I find it amusing, but I’m not sure what "assing the assembly file" means in this context.

    Also, posted this entry more to demonstrate how hard it is to create CustomActions correctly. I always get nervous when people with CustomActions in their setup say, "Now you can safely rely on MSI…" because they’ve just modified the installation path and rarely understand the implications of those modifications.

    Finally, no offense intended (if any felt implied).

  3. Jiho Han says:

    Rob,

    Does using the WiX toolset remedy the uninstall scenario – with two products and one getting uninstalled?

    Thanks

  4. damir says:

    Rob,

    a bit unrelated question: is there a way to find out through WMI which components did Windows Installer install as part of some install?

    For example, if I install Office 2003, I can figure out through WMI that office is installed, but is there a way to know which components — thait is, somebody choose not to install Picture Editor. (Either through wmi or some other method).

    Sorry if this is out of scope, but your mention of you doing office deployments caught my attention. 😉

    Thanks

  5. Omar Shahine says:

    Sorry, let me be more clear.

    The final work around in my post talks about how to get COM Registration to work w/p using a custom action. Instead of adding the project output of your project to the setup, you add the actual dll or assembly. You mark that assembly for vsaCOM and it’s registered w/o the need for a Custom Action. That seems to be the best work around and the one I am recommending (not the custom action method).

    Sorry if I wasn’t clear (and sorry for my horrible spelling!).

  6. Jiho,

    Yes, the uninstall scenario will be handled correctly as long as you do two things:

    1. Respect the Component Rules: http://blogs.msdn.com/robmen/archive/2003/10/18/56497.aspx

    2. Install the file to the same place in each Component. I’ll write more about this point later.

  7. damir,

    I know you can query a fair bit of Windows Installer information via WMI. Unfortunately, I don’t play with WMI so I can’t really provide any specifics. Best luck on you searching… if I ever read up on the topic in the future, I’ll be sure to post something here.

  8. damir says:

    Rob, thanks for replying.

  9. David Levine says:

    There’s another issue related to uninstall (an upgrade, really) that I’ve recently run into that will impact this. It turns out that MSI creates/hosts a single appdomain (the default appdomain) that it uses to load all managed assemblies when it runs custom actions in them.

    This isn’t a problem during an install or uninstall, but during an upgrade (i.e. detecting and automatically removing an old installation and then installing a new application) it first calls the custom action in the assembly being removed, then it deletes the old assemblies, installs the new assemblies and again calls the custom action in it. The problem is that if the custom action is in the same assembly (i.e. the assembly has the same name, but is a different version) it will wind up running the custom action code in the previous assembly that you thought had just been uninstalled.

    The reason is that once an assembly is loaded into an appdomain it never gets unloaded. MSI uses a single appdomain, it never gets unloaded, so even though you think you are running code in the new assembly it is really running the code in the old assembly.

    Yet another reason for avoiding custom actions…or for not using the automatic upgrade option.

  10. mohd fairol says:

    no yet.help setting .

  11. Ewan MacIntyre says:

    Why do MSI files created through Visual Studio

    .NET such that assemblies will be registered

    for COM interop, have the appropriate

    registry keys listed explicitly in the

    installer XML? Surely this info is already

    in the TLB, and since this can in turn be

    extracted from the assembly, there’s no point

    in duplicating it?

  12. leida1983 says:

    Ping Back来自:blog.csdn.net

  13. Phil Wilson says:

    As a rather tardy response to Ewan McIntyre’s comment, when VS does the vsdraCOM thing on an assembly it basically runs regasm with the option to create a .reg file, and then it imports the .reg file. There is no .tlb registration when this happens (this is clear from looking in the resulting MSI file). If you need type library registration, use tlbexp to create one from your assembly and add it to the setup, and use the vsdr(x)COM Register setting. VS does not do this for you! This behavior where VS does not do the same as regasm (because of the missing typelib registration) seems to cause no end of bother.

  14. brian says:

    I noticed that the WixEdit compiler is inidicating an error when I use AssemblyRegisterComInterop="yes" in version 2.0.3220. Has this been removed?

    thanks

    brian

  15. robmen says:

    Brian, yes the AssemblyRegisterComInterop attribute was removed along with all of the other Resource generation code. Generating Resources is a very dangerous thing to do because the Component Rules have to be respected.

    The tallow.exe tool was created to extract the information from your file once and is the substitution for AssemblyRegisterComInterop.

  16. Marco Casalaina says:

    Has this issue been addressed in VS 2005?  I’m still on 2003, but I would be inclined to upgrade if it would fix this  issue.

  17. Nektar says:

    The question that nobody has still asked is why was there such a problem in the first place? Ie. why when building an Outlook add-in through Visual Studio 2003 and when you allow the default behaviour of registering the main project output, com registration may fail? Has Omar found the reasons behind this issue?