I recently answered a question on installing COM+ applications in an MSI on one of the newsgroups. I promised that I would go into a bit more depth on the subject here, specifically on the different methods of installation I have tried and why they didn't work (and of course, what did work).
I had an application that consisted of about 200 COM+ components. These components were to be installed into three different directories, essentially separated by their tier (DB, Business, Common). The naming convention was related to the purpose of the DLL. As an example the Business component was "CurrentCustomerListBus.dll" and the DB component was "CurrentCustomerListDB.dll." The build output mimicked the actual desired installation directory structure. Several of these DLL's required configuration by means of adjusting the activation string and timeouts. These settings were different for each installation.
The first approach tried is the Microsoft suggested approach of exporting the COM+ application as an MSI. This pretty much populates an MSI with the files and TypeLibraries, plus adds a blob of undocumented stuff to the ExpType column of the Complus table of the MSI. When this is done, the output was less than desirable:
- MSI Validation fails due to short file name (SFN) clashes. Not really a big deal, but worth mentioning. This is due to point 2 below.
- The destination directory is a single directory. When exporting the application, there were no SFN clashes since they were in separate directories. Note the similarity of the first several filenames above. When the package is deployed they are going to the same directory thanks to the export process, so these clashes now exist. Although the single directory was not according to specs, it was a livable limitation.
- The installation specific activation strings could not be configured in the MSI. A Custom Action would still need to be written to programmatically change them after installation in the host MSI. Again, this is a problem we could live with.
- Deployment via a nested installation, meaning this exported COM+ MSI would need to be installed as part of another MSI. Nested applications are extremely difficult (if not impossible) to patch (exact words from the above link are "Patching and upgrading may not work with nested installations"). Additionally, to properly assure the application (which in my case was a Web application) would not be accessed until the application was fully installed, the nested approach would not be ideal. This one was a deal-breaker, but as it turned out, not the only one.
- Type Library clashes. Although this is not a problem with the export, it does indicate a problem with the construction (programming) of the COM+ application. While the application still functioned OK with these issues, redeployment through the exported MSI was just not possible. Plus, this was not discovered until much later - so I did not know the exported MSI really didn't work.
Approach #2 built upon the first approach. We were using Wise for Windows Installer 3.x which did not have COM+ deployment, so this limited other options. I took the exported MSI and turned it into a merge module using the Windows Installer API's and some tricky scripting. This approach solved problems 1 and 4 above. That is when we found out about problem #5 above, which completely killed this option.
Approach #3 was upgrading to Wise for Windows Installer 4.x Pro (Pro edition allowed for COM+ deployment plus build scripting). This approach failed due to all but reason #4 above, which was the only thing the Wise method solved. To blatantly rip-off Wise's new saying that they stole from my former coworkers (yes, you know who you are) "...but it worked on my machine." Yeah, right!
Approach #4 was the final, shipping method that I found to be 100% reliable, extensible, and portable. We started using Wise and moved to InstallShield with very little work (and none related to COM+) because of this method. For the tool migration benefits alone, this is why I prefer not to use vendor or tool-specific MSI extensions (InstallScript especially). But that is a subject for another future blog entry...
One last bit about approach #4 before I give away my secrets. I implemented it not exactly in the manner I am suggesting here. The main reason I didn't is to hit the testing deadlines and be sure what I released would work in the field. My approach is not perfect, and is difficult to move to another install. In other words, it is not a well-written series of CustomActions and tables that are portable enough to move to another installation without some modification. After this release, time became an issue so I couldn't reimplement it in a more extensible and portable manner. Please don't make the same mistake with your implementation.
To use approach #4, you should be familiar with interacting with Windows Installer via custom actions (VB Script if you want to use the many examples floating around - I prefer JScript). Secondly, you should not be afraid of programmatic access to COM+.
- Create a feature and mark as hidden from the UI. Assure that this feature is always installed via using a Level or a custom action that sets it to be installed based on whatever conditions you require. Note that this feature can not be advertised.
- Add your COM+ component dlls, and only your COM+ component dlls to this feature. Assure that you do not have them self-register or extract the COM information at install build time.
- Use the COM+ APIs to construct some CustomAction(s):
- At uninstall, call ShutdownApplication() on the COM+ Application and remove the COM+ application before the files are being removed. If your COM+ components are used by a service (homebrew, IIS, etc.), make sure you stop the means of access to the COM+ application. I generally place this action after the ServiceControl action in the standard sequence. This will prevent in-use problems at uninstall time.
- During repair/reinstall/maintenance, you want to restrict access to creating the COM+ application and call ShutdownApplication() on it prior to any file actions.
- During installation (and I do it as part of repair), delete and recreate the COM+ application and install each component. Essentially, you need to create a COMAdminCatalog, and on it call GetCollection("Applications"). You are returned a COMAdminCatalogCollection object consisting of Applications. Populate it by calling Populate() on it. Iterate the collection, remove/edit/create as necessary a new COMAdminCatalogObject to represent your COM+ application. Set the Name, Description, and ID values appropriately (refer to this) and then call SaveChanges() on the collection. This gets you the configured COM+ Application. To add components to it, call InstallComponent() for each COM+ dll. I make sure to create progress bar information as well so it does not appear to make the installation hang. I would use the information in the FeatureComponents, File, and Component tables to create a query that will ultimately allow you to get the path to each COM+ dll included in the hidden feature to build this list, then pass it as CustomActionData to the deferred action that actually does the work described above. Most of the COM+ stuff can be seen in action at the link I gave earlier. The MSI interaction stuff I expect you to should already know or be able to figure out from the inline links above - if you have specific questions, just ask!
- Optional - configure component properties as required, such as the activation string, timeouts, etc. on a per component basis. I chose to hard-code this instead of creating a new table in the MSI to store these customizations, but in the future I would use a custom MSI table based approach.
That's all there was to it. It actually took less time to complete approach #4 than approach #2. I am not looking at the source code of my implementation to be sure I'm not missing anything, but this should be complete enough for you to use as a starting point at the very least.