For those of you who have every worked with any installer technologies, you probably know how cumbersome it can be to do anything non-conventional. A lot of the higher ticket installer packages like InstallShield or WISE offer a robust set of options, but still lack the granularity that some installations may require, aside from costing money. There are a number of decent free options available such as Nullsoft Scriptable Install System (NCSIS), Inno Setup, and Windows Installer XML (WiX). A lot of simple installations can rely on these free technologies, or even the built in Windows Installer support provided with Visual Studio. With the F5 Networks Management Pack there are a lot of post installation steps. This is where WiX came in to save the day. Without having to learn a new scripting language, or abide by the constraints of the Visual Studio MSI builder, WiX gives us the flexibility to perform all needed post installation steps, while staying in the comfortable setting of XML. WiX is also built on top of standard MSI technology, which is well documented and supported by the community, and that makes understanding exactly how everything works a lot easier.
I’m not going to go into the basics of WiX because there is already plenty of good documentation on how to get an installer off the ground. What really interests me ( and unfortunately has very little documentation) is the proper way to set up managed custom actions, and more so, how to cover all of your all installation scenarios and schedule custom actions in sequence with proper rollback. José Almeida has written a very useful blog, which will actually cover a lot of the same material I am going to cover, so my recommendation is to read through that first, as it is very useful and will cover a large percentage of custom action scenarios you may wish to implement. Now actually getting a managed Custom Action working in WiX is not too hard. In fact, it only requires a few lines of XML. That being said, there is a right way and a wrong way, so while it’s easy, it can also be very complex. If you plan to have a a large scale installer (20+ components), I’d recommend fragmenting your script at this point into several WiX files so that you can more easily manage your installer down the road. One such fragment should be your custom actions and as well as the InstallExecuteSequence.
Creating the Custom Actions
In Jose’s blog post, he uses the installUtilLib as the binary in which to run as the Custom Action. The reality is that you can use just about any binary. You may want to embed the binary or perhaps use an executable that is included with the installation package itself. For the sake of this post, I’m going to ignore a few unwritten rules and go ahead and show you how to perform a custom action using a freshly installed binary. If you do choose to use an executable, the best way to do this is to implement it as a Quiet Execution Custom Action. If you are going to use a Quiet CA, you MUST add the following line to your wxs file:
<Binary Id=”wixca” SourceFile=”wixca.dll” />
This line will add the WiX Custom Action binary library so that you can use it as an entry point for your Custom Actions because we want to run our binary quietly, otherwise we will get a cmd window popping up in the middle of install. So for the sake of this example, we are going to say that there is a binary file being dropped by the installer called ConfigurationManager.exe. By itself, this executable does not do anything, but with the proper command line arguments, it will actually run the custom actions. So for one complete custom action we will have something that looks like this:
<CustomAction Id="CAWriteConfigurationFile.Install" Return="check" Execute="deferred" BinaryKey="wixca" DllEntry="CAQuietExec" />
<CustomAction Id="CAWriteConfigurationFile.Install.Command" Property="CAWriteConfigurationFile.Install" Return="check" Value=’”[INSTALLDIR]ConfigurationManager.exe” Action=”WriteConfigFile” Type=”Install”’ />
<CustomAction Id="CAWriteConfigurationFile.Install.Rollback" Return="check" Execute="Rollback" BinaryKey="wixca" DllEntry="CAQuietExec" />
<CustomAction Id="CAWriteConfigurationFile.Install.Rollback.Command" Property="CAWriteConfigurationFile.Install" Return="check" Value=’”[INSTALLDIR]ConfigurationManager.exe” Action=”WriteConfigFile” Type=”Install.Rollback”’ />
<CustomAction Id="CAWriteConfigurationFile.Uninstall" Return="check" Execute="deferred" BinaryKey="wixca" DllEntry="CAQuietExec" />
<CustomAction Id="CAWriteConfigurationFile.Uninstall.Command" Property="CAWriteConfigurationFile.Uninstall" Return="check" Value=’”[INSTALLDIR]ConfigurationManager.exe” Action=”WriteConfigFile” Type=”Uninstall”’ />
<CustomAction Id="CAWriteConfigurationFile.Uninstall.Rollback" Return="check" Execute="Rollback" BinaryKey="wixca" DllEntry="CAQuietExec" />
<CustomAction Id="CAWriteConfigurationFile.Uninstall.Rollback.Command" Property="CAWriteConfigurationFile.Uninstall.Rollback" Return="check" Value=’”[INSTALLDIR]ConfigurationManager.exe” Action=”WriteConfigFile” Type=”Uninstall.Rollback”’ />
These are the very basic actions you need to implement in order to make this all work properly. Each pair above represents one piece of the whole custom action, but I will briefly dissect one pair to help give you a better understanding of what is going on. In the first line we have the Id of the Custom Action, which you should name something that makes reading it easier. Next we have Return, which is set to check so that if the action fails, we know to rollback. Then we have Execute, which is set to deferred, which I will explain in a moment. The last 2 pieces are the BinaryKey and DllEntry, which are explained on plenty of pages, but basically it’s telling the WiX compiler to use the WiX Custom Action library and call the function CAQuietExec, which will execute the next line’s Value property, which was also purposefully named with a .Command at the end. So in the second line, we have the actual command that CAQuietExec will run. The Property field MUST be named the same as the Id field of the previous line. This is important, as it tells the .Command which Custom Action to ‘link’ to. The last piece of the second line is the Value property, which contains the actual command to run. In this case, we are running ConfigurationManager.exe and passing it options for Action and Type, which tell it what to do. It’s useful to handle the executable this way, as it gives us a framework in which to properly implement the Custom Action in all scenarios. The last note to make is that the Execute property on the Rollback actions are set to Rollback. This ensures that these commands ONLY run when Rollback has been initiated.
Sequencing the Custom Actions
Moving forward from here, the most important thing to remember is that the actual Custom Actions are not wrapped in the same type of transaction as the rest of the installer, which means it is imperative that you take care of any rollback on your own. If you do not do this, you could leave the system in a potentially bad state. That being said, other scenarios you might want to consider are Upgrade and Reinstall/Repair. If your Install action is the same for upgrade and repair, you just need to adjust the conditional in the next section; however, if they are different, you will have to create a set for each one like the ones listed above. Once you have laid out the Custom Actions you wish to run, the next piece of the puzzle is to lay out the InstallExecuteSequence. I personally like to keep things in the proper sequence, as it is easier to read and maintain later, so we will actually start with the Uninstall Sequence. For the actions listed above, it will look something like this:
<!-- Uninstall Action Sequence –>
<Custom Action="CAWriteConfigurationFile.Uninstall.Rollback.Command" After=’InstallInitialize’>Installed and REMOVE="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Uninstall.Rollback" After='CAWriteConfigurationFile.Uninstall.Rollback.Command'>Installed and REMOVE="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Uninstall.Command" After='CAWriteConfigurationFile.Uninstall.Rollback'>Installed and REMOVE="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Uninstall" After=’CAWriteConfigurationFile.Uninstall.Command’>Installed and REMOVE="ALL"</Custom>
In the section above, you will notice that the Rollback is scheduled before the actual command. This is very important, as it ensures that the Rollback action gets scheduled before we run the actual command. Otherwise, if the command fails, the Rollback action was never scheduled and any changes made by the command will not be undone. This can leave the system in an unstable state, so it is important you make sure to put these in the correct order. You will also notice in the first line that we are scheduling the Uninstall Sequence to basically start after InstallInitialize. We can actually schedule these actions to run after ProcessComponents, but it must be scheduled before UnpublishComponents if you plan to use an executable dropped by the installer, as UnpublishComponents will make the binary unavailable to use for the Custom Actions. So now that we have the Uninstall Sequence set up, we will look at the Install Sequence:
<!-- Install Action Sequence –>
<Custom Action="CAWriteConfigurationFile.Install.Rollback.Command" After=’PublishProduct’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install.Rollback" After='CAWriteConfigurationFile.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install.Command" After='CAWriteConfigurationFile.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install" After=’CAWriteConfigurationFile.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom>
Just as in the Uninstall Sequence, we schedule the Rollback action directly before the command itself to ensure proper rollback. You will also notice that the first part of the Install Sequence happens after PublishProduct, which ensures that the binary we wish to use is available for the Custom Action.
Multiple Managed Custom Actions
The information before this point can actually be found in pieces around the internet, but what I’m about to show you is not. Assume for a moment that we had not one, but 3 or 4 Custom Actions we wish to run using our ConfigurationManager.exe tool. Not only that, but they require us to run them in a particular order. Now we have an interesting case in which to look at. Creating the Custom Actions themselves is simple enough. Following the pattern above will get you on the right path in a few minutes, but to ensuring proper scheduling can be tricky. Let’s say we have 4 actions called CAUpdateVirtualMachineDatabase, CARegisterNonGacComponents, CARegisterNewVirtualizationSnapIn, and CAImportCustomDataCenterManagementPack. Including the CAWriteConfigurationFile action, we have have five, which must run in this order: CARegisterNonGacComponents, CAWriteConfigurationFile, CAUpdateVirtualMachineDatabase, CARegisterNewVirtualizationSnapin, and finally CAImportCustomDataCenterManagementPack, which relies on each of the previous 4 actions in some way. The Install Action Sequence would look something like this:
<!-- Install Action Sequence –>
<Custom Action="CARegisterNonGacComponents.Install.Rollback.Command" After=’PublishProduct’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNonGacComponents.Install.Rollback" After=’CARegisterNonGacComponents.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNonGacComponents.Install.Command" After=’CARegisterNonGacComponents.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNonGacComponents.Install" After=’CARegisterNonGacComponents.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install.Rollback.Command" After=’CARegisterNonGacComponents.Install’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install.Rollback" After=’CAWriteConfigurationFile.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install.Command" After=’CAWriteConfigurationFile.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAWriteConfigurationFile.Install" After=’CAWriteConfigurationFile.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom> <Custom Action="CAUpdateVirtualMachineDatabase.Install.Rollback.Command" After=’CAWriteConfigurationFile.Install’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install.Rollback" After=’CAUpdateVirtualMachineDatabase.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install.Command" After=’CAUpdateVirtualMachineDatabase.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install" After=’CAUpdateVirtualMachineDatabase.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom> <Custom Action="CARegisterNewVirtualizationSnapin.Install.Rollback.Command" After=’CAUpdateVirtualMachineDatabase.Install’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNewVirtualizationSnapin.Install.Rollback" After=’CARegisterNewVirtualizationSnapin.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNewVirtualizationSnapin.Install.Command" After=’CARegisterNewVirtualizationSnapin.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CARegisterNewVirtualizationSnapin.Install" After=’CARegisterNewVirtualizationSnapin.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom> <Custom Action="CAImportCustomDataCenterManagementPack.Install.Rollback.Command" After=’CARegisterNewVirtualizationSnapin.Install’>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAImportCustomDataCenterManagementPack.Install.Rollback" After=’CAImportCustomDataCenterManagementPack.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAImportCustomDataCenterManagementPack.Install.Command" After=’CAImportCustomDataCenterManagementPack.Install.Rollback'>NOT Installed or REINSTALL="ALL"</Custom>
<Custom Action="CAImportCustomDataCenterManagementPack.Install" After=’CAImportCustomDataCenterManagementPack.Install.Command’>NOT Installed or REINSTALL="ALL"</Custom>
So after looking at the XML above, the problem might seem a little easier to handle. There is one other small issue we need to address: What happens when one of the above acts differently upon repair or upgrade. So let us look at the upgrade scenario, specifically the Database related custom action. If a database is updated, typically we do not want to destroy the data, so we will want to ‘upgrade’ the database with the new fields or tables, etc. The Custom Action blob itself will look exactly like one in the previous section. The above blob will actually look different. We’ll call the upgrade CA CAUpgradeVMDatabase. Here’s how I would fit it in the existing Install Action Sequence:
<Custom Action="CAUpgradeVMDatabase.Upgrade.Rollback.Command" After=’CAWriteConfigurationFile.Install’>Installed and REINSTALL="ALL" and UPGRADEPATH</Custom>
<Custom Action="CAUpgradeVMDatabase.Upgrade.Rollback" After=’CAUpgradeVMDatabase.Upgrade.Rollback.Command'>Installed and REINSTALL="ALL" and UPGRADEPATH</Custom>
<Custom Action="CAUpgradeVMDatabase.Upgrade.Command" After=’CAUpgradeVMDatabase.Upgrade.Rollback'>Installed and REINSTALL="ALL" and UPGRADEPATH</Custom>
<Custom Action="CAUpgradeVMDatabase.Upgrade" After=’CAUpgradeVMDatabase.Upgrade.Command’>Installed and REINSTALL="ALL" and UPGRADEPATH</Custom> <Custom Action="CAUpdateVirtualMachineDatabase.Install.Rollback.Command" After=’CAUpgradeVMDatabase.Upgrade’>NOT Installed or REINSTALL="ALL" and NOT UPGRADEPATH</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install.Rollback" After=’CAUpdateVirtualMachineDatabase.Install.Rollback.Command'>NOT Installed or REINSTALL="ALL" and NOT UPGRADEPATH</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install.Command" After=’CAUpdateVirtualMachineDatabase.Install.Rollback'>NOT Installed or REINSTALL="ALL" and NOT UPGRADEPATH</Custom>
<Custom Action="CAUpdateVirtualMachineDatabase.Install" After=’CAUpdateVirtualMachineDatabase.Install.Command’>NOT Installed or REINSTALL="ALL" and NOT UPGRADEPATH</Custom> Dissecting what we have above, you’ll notice that the big difference between the two is the conditions in which they run. The Upgrade CA requires the product to be installed and the REINSTALL and UPGRADEPATH flags to be set. We do this so that we can schedule this single CA in the middle of the installation sequence. Not only does this make it easier to read, but if we only have the one CA that is affected by a major upgrade it doesn’t make much sense to write 4 more Custom Actions for the rest of the install Custom Actions when we can ensure it gets run in the right sequence with the rest of them. On a side note, the UPGRADEPATH property is actually set by a piece of WiX that looks something like this:
<Upgrade Id='XXXXXXXX-XXX-XXXX-XXXX-XXXXXXXXXXXX'><UpgradeVersion OnlyDetect='no' Property='UPGRADEPATH' Minimum="5.6.1" IncludeMinimum='yes' Maximum='9.5.0' IncludeMaximum='no' /></Upgrade>
This will tell the Custom Action CAUpgradeVMDatabase to run when we are installing on top of a minimum version of 5.6.1. With that being said, you should have all the information you now need to build sequenced managed Custom Actions using WiX. If there is anything I missed, feel free to leave comments or criticisms. Also keep in mind that there are a handful of individuals out there who will recommend against doing what I just showed you, but if you follow the few guidelines I set up, and don’t forget your rollback/uninstall/upgrade/repair actions, you should be able to safely run your managed Custom Actions.