After my recent cribbing about my wish-list for WIX, it is only fair that I talk about the advantages of WIX over other tools like InstallShield. I am primarily comparing it with InstallShield, as it is the only tool that I am aware of to an extent and is also the current market leader (in my opinion) in the installation tools market place.
1) XML Format: WIX source files are in a programmer friendly format. You are allowed to comment the code at any place, that makes the installation project source files as readable as the application's source files. Tools like InstallShield X allow you to save your file in the XML format but editing the XML and adding comments to it is not really easy. Furthermore, the changes in the source code across versions can be easily tracked by diffing from the source control, which would be very inconvenient with binary files.
2) Distributed Development: Each setup developer or even the application developer can edit a small fragment of the project to produce multiple WXS files which can be compiled independently and linked in the final process to produce MSI/MSM files. To my knowledge, this feature is currently available only with WIX. Of course, you could use other tools to create Merge Modules, but they would only add to the confusion with the modularization of the columns than to aid the process of distributed development. You can use WIX to maintain the readability of code. You could include comments within the code to actually point to the WXS file from which the element is referenced.
3) Clean Installation: You do not have to go through a whole big deal of the installation process just to get the package authoring system available on the build and the design environments. The WIX package decompressed is only 3.56 MB including the documentation and comes completely with custom actions to work with IIS, SQL Server, hypercharged LockPermissions functionality and much more. There are no DLLs to be registered, no registry entries to be created. Just unzip it into a machine with .NET Framework 1.1 installed and WIX would run without any problems. You would need Platform SDK on the machine to work with merge modules. Or at least the mergemod.dll registered on the build machine.
4) Custom Action Library: WIX comes with a custom action library to install/configure IIS websites and virtual directories. It is also present in InstallShield. WIX has support for SQL Server 2000 which is also present in InstallShield X. So I guess that levels the playing field. What WIX currently does not have is the support for installing COM+ Applications. But people following the Wix-Users list would know that these are already been worked upon and there have been a couple of users who have implemented the code and are currently contributing it back to the community.
Friday, October 29, 2004
Thursday, October 28, 2004
What does WIX need?
After a long time, I have got some time to get to my blog. After a long day of presentations, I guess it is only fair enough that I relax by concentrating sometime on my long forgotten blog. We in our organization primarily use InstallShield for most of our setup development needs. I and one of my colleagues were discussing the topic of distributed development. InstallShield is good in a lot of things but distributed development is not one of them.
As a common sense approach, distributed development is done using merge modules. Although these may be discrete chunk of non reusable installation units, merge modules are the only way to enable Distributed development with InstallShield. I then truly realized the power of WIX which inherently supports distributed development. I then mentally started preparing a list of features that WIX ought to have to replace tools like InstallShield.
1) GUI - I guess WIXStudio need to be pushed a little harder. I personally do not believe that a development tool such as WIX really needs a GUI. But if the world were to run only on my beliefs, it would be a far better place than it is right now . WIX language is pretty straight forward and the tool nudges the developers on to the right track. Where the tool fails to point to the right direction, wix-users list does the job.
2) Bells and Whistles - Tallow is a cool tool but is unfortunately very buggy. As Rob suggested it is a fertile field for development and is constantly improving. I have seen some pretty encouraging messages in the Users list. I still have not become a part of the dev-list as yet, as I really cannot contribute at this point in time. I would however like to do so as soon as I have access to my computer at home.
3) Dialogs - It would be great to have a dialog editor. If not a full blown IDE for WIX, the least setup developers expect would be to have a dialog editor. Rob would argue that setup need not have a user interface, but in reality, dialogs are required for a enterprise setup of a decent quality. I have posted a message on WIX-Users-list for a list of free/open source MSI editors with visual Dialog editors. We could then dark the setup and create a WXS fragment.
4) Documentation – Last but not the least, WIX really needs a lot more documentation. I have been really busy off-late and have not been able to contribute as much to WIX as before. I hope the WIX Wiki would grow to fill this need.
IMHO, WIX is still the best tool to create installations for large projects.
As a common sense approach, distributed development is done using merge modules. Although these may be discrete chunk of non reusable installation units, merge modules are the only way to enable Distributed development with InstallShield. I then truly realized the power of WIX which inherently supports distributed development. I then mentally started preparing a list of features that WIX ought to have to replace tools like InstallShield.
1) GUI - I guess WIXStudio need to be pushed a little harder. I personally do not believe that a development tool such as WIX really needs a GUI. But if the world were to run only on my beliefs, it would be a far better place than it is right now
2) Bells and Whistles - Tallow is a cool tool but is unfortunately very buggy. As Rob suggested it is a fertile field for development and is constantly improving. I have seen some pretty encouraging messages in the Users list. I still have not become a part of the dev-list as yet, as I really cannot contribute at this point in time. I would however like to do so as soon as I have access to my computer at home.
3) Dialogs - It would be great to have a dialog editor. If not a full blown IDE for WIX, the least setup developers expect would be to have a dialog editor. Rob would argue that setup need not have a user interface, but in reality, dialogs are required for a enterprise setup of a decent quality. I have posted a message on WIX-Users-list for a list of free/open source MSI editors with visual Dialog editors. We could then dark the setup and create a WXS fragment.
4) Documentation – Last but not the least, WIX really needs a lot more documentation. I have been really busy off-late and have not been able to contribute as much to WIX as before. I hope the WIX Wiki would grow to fill this need.
IMHO, WIX is still the best tool to create installations for large projects.
Friday, October 15, 2004
Friday, October 08, 2004
Riko's Blog Entry on "Resources and Data"
Imagine a blog entry where Riko speaks on the Resources, User Data and Application Data. As noted in his blog, he is yet to make his point. It is an excellent blog entry. Also read Rob's entry on what setup is. There are a couple of links to the same entry in Riko's article too.
Thursday, October 07, 2004
Handling Application Data
Recently, I had initiated a thread in the WIX users list regarding the inclusion of application data in the MSI package. My thanks to Riko, who shared his views on that. It has always been tricky and will continue to be a tricky situation to deal with application data. Most applications include their application runtime and configuration data as registry entries, INI or XML files. An unassuming developer might package these registry entries with the Main application's component, as they are dependent on the main application. This would work fine during the first install, but would cause significant issues during an upgrade. Geoff has blogged one such scenario. This blog tries to capture some of my thoughts regarding such Application Data.
For starters, lets look at what happens when you include the application data in the MSI package. The user, to configure the software the way they want, usually modifies these entries. Many times, it is the application itself that changes or writes data to the registry, INI or XML files to persist the configuration state of the application. Assuming we have included such files and registry entries grouped in the same component as the main executable. We can argue that it would be a good design as these entries are logically bound to the application and have no sense existing as a standalone unit. The application would roll out pretty smooth and will have no problems until you hit a point when you have to upgrade. Let us consider a minor upgrade, no major changes, a few DLLs modified and couple of executables added. Let main executable file have its version bumped. As the executable is the key file of the component, it qualifies for an upgrade. Let us also consider changes made to the XML files and a few Registry entries to accommodate the change. During an upgrade, all hell would break loose. The registry would be overwritten and the registry might be left with conflicting entries and some XML files would updated, the others may be left untouched as it would have a greater modified date. If you are really unlucky, the testing team might not quite get the tests done right. The Result: Total Chaos.
So how should these things be handled? The answer to this question is not straightforward and seems to be pretty tricky. The most ideal scenario would be to let the application handle its own configuration. The application should be able to create these registry entries and XML files during the first run. The setup developer then has to merely author the RemoveFile and RemoveReigstry tables to get the files out during uninstallation. As far as XML handling is concerned, it is best left to the application to handle it. MSI does not support XML handling and we need robust custom actions to do it for us. Handling XML data/SQL Data during upgrades can only be done using custom code. It would be desirable if this code is present on the application's end and not on the installer's end. If you do have to include the application data in the MSI package, the following guidelines may be useful.
1) Identify and Isolate the Application Data:
Clearly identify the resources like registry entries, INI Files and other configuration files that are to be used by the application. Use the INIFile table to author INI Files. Avoid including the INI file itself in the File table. Once identified, include the application data in a separate component. Use multiple components, if necessary.
2) Minor Upgrade and Uninstall Scenario:
If the application creates extra files or registry entries, ensure that the RemoveFile and the RemoveRegistry tables are populated. The problem is that these tables are referred to even during installation of the component. So you would have to set the Never Overwrite bit of the component to make sure that this component is never marked for an upgrade. Code the upgrade logic for the application data in to the application itself. In the unlikely event of it not being feasible, write custom actions to achieve the same. Avoid writing custom actions for tasks that can be handled by the application.
3) Major Upgrade Scenario:
Certain applications like InstallShield X, schedule the RemoveExistingProducts action right before the InstallInitialize action. This is not the most efficient placement for this action. The most efficient placement for this action would be after the InstallFinalize action. I will talk about this in detail another time, if you haven't figured it out as yet. If the authoring tool places the RemoveExistingProducts action before InstallInitialize, manually schedule it after InstallFinalize. There are certain custom actions in InstallShield X (I guess Component Services) that are not very happy with this placement, although I am not very sure about this. In these cases, you would have to use related locator (AppSearch, RegLocator, IniLocator, etc.,) and signature tables to get save the information into properties and reuse them in the install. This is a very elaborate and time-consuming exercise. The shorter and a devilish way to get around this is to write custom actions to read the values in the registry and store them in a text file, back up your application data files and restore them back with another set of custom actions. As unscientific as it might sound, a lot of people have used it to get out of this mess.
Rob is currently working on a similar blog entry. I can't wait to read on what he has to say about this.
For starters, lets look at what happens when you include the application data in the MSI package. The user, to configure the software the way they want, usually modifies these entries. Many times, it is the application itself that changes or writes data to the registry, INI or XML files to persist the configuration state of the application. Assuming we have included such files and registry entries grouped in the same component as the main executable. We can argue that it would be a good design as these entries are logically bound to the application and have no sense existing as a standalone unit. The application would roll out pretty smooth and will have no problems until you hit a point when you have to upgrade. Let us consider a minor upgrade, no major changes, a few DLLs modified and couple of executables added. Let main executable file have its version bumped. As the executable is the key file of the component, it qualifies for an upgrade. Let us also consider changes made to the XML files and a few Registry entries to accommodate the change. During an upgrade, all hell would break loose. The registry would be overwritten and the registry might be left with conflicting entries and some XML files would updated, the others may be left untouched as it would have a greater modified date. If you are really unlucky, the testing team might not quite get the tests done right. The Result: Total Chaos.
So how should these things be handled? The answer to this question is not straightforward and seems to be pretty tricky. The most ideal scenario would be to let the application handle its own configuration. The application should be able to create these registry entries and XML files during the first run. The setup developer then has to merely author the RemoveFile and RemoveReigstry tables to get the files out during uninstallation. As far as XML handling is concerned, it is best left to the application to handle it. MSI does not support XML handling and we need robust custom actions to do it for us. Handling XML data/SQL Data during upgrades can only be done using custom code. It would be desirable if this code is present on the application's end and not on the installer's end. If you do have to include the application data in the MSI package, the following guidelines may be useful.
1) Identify and Isolate the Application Data:
Clearly identify the resources like registry entries, INI Files and other configuration files that are to be used by the application. Use the INIFile table to author INI Files. Avoid including the INI file itself in the File table. Once identified, include the application data in a separate component. Use multiple components, if necessary.
2) Minor Upgrade and Uninstall Scenario:
If the application creates extra files or registry entries, ensure that the RemoveFile and the RemoveRegistry tables are populated. The problem is that these tables are referred to even during installation of the component. So you would have to set the Never Overwrite bit of the component to make sure that this component is never marked for an upgrade. Code the upgrade logic for the application data in to the application itself. In the unlikely event of it not being feasible, write custom actions to achieve the same. Avoid writing custom actions for tasks that can be handled by the application.
3) Major Upgrade Scenario:
Certain applications like InstallShield X, schedule the RemoveExistingProducts action right before the InstallInitialize action. This is not the most efficient placement for this action. The most efficient placement for this action would be after the InstallFinalize action. I will talk about this in detail another time, if you haven't figured it out as yet. If the authoring tool places the RemoveExistingProducts action before InstallInitialize, manually schedule it after InstallFinalize. There are certain custom actions in InstallShield X (I guess Component Services) that are not very happy with this placement, although I am not very sure about this. In these cases, you would have to use related locator (AppSearch, RegLocator, IniLocator, etc.,) and signature tables to get save the information into properties and reuse them in the install. This is a very elaborate and time-consuming exercise. The shorter and a devilish way to get around this is to write custom actions to read the values in the registry and store them in a text file, back up your application data files and restore them back with another set of custom actions. As unscientific as it might sound, a lot of people have used it to get out of this mess.
Rob is currently working on a similar blog entry. I can't wait to read on what he has to say about this.
Monday, October 04, 2004
Designing Upgrades - Part I
The previous week had been really busy. With five new recruits on board, I was asked to give them a quick introduction to InstallShield X and Windows Installer. It was broken up into 5 sessions, 3 hours each. And of course, I had my routine to take care of. So, after all this hard work, I decided to pamper myself with a movie the weekend. Before that I have decided to write something about designing upgrades with Windows Installer and WIX. I have split this article into two for easy consumption.
Designing upgrades are fairly basic and I have seen a lot of people in the forum having a little trouble getting started with the upgrades. But once they understand the concept of upgrades from the Windows Installer's perspective, it becomes a cakewalk. So, for starters, let me just talk about the different types of upgrades that you can perform with Windows Installer and implement the same using WIX. This article is designed only to be a quick start guide and is no means a complete guide for an upgrade. If you want to know more about upgrades, read the Upgrades and Patching section of the Windows Installer SDK documentation.
Windows Installer keeps track of products and packages using GUIDs. There are three important GUIDs that you need to know to understand upgrades.
And then there is the ProductVersion property, which specifies the version of the product. Windows Installer recognizes three types of upgrades. In all these upgrades, the package code will always change.
The small update and the minor upgrade can be installed over the existing installation. Usually, they only make changes to the parts of applications that have been changed. There are usually no major design changes to the product tree, excepting a few additions and modifications.
The major upgrade however, installs a completely new product and uninstalls the existing version of the product(s). Minor upgrade and small update can be targetted only at a particular product but Major upgrades can target more than one product. Major upgrade usually would have major changes made to the product tree. There might be situations that you might have to change the product code. In those cases, you would have no other choice but to perform a major upgrade.
So, as long as you do not have to change the product code, you can perform a small update or a minor upgrade. Minor upgrade would allow you to track the upgrade applied by looking at the ProductVersion. You can apply a minor upgrade or small update using the following command line.
msiexec.exe /i REINSTALL=ALL REINSTALLMODE=vomus
The value of the REINSTALL property is a list of features delimited by commas that are to be reinstalled. The features listed must be present in the Feature column of the Feature table. The REINSTALLMODE property is a string containing letters specifying the type of reinstall to perform. Options are case-insensitive and order-independent. This property should normally always be used in conjunction with the REINSTALL property. However, this property can also be used during installation, not just reinstall.
To design a major upgrade, you would have to author the Upgrade table. The upgrade table allows you to filter products and subsequently features based on the UpgradeCode, ProductVersion and Language. The Remove column of the upgrade table can be used to specify the list of features to be removed. Windows Installer will remove all the features, if the column is null. The ActionProperty column can be used to specify the name of a property. For the major upgrade to work, you need to have FindRelatedProductsAction and RemoveExistingProducts action in the InstallExecuteSequence table. The FindRelatedProducts action reads the settings in the upgrade table and stores the matching ProductCode(s) in the property specified by the ActionProperty column of the Upgrade Table. The RemoveExistingProducts action removes the product during installation. The most efficient placement for the RemoveExistingProducts action is after the InstallFinalize action. However, tools like InstallShield X, sequence this action in between InstallValidate and InstallInitialize actions.
To be continued...
Designing upgrades are fairly basic and I have seen a lot of people in the forum having a little trouble getting started with the upgrades. But once they understand the concept of upgrades from the Windows Installer's perspective, it becomes a cakewalk. So, for starters, let me just talk about the different types of upgrades that you can perform with Windows Installer and implement the same using WIX. This article is designed only to be a quick start guide and is no means a complete guide for an upgrade. If you want to know more about upgrades, read the Upgrades and Patching section of the Windows Installer SDK documentation.
Windows Installer keeps track of products and packages using GUIDs. There are three important GUIDs that you need to know to understand upgrades.
- ProductCode - This uniquely identifies a product. This value is written to the Property table under the name ProductCode.
- UpgradeCode - This GUID is used to logically bind related products. This value is written to the Property table under the name UpgradeCode.
- Package Code - This uniquely identifies a package. Almost any change in the MSI package, mandates a new Package GUID. This value is written to the summary information sream under the name of 'Revision Number'.
And then there is the ProductVersion property, which specifies the version of the product. Windows Installer recognizes three types of upgrades. In all these upgrades, the package code will always change.
- When the upgrade just changes the application files but does not change the Product Code or the Product version, it is termed as a small update.
- When the upgrade changes only the ProductVersion but does not change the ProductCode, it is termed as a minor upgrade.
- When both the ProductCode and ProductVersion changes, it is termed as a Major Upgrade.
The small update and the minor upgrade can be installed over the existing installation. Usually, they only make changes to the parts of applications that have been changed. There are usually no major design changes to the product tree, excepting a few additions and modifications.
The major upgrade however, installs a completely new product and uninstalls the existing version of the product(s). Minor upgrade and small update can be targetted only at a particular product but Major upgrades can target more than one product. Major upgrade usually would have major changes made to the product tree. There might be situations that you might have to change the product code. In those cases, you would have no other choice but to perform a major upgrade.
So, as long as you do not have to change the product code, you can perform a small update or a minor upgrade. Minor upgrade would allow you to track the upgrade applied by looking at the ProductVersion. You can apply a minor upgrade or small update using the following command line.
msiexec.exe /i
The value of the REINSTALL property is a list of features delimited by commas that are to be reinstalled. The features listed must be present in the Feature column of the Feature table. The REINSTALLMODE property is a string containing letters specifying the type of reinstall to perform. Options are case-insensitive and order-independent. This property should normally always be used in conjunction with the REINSTALL property. However, this property can also be used during installation, not just reinstall.
To design a major upgrade, you would have to author the Upgrade table. The upgrade table allows you to filter products and subsequently features based on the UpgradeCode, ProductVersion and Language. The Remove column of the upgrade table can be used to specify the list of features to be removed. Windows Installer will remove all the features, if the column is null. The ActionProperty column can be used to specify the name of a property. For the major upgrade to work, you need to have FindRelatedProductsAction and RemoveExistingProducts action in the InstallExecuteSequence table. The FindRelatedProducts action reads the settings in the upgrade table and stores the matching ProductCode(s) in the property specified by the ActionProperty column of the Upgrade Table. The RemoveExistingProducts action removes the product during installation. The most efficient placement for the RemoveExistingProducts action is after the InstallFinalize action. However, tools like InstallShield X, sequence this action in between InstallValidate and InstallInitialize actions.
To be continued...
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.
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.
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
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.
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
Friday, August 27, 2004
Self-Registration vs. Registry tables group
COM Servers are every Setup developer's nightmare. One of my friends, a release engineer, expressed her extreme frustration over the technique that they had used to register COM Servers. The setup developer had written 100s of lines of code to launch RegSvr32 using the '/s' switch to register these COM Servers. In theory, the process of registration of a COM Server is perfectly fine when done via the regsvr32 utility. But there are certain disadvantages while doing that from the MSI Engineer's perspective. Firstly, all the dependencies of the server need to be registered in a specific order. And secondly, the user performing the installation should have administrative rights. The latter aspect makes setup developers and administrators loathe the registration process.
Further, system administrators do not know the registry settings and COM interfaces that are exposed by the COM object. COM objects do not support reflection and hence are essentially black boxes. Windows Installer has a 'SelfReg' table which can be used to self-register COM servers. There are several disadvantages while using this table.
Tools like InstallShield X provide alternative methods to perform self-registration. This works around certain limitations like specifying the order of registration and registering EXE files. But largely there are limitations that are not in sync with Windows Installer's philosophy of a setup. Speaking of Setup Philosophy, RobMen's blog on his philosophical musings is really worth a read. I personally consider performing self-registration a fiendish act. Microsoft does not absolutely recommend it. Instead it recommends the use of Registry Table group. These are a set of tables like Class, ProgId, Typelib, MIME, Extension and so forth that can be used to store COM information. Most of this information should be available in the generated IDL file for the COM component. If you have any trouble figuring it out, you can peep into the registry during the registration process using various registry-spying tools and extract the appropriate information. Thankfully, tools like InstallShield X abstract this process. For example, using InstallShield X you can extract COM information from the key file of the component during the build time by setting the "COM Extract at Build" attribute of the component in the InstallShield X IDE. Although this makes life easy for a lot of setup developers, this technique has its share of pains. Although not perfect, this technique would solve most of the issues related with COM Registration.
InstallShield also provides a simple utility call RegSpyUI.exe that helps users look at COM information for a specific DLL or exe. This tool is undocumented, unsupported and hence I do not know all the bad things that it might do. But it is a really cool utility, which helps us work around such nasty limitations. Search the InstallShield X or DevStudio installation directory for this tool. There are a few references to this tool in the InstallShield Communities. So if something bad happens to your computer, don’t blame me. You have been warned.
Some of the motivating factors for authoring these tables as opposed to self-registration are:
With all these facts by your side, I am now sure that you can convince any setup developer to use the Registry Table Group instead of performing self-registration.
Further, system administrators do not know the registry settings and COM interfaces that are exposed by the COM object. COM objects do not support reflection and hence are essentially black boxes. Windows Installer has a 'SelfReg' table which can be used to self-register COM servers. There are several disadvantages while using this table.
- Order of registration cannot be specified. Thus if the DLL has dependencies and the dependencies are not registered already, the self-registration would fail.
- Rollback cannot be done reliably
- Self-Registration requires administrative privileges
- You cannot exploit the install-on-demand features of Windows Installer
- You cannot register EXE (Out of process) servers.
Tools like InstallShield X provide alternative methods to perform self-registration. This works around certain limitations like specifying the order of registration and registering EXE files. But largely there are limitations that are not in sync with Windows Installer's philosophy of a setup. Speaking of Setup Philosophy, RobMen's blog on his philosophical musings is really worth a read. I personally consider performing self-registration a fiendish act. Microsoft does not absolutely recommend it. Instead it recommends the use of Registry Table group. These are a set of tables like Class, ProgId, Typelib, MIME, Extension and so forth that can be used to store COM information. Most of this information should be available in the generated IDL file for the COM component. If you have any trouble figuring it out, you can peep into the registry during the registration process using various registry-spying tools and extract the appropriate information. Thankfully, tools like InstallShield X abstract this process. For example, using InstallShield X you can extract COM information from the key file of the component during the build time by setting the "COM Extract at Build" attribute of the component in the InstallShield X IDE. Although this makes life easy for a lot of setup developers, this technique has its share of pains. Although not perfect, this technique would solve most of the issues related with COM Registration.
InstallShield also provides a simple utility call RegSpyUI.exe that helps users look at COM information for a specific DLL or exe. This tool is undocumented, unsupported and hence I do not know all the bad things that it might do. But it is a really cool utility, which helps us work around such nasty limitations. Search the InstallShield X or DevStudio installation directory for this tool. There are a few references to this tool in the InstallShield Communities. So if something bad happens to your computer, don’t blame me. You have been warned.
Some of the motivating factors for authoring these tables as opposed to self-registration are:
- You can forget about dependencies. Since all the information is already authored in the table, you do not need the COM servers registered in order.
- You can register all kinds of COM Components (DLL, EXE, OCX and the like.)
- You do not need administrative privileges. Since the registration is carried out by the standard actions, you can exploit MSI's support for elevated privileges. This is a boon for people handling locked down environments.
- You can now support Installation-on-Demand for these COM Components. I would cover this in detail some other day.
With all these facts by your side, I am now sure that you can convince any setup developer to use the Registry Table Group instead of performing self-registration.
Saturday, August 21, 2004
Reading Windows Installer Logs
As we all know Windows Installer is a complicated and a elegant technology. Its beastly complexities are beautifully abstracted by the authoring packages like InstallShield X, DevStudio and Developer. But we live in a less than perfect world and Murphy’s Law still holds good for the Windows Installer world too. There might be several reasons why an Installation might fail. Some errors might be meaningful while others might just spit out a cryptic error message with an error number during installation. The only way to make sense of these would be to look at the log files generated during installation. You can generate a log file for the installation using the following command line.
msiexec /i productname.msi /l*v package.log
This command generates a verbose log file logging all errors, warnings and debug messages and produces a huge log file in the text format. The first look at the log file might be slightly intimidating. This article tries to break the ice with the basics of reading the log file to troubleshoot problems with installation. Discussing all the error messages and its aspects is out of scope of this article. If you have specific questions, feel free to drop me a word. (at installneo@yahoo.com)
The Bottom Up Approach:
Whenever I read a log file, I start reading it from the bottom of the file. You might dismiss this technique as one of my eccentricities but I believe that this is the quickest, if not the most efficient, way of reading a log file in case of errors like 1603 or 1721. As a convention, the letter 'C' within brackets denotes the client context and the letter 'S' denotes the server context. So all messages in the log, which start as below, are messages in the client's context.
MSI (c) (A0:4C): Doing action: INSTALL
Action start 0:12:20: INSTALL.
MSI (c) (A0:4C): UI Sequence table 'InstallUISequence' is present and populated.
MSI (c) (A0:4C): Running UISequence
All messages in the log which start as below are messages from the MSI running in the server's context.
MSI (s) (1C:7C): Doing action: INSTALL
Action start 0:12:54: INSTALL.
MSI (s) (1C:7C): Running ExecuteSequence
Let us first analyze a successful log file and get ready to compare the anomalies with a not so perfect log file. I have used a simple Basic MSI project created with InstallShield X that just installs notepad.exe and I have disabled Update Ser. What we should be looking at is actions that do not have a return code of 1. You would find lines similar to the ones below just above the Server context property dump.
MSI (c) (04:B0): Doing action: SetupCompleteSuccess
Action start 18:03:47: SetupCompleteSuccess.
Action 18:03:47: SetupCompleteSuccess. Dialog created
Action ended 18:03:48: SetupCompleteSuccess. Return value 2.
Action ended 18:03:48: INSTALL. Return value 1.
Hmm... What does this mean? The "Action ended 18:03:48: SetupCompleteSuccess. Return value 2." message indicates that user cancelled the action or the operation was aborted due to a user instruction. But god knows that I did not instruct it stop. There must be something else that is wrong. The most obvious place to look for it would be the "Finish" button in the SetupCompleteSuccess dialog. You would find a line like the one below once you click on the Behavior tab of the dialog and select the OK control. If you insist on doing it via Orca, you can look at the ControlEvent table for the OK control in the SetupCompleteSuccess dialog.
EndDialog - Exit - 1
BINGO! We have found the culprit. The argument for the control event must be 'Return' instead of 'Exit'. Refer Windows Installer SDK help library article titled "EndDialog ControlEvent" for more information. Rebuild the project after changing it to 'Return' and you would find that none of the actions have return values other than '1'.
We have thus finally solved a trivial problem with the help of the log file, which would have gone unnoticed otherwise. Ah! Sweet Perfection.
:-)
Deferred Custom Actions:
One of the common places where users get confused is with deferred custom actions. Deferred Execution Custom Actions are not executed the first time the installer processes the custom actions in the Execute Sequence table. They are instead written to the MSI script. All these actions are executed within the InstallFinalize action. In these cases, you would have to look at the return value of the InstallFinalize action. A few lines above the line that indicates the return value of InstallFinalize would hint the action that failed. Let us verify this with a simple VBScript Custom Action that does nothing else but cause the install to fail </wink>. This is the code for the VBScript.
function MyFunction
MyFunction=1603
end function
I then create a Type 1030 custom action (VBScript Stored in Binary Table + Deferred Execution). This custom action is scheduled after InstallFiles action in the Install Execute Sequence. Run and log the installation. The installation proceeds and comes up with a dialog, which states that the Installation operation has failed. Let us look into the log for more information.
The final few lines in the log file complain that installation failed. But it still does have not enough information to identify an error.
MSI (c) (34:30): Product: SimpleInstaller -- Installation operation failed.
MSI (c) (34:30): Grabbed execution mutex.
MSI (c) (34:30): Cleaning up uninstalled install packages, if any exist
MSI (c) (34:30): MainEngineThread is returning 1603
Above the client contexts' property dump, the following lines of code indicate that INSTALL (Top level action) returns a value of 3 and the main thread is returning 1603. The SetupCompleteError is the same as we discussed earlier. This does not give much information either.
MSI (s) (DC:D8): MainEngineThread is returning 1603
MSI (c) (34:30): Back from server. Return value: 1603
MSI (c) (34:30): Decrementing counter to disable shutdown. If counter >= 0, shutdown will be denied. Counter after decrement: -1
Action ended 19:24:28: ExecuteAction. Return value 3.
MSI (c) (34:30): Doing action: SetupCompleteError
Action start 19:24:28: SetupCompleteError.
Action 19:24:29: SetupCompleteError. Dialog created
Action ended 19:24:30: SetupCompleteError. Return value 2.
Action ended 19:24:30: INSTALL. Return value 3.
Assuming that you have opened the log in notepad, search quickly for the word 'InstallFinalize' and specify the direction of search as up. You will now be able to see the following lines.
...
Our custom action is being called
MSI (s) (DC:D8): Executing op: ActionStart(Name=NewCustomAction1,,)
Action 19:24:28: NewCustomAction1.
MSI (s) (DC:D8): Executing op: CustomActionSchedule(Action=NewCustomAction1,ActionType=1030,Source=function MyFunction
MyFunction=1603
end function,Target=MyFunction,)
MSI (s) (DC:D8): Creating MSIHANDLE (3) of type 790536 for thread 2008
MSI (s) (DC:D8): Creating MSIHANDLE (4) of type 0 for thread 2008
MSI (s) (DC:D8): Closing MSIHANDLE (4) of type 0 for thread 2008
MSI (s) (DC:D8): Closing MSIHANDLE (3) of type 790536 for thread 2008
Our custom action fails causing the InstallFinalize action to fail
Action ended 19:24:28: InstallFinalize. Return value 3.
Initiate Rollback
MSI (s) (DC:D8): User policy value 'DisableRollback' is 0
MSI (s) (DC:D8): Machine policy value 'DisableRollback' is 0
MSI (s) (DC:D8): Executing op: Header
...
Rolls back changes
As you would have aptly guessed, the lines in bold are my comments. So you can nail most of troublesome deferred custom action using this technique.
I am really sleepy and have got some real work to do. I am sure I would touch on the log files again covering more aspects. Love it or Hate it, feel free to post your comments.
msiexec /i productname.msi /l*v package.log
This command generates a verbose log file logging all errors, warnings and debug messages and produces a huge log file in the text format. The first look at the log file might be slightly intimidating. This article tries to break the ice with the basics of reading the log file to troubleshoot problems with installation. Discussing all the error messages and its aspects is out of scope of this article. If you have specific questions, feel free to drop me a word. (at installneo@yahoo.com)
The Bottom Up Approach:
Whenever I read a log file, I start reading it from the bottom of the file. You might dismiss this technique as one of my eccentricities but I believe that this is the quickest, if not the most efficient, way of reading a log file in case of errors like 1603 or 1721. As a convention, the letter 'C' within brackets denotes the client context and the letter 'S' denotes the server context. So all messages in the log, which start as below, are messages in the client's context.
MSI (c) (A0:4C): Doing action: INSTALL
Action start 0:12:20: INSTALL.
MSI (c) (A0:4C): UI Sequence table 'InstallUISequence' is present and populated.
MSI (c) (A0:4C): Running UISequence
All messages in the log which start as below are messages from the MSI running in the server's context.
MSI (s) (1C:7C): Doing action: INSTALL
Action start 0:12:54: INSTALL.
MSI (s) (1C:7C): Running ExecuteSequence
Let us first analyze a successful log file and get ready to compare the anomalies with a not so perfect log file. I have used a simple Basic MSI project created with InstallShield X that just installs notepad.exe and I have disabled Update Ser. What we should be looking at is actions that do not have a return code of 1. You would find lines similar to the ones below just above the Server context property dump.
MSI (c) (04:B0): Doing action: SetupCompleteSuccess
Action start 18:03:47: SetupCompleteSuccess.
Action 18:03:47: SetupCompleteSuccess. Dialog created
Action ended 18:03:48: SetupCompleteSuccess. Return value 2.
Action ended 18:03:48: INSTALL. Return value 1.
Hmm... What does this mean? The "Action ended 18:03:48: SetupCompleteSuccess. Return value 2." message indicates that user cancelled the action or the operation was aborted due to a user instruction. But god knows that I did not instruct it stop. There must be something else that is wrong. The most obvious place to look for it would be the "Finish" button in the SetupCompleteSuccess dialog. You would find a line like the one below once you click on the Behavior tab of the dialog and select the OK control. If you insist on doing it via Orca, you can look at the ControlEvent table for the OK control in the SetupCompleteSuccess dialog.
EndDialog - Exit - 1
BINGO! We have found the culprit. The argument for the control event must be 'Return' instead of 'Exit'. Refer Windows Installer SDK help library article titled "EndDialog ControlEvent" for more information. Rebuild the project after changing it to 'Return' and you would find that none of the actions have return values other than '1'.
We have thus finally solved a trivial problem with the help of the log file, which would have gone unnoticed otherwise. Ah! Sweet Perfection.
:-)
Deferred Custom Actions:
One of the common places where users get confused is with deferred custom actions. Deferred Execution Custom Actions are not executed the first time the installer processes the custom actions in the Execute Sequence table. They are instead written to the MSI script. All these actions are executed within the InstallFinalize action. In these cases, you would have to look at the return value of the InstallFinalize action. A few lines above the line that indicates the return value of InstallFinalize would hint the action that failed. Let us verify this with a simple VBScript Custom Action that does nothing else but cause the install to fail </wink>. This is the code for the VBScript.
function MyFunction
MyFunction=1603
end function
I then create a Type 1030 custom action (VBScript Stored in Binary Table + Deferred Execution). This custom action is scheduled after InstallFiles action in the Install Execute Sequence. Run and log the installation. The installation proceeds and comes up with a dialog, which states that the Installation operation has failed. Let us look into the log for more information.
The final few lines in the log file complain that installation failed. But it still does have not enough information to identify an error.
MSI (c) (34:30): Product: SimpleInstaller -- Installation operation failed.
MSI (c) (34:30): Grabbed execution mutex.
MSI (c) (34:30): Cleaning up uninstalled install packages, if any exist
MSI (c) (34:30): MainEngineThread is returning 1603
Above the client contexts' property dump, the following lines of code indicate that INSTALL (Top level action) returns a value of 3 and the main thread is returning 1603. The SetupCompleteError is the same as we discussed earlier. This does not give much information either.
MSI (s) (DC:D8): MainEngineThread is returning 1603
MSI (c) (34:30): Back from server. Return value: 1603
MSI (c) (34:30): Decrementing counter to disable shutdown. If counter >= 0, shutdown will be denied. Counter after decrement: -1
Action ended 19:24:28: ExecuteAction. Return value 3.
MSI (c) (34:30): Doing action: SetupCompleteError
Action start 19:24:28: SetupCompleteError.
Action 19:24:29: SetupCompleteError. Dialog created
Action ended 19:24:30: SetupCompleteError. Return value 2.
Action ended 19:24:30: INSTALL. Return value 3.
Assuming that you have opened the log in notepad, search quickly for the word 'InstallFinalize' and specify the direction of search as up. You will now be able to see the following lines.
...
Our custom action is being called
MSI (s) (DC:D8): Executing op: ActionStart(Name=NewCustomAction1,,)
Action 19:24:28: NewCustomAction1.
MSI (s) (DC:D8): Executing op: CustomActionSchedule(Action=NewCustomAction1,ActionType=1030,Source=function MyFunction
MyFunction=1603
end function,Target=MyFunction,)
MSI (s) (DC:D8): Creating MSIHANDLE (3) of type 790536 for thread 2008
MSI (s) (DC:D8): Creating MSIHANDLE (4) of type 0 for thread 2008
MSI (s) (DC:D8): Closing MSIHANDLE (4) of type 0 for thread 2008
MSI (s) (DC:D8): Closing MSIHANDLE (3) of type 790536 for thread 2008
Our custom action fails causing the InstallFinalize action to fail
Action ended 19:24:28: InstallFinalize. Return value 3.
Initiate Rollback
MSI (s) (DC:D8): User policy value 'DisableRollback' is 0
MSI (s) (DC:D8): Machine policy value 'DisableRollback' is 0
MSI (s) (DC:D8): Executing op: Header
...
Rolls back changes
As you would have aptly guessed, the lines in bold are my comments. So you can nail most of troublesome deferred custom action using this technique.
I am really sleepy and have got some real work to do. I am sure I would touch on the log files again covering more aspects. Love it or Hate it, feel free to post your comments.
Monday, August 16, 2004
This is the beginning of....
I have just started this blog to document some of the interesting and difficult...ahem..challenging scenarios I faced while supporting people who package applications. I always wanted to write technical articles. I guess this would be a start.
Subscribe to:
Posts (Atom)