Sunday, September 26, 2004

Installing Windows Services (Created with .NET) with WIX

This is a pretty obscure topic and there is not enough literature on this. Installing a .NET Service is a fairly simple task. The Visual Studio.NET interface allows you to add the System.Configuration.Installer class to your assembly that enables managed installations. Developers often test their services by using the installutil.exe command line tool. But this tool is not the most appropriate for packaging as it shows an ugly command window during installation, which no setup developer would desire. Microsoft, includes a DLL named installutillib.dll with a single MSI entry point function named ManagedInstall. This function can be called via a MSI custom action to handle the four overridable functions exposed by the Installer Class. I am an InstallShield X user and installing .NET service is as easy as setting a property for the component from the IDE. I was a little lost when I wanted to achieve the same with WIX. Although, I knew that I could create these custom actions myself, I was hunting for a way by which I could do it in an easier fashion with WIX. After hours of searching the WiX.chm file, I decided to get on with it myself. I still am not sure if its hidden somewhere in the Wix.chm file.

I was a little skeptical about this and hence started of with an empty .NET enabled Windows Service that does nothing. I added the Installer class to the service and built it. I was too lazy to change the name. So my service was just called Service1 as christened by VS.NET 2003. Once we have our service executable ready, we have to create a little configuration file in XML that specifies the supported frameworks. I called it the IUConfig.XML For people wondering what IU stands for, it is short for InstallUtil <smile/>. The file is fairly simple and it goes something like this.

<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v1.1.4322"/>
</startup>
</configuration>


I put my InstallUtilLib.dll, FirstWindowsService.exe (My Windows Service) and IUConfig.xml in a folder called src. You can find the InstallUtilLib.dll in your [WindowsFolder]\Microsoft.NET\Framework\v1.1.4322\ directory. Once you have these three files, its time to start coding the WXS file. Again, just for the sake of simplicity, I am going to install only this service and nothing else. So here is my WXS file. As you can see it is not much. It has only a feature with one component, containing the service executable and the iuconfig.xml file. I have added the InstallUtilLib.dll as a binary.

<?xml version='1.0'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'>
<Product Id='F47A6F48-86C1-47A8-B404-35656C908BEB' Name='DotNetService' Language='1033' Version='1.0.0.0' Manufacturer='Vagmi' UpgradeCode='5BDA92CF-5D2B-4638-8550-4B8BE5BA8F24'>

<Package Id='????????-????-????-????-????????????' Description='Dot Net Service' Comments='Creating a .NET service' Manufacturer='Vagmi' InstallerVersion='200' Compressed='yes'/>
<Media Id='1' Cabinet='dotnet.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='DOTNETSERVICE' Name='DotNet' LongName='DotNetService'>
<Component Id='TheService' Guid='FF15180D-B296-4F30-9385-0F15B8ACC1FF'>
<File Id='WindowsService' Name='Firstw~1.exe' LongName='FirstWindowsService.exe' KeyPath='yes' DiskId='1' src='src\FirstWindowsService.exe' />
<File Id='ConfigFile' Name='IuConfig.xml' LongName='IuConfig.xml' DiskId='1' src='src\IuConfig.xml' CompanionFile='WindowsService'/>
</Component>
</Directory>
</Directory>
</Directory>

<Feature Id='TheOnlyFeature' Description='Feature contains the single component' Level='1'>
<ComponentRef Id='TheService'/>
</Feature>

<!-- Including the InstallUtilLib.dll. This file does all the magic of installing the services. -->
<Binary Id='InstallUtil' src='src\InstallUtilLib.dll' />
</Product>
</Wix>


To perform a managed install of the service, we need four custom actions - two deferred custom actions for installing and uninstalling, one commit custom action and one rollback custom action. As these custom actions execute in the higher security context, we need to pass data to these custom actions using four separate 'Set Property (Type 51)' custom actions. The ManagedInstall function expects the following parameters. The exact functionality of the parameters is still a mystery to me and I have to yet reasearch on it. But for now, we would take this for granted.

/installtype=notransaction /action=(install/uninstall/commit/rollback) /LogFile= "PathTo\Assembly" "PathTo\iuconfig.xml"

So the code for custom actions would look something like this.

<CustomAction Id='InstallServiceSetProp' Property='InstallService' Value='/installtype=notransaction /action=install /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='InstallService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='deferred' />
<CustomAction Id='UnInstallServiceSetProp' Property='UnInstallService' Value='/installtype=notransaction /action=uninstall /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='UnInstallService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='deferred' />
<CustomAction Id='CommitServiceSetProp' Property='CommitService' Value='/installtype=notransaction /action=commit /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='CommitService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='commit' />
<CustomAction Id='RollbackServiceSetProp' Property='RollbackService' Value='/installtype=notransaction /action=rollback /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='RollbackService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='rollback' />


You would not have to sequence these custom actions such that the uninstall custom actions run before the RemoveFiles action, and install, rollback & commit custom actions are scheduled after the InstallFiles action in the same order. So your <InstallExecuteSequence> would look something like this.

<InstallExecuteSequence>
<Custom Action='UnInstallServiceSetProp' After='MsiUnpublishAssemblies'>$TheService=2</Custom>
<Custom Action='UnInstallService' After='UnInstallServiceSetProp'>$TheService=2</Custom>
<Custom Action='InstallServiceSetProp' After='StartServices'>$TheService>2</Custom>
<Custom Action='InstallService' After='InstallServiceSetProp'>$TheService>2</Custom>
<Custom Action='RollbackServiceSetProp' After='InstallService'>$TheService>2</Custom>
<Custom Action='RollbackService' After='RollbackServiceSetProp'>$TheService>2</Custom>
<Custom Action='CommitServiceSetProp' After='RollbackService'>$TheService>2</Custom>
<Custom Action='CommitService' After='CommitServiceSetProp'>$TheService>2</Custom>
</InstallExecuteSequence>


Putting all this together, we would have a file like this.

<?xml version='1.0'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'>
<Product Id='F47A6F48-86C1-47A8-B404-35656C908BEB' Name='DotNetService' Language='1033' Version='1.0.0.0' Manufacturer='Vagmi' UpgradeCode='5BDA92CF-5D2B-4638-8550-4B8BE5BA8F24'>

<Package Id='????????-????-????-????-????????????' Description='Dot Net Service' Comments='Creating a .NET service' Manufacturer='Vagmi' InstallerVersion='200' Compressed='yes'/>
<Media Id='1' Cabinet='dotnet.cab' EmbedCab='yes' />

<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='DOTNETSERVICE' Name='DotNet' LongName='DotNetService'>
<Component Id='TheService' Guid='FF15180D-B296-4F30-9385-0F15B8ACC1FF'>
<File Id='WindowsService' Name='Firstw~1.exe' LongName='FirstWindowsService.exe' KeyPath='yes' DiskId='1' src='src\FirstWindowsService.exe' />
<File Id='ConfigFile' Name='IuConfig.xml' LongName='IuConfig.xml' DiskId='1' src='src\IuConfig.xml' CompanionFile='WindowsService'/>
</Component>
</Directory>
</Directory>
</Directory>

<Feature Id='TheOnlyFeature' Description='Feature contains the single component' Level='1'>
<ComponentRef Id='TheService'/>
</Feature>

<!-- Including the InstallUtilLib.dll. This file does all the magic of installing the services. -->
<Binary Id='InstallUtil' src='src\InstallUtilLib.dll' />

<!--Write custom actions to install, uninstall, commit and rollback the changes-->

<CustomAction Id='InstallServiceSetProp' Property='InstallService' Value='/installtype=notransaction /action=install /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='InstallService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='deferred' />

<CustomAction Id='UnInstallServiceSetProp' Property='UnInstallService' Value='/installtype=notransaction /action=uninstall /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='UnInstallService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='deferred' />

<CustomAction Id='CommitServiceSetProp' Property='CommitService' Value='/installtype=notransaction /action=commit /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='CommitService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='commit' />

<CustomAction Id='RollbackServiceSetProp' Property='RollbackService' Value='/installtype=notransaction /action=rollback /LogFile= "[#WindowsService]" "[DOTNETSERVICE]iuconfig.xml"'/>
<CustomAction Id='RollbackService' BinaryKey='InstallUtil' DllEntry='ManagedInstall' Execute='rollback' />

<!-- Now to sequence these CAs in the execute sequence -->

<InstallExecuteSequence>

<Custom Action='UnInstallServiceSetProp' After='MsiUnpublishAssemblies'>$TheService=2</Custom>
<Custom Action='UnInstallService' After='UnInstallServiceSetProp'>$TheService=2</Custom>

<Custom Action='InstallServiceSetProp' After='StartServices'>$TheService>2</Custom>
<Custom Action='InstallService' After='InstallServiceSetProp'>$TheService>2</Custom>

<Custom Action='RollbackServiceSetProp' After='InstallService'>$TheService>2</Custom>
<Custom Action='RollbackService' After='RollbackServiceSetProp'>$TheService>2</Custom>

<Custom Action='CommitServiceSetProp' After='RollbackService'>$TheService>2</Custom>
<Custom Action='CommitService' After='CommitServiceSetProp'>$TheService>2</Custom>


</InstallExecuteSequence>

<!--Now we're done-->

</Product>
</Wix>


Despite all my skepticism, the above code ran perfectly fine. Now, I have to work on the real services. Hope you find this useful.

Saturday, September 25, 2004

To Nest or To Chain or To Merge

This blog entry is for relatively novice setup developers and not for the die-hard Windows Installer gurus. Normally different groups in an organization develop components of software, which can be used standalone or as a part of a suite of products. This is a very normal practice. So they go ahead and create a software package with a complete setup.exe and a separate MSI for it. But when they plan to integrate it with the suite. That is when the setup developer's headache starts. The setup developer might be hard pressed for time and would choose nested install of some sort to cut down time. This article discusses a few techniques to handle it in a better fashion.

Firstly, nested installs are evil. Let me tell you why. They can never be patched and cannot be cleanly removed from the target system. That alone should drive you away from using nested installs. You perform a nested installation. And Boom! The next thing you know is that you have lost control over the life cycle of your application, your house has been burgled and your wife has run away with your neighbor. Well, it was a slight exaggeration but you get the picture.

A smarter way to handle this, as many argue would be to call the child install by launching the MSI package's setup.exe or launch msiexec and pass appropriate command line parameters to install the package. This is equally bad. Such installations do not have their rollback logic integrated with the parent installer. Thus if the nested setup fails, you do not have any way to roll back the parent setup reliably or the vice-versa. Furthermore, Windows Installer's architecture allows only one installation at a point to make changes to the system. Having two installations modify system resources might not be the smartest thing to do.

The only way out of this mess is to use something called as the Merge Module. Merge Module is an atomic unit consisting of components, custom actions and various resources like files and registry entries. These merge modules can be integrated into the main installer at design time by the merge tool. You can either use tools like InstallShield to include merge modules in the application or use WIX's tag to include the merge module during build. The advantage of using the merge module is that the components in the merge module remain immutable. Thus it ensures that setup developers follow some of the many important component design rules. A resource going to a location will always have the same component GUID regardless of the product its being installed with. Windows Installer will then be able to do clean refcounting of the components and will give you a solid setup.

Beyond all this, if the setup were a third party setup, you would have to use the MSI package unless the vendor agrees that you can repackage his setup. Buts lets just assume that he does not. Even in that case, it is recommended that you keep the logic of the third-party setup miles away from your application. You can handle the installation of the third party application from the bootstrapper. There are a couple of applications that do that. InstallShield X Premier and Professional editions have a neat feature called as Setup Prerequisites, which let you call other MSI or non-MSI based installations from the bootstrapper. If you would like to stay open source, then you can use DevAge’s DotNetInstaller to achieve the same. Both of them are very easy to use. InstallShield’s solution comes with a price tag, sporting a snazzy killer interface and excellent documentation. While the DotNetInstaller is not a setup creation program but is just a bootstrapper and it is free.

All About WIX

Of late, I was very busy amidst many meetings. My friends would know the reasons for that. As soon as I got away from my day job (actually afternoon, I work from 1PM to 10PM), I got really involved with WIX. I have found this tool very versatile, stable and extremely lightweight. I believe that this is used internally at Microsoft by MS Office team. The MS Office team actually created Windows Installer (Codenamed Darwin) and hence it is only fair to assume that they use every feature of Windows Installer. This should give you some idea of the versatility of this tool. For folks who don’t know what WIX is, Rob has included a link to the Introduction to Windows Installer XML video in his blog. He gives a broad overview of the tool and demonstrates the usability of this tool in real-time. Although this tool is versatile, learning it would be difficult if you do not know Windows Installer. Unfortunately, there are not enough good resources to learn Windows Installer. I did my learning with Bob Baker's books published by InstallShield Press. I supplemented my knowledge of Windows Installer with the Mike Gunderloy's book VB/VBA's developers Guide for Windows Installer. I have to yet read Phil's The Definitive Guide to Windows Installer. I am not sure if I could get hold of it in the near future, as it is not available in the local bookshops. But the bible or should I say the Gita of Windows Installer (MSI.chm) is my lifesaver any day.

It was pretty fun working with WIX and I believe that all setup developers must seriously consider this tool as an alternative to other commercial tools. There are a number of high-level utilities which help you automate most of the rudimentary tasks with the XML file but are still a fertile field for more development. There is currently no CodeDOM available for WIX so generating the WIX source is not as easy. But I believe that there are a couple of initiatives for the same. So for people who have a phobia for editing text files, WIX is NOT for you, atleast until the higher-level apps come in. The documentation is still skimpy but I believe that we should be able to see that changing shortly. WIX, however has a very active users community to extend help when you get stuck. As the tool is open-source, you can go ahead and fix a bug yourself depending on the criticality. There is a lot of scope for development for the tools like Tallow and sca.dll. Tallow.exe is a all purpose utility which does some rudimentary code gen, extracts self-reg entries, extracts registration information for assembles, process .rc files to create WIX UI fragments and the like. Sca.dll provides several custom actions like creating WebSites/Virtual directories, Users, run SQL Scripts, etc. Since the .wxs files are plain XML files, they are much easier to check in and check out than binary formats used by commercial tools like InstallShield. BTW, InstallShield does support XML format to store its project file but is nowhere close to the level of distributed application development functionality supported by WIX. Watch the video for more information about this. So if you are all set to download WIX and get running with it, jump here and click on the download link. You might also want to read an article about WIX on O'Reilly.

I just finished stealing UI from one of my InstallShield Basic MSI projects by "dark"ing (decompiling) the built MSI and cleaning up the WXS file and editing it down to size. I had the custom actions and InstallShield specific properties cleaned out and removed the branding. Thanks to the folks at Wix-Users mailing list, I have successfully separated the UI from my main product's installation and have documented the instructions to include the exact and to be used. I am still a developer so don’t expect me to write many lines of verbose comments. It is just a commented out block of code that you can cut and paste in the main WXS file. If you would like to have a copy of this .wxs file, please email me at vagmi.mudumbai@gmail.com.