As developers, we tend to ignore installation for as long as possible, hoping there will be a quick ‘wizard’ we can run that will abstract the problem away. Utilities such as InstallAware, InstallShield and so on tap into that inherent reluctance and are somewhat successful at it, although I have found that they actually just substitute one set of problems for another.
The truth is that installation is non-trivial and should be an integral part of product design and development, and that developers need to understand the installation process better.
Image may be NSFW.
Clik here to view.Many people think of installation as just copying a few files, but that is a gross oversimplification. According to The Definitive Guide to Windows Installer, these items also need to be considered:
- Determining where the files go – this is a non-trivial problem in itself, with various different versions and editions of Windows, plus shared files, 32-bit vs. 64-bit, and so on
- Verification of prerequisites and redistribution of required supporting files
- preventing downgrades or installation of lower versioned files than are already on the system
- Installing Windows Services
- In-use files, which necessitates shutting down some applications and possibly deferring some actions until after a reboot
- Security, elevated permissions and target user
- Product maintenance; planning for updates
- Packaging the product into “features” with optional installation for some features
- Uninstallation
- Development installs vs. production installs
- Transactional behaviour: dealing with failed installations (backup and roll-back)
Transactional Behaviour: Avoid Custom Code
The last item in that list, transactional behaviour, is a key feature of Windows Installer. Many developers fail to understand this key benefit and do not consider what will happen when their script-based installer fails half way through, leaving a system in an inconsistent state that cannot be easily recovered. Transactional behaviour means that either an installation must succeed completely, or the system will be left as if the installation had never been started.
Getting deterministic transactional behaviour requires that an installation process be declarative and avoid using custom code as far as possible. Installations that have a large amount of custom code are risk-prone. This implies that the use of custom actions needs to be minimized. Custom actions are pieces of user-supplied code or script that are executed during the installation process. Such code weakens the transactional nature of an installation process. As an example of this, consider COM registration. Historically COM servers would be registered by calling their DllRegisterServer function; modern .NET assemblies can be registered by calling RegAsm.exe or RegistrationServices.RegisterAssembly(). All of these options require the assembly to be installed and in some cases loaded and initialized. The assembly itself, or a dependent assembly or DLL may not have been installed, perhaps because the file was in-use and needs to be replaced during a reboot, so these processes may cause the installer to fail, at which point any registrations that have already executed need to be rolled back, and the uninstall process may also fail because of bugs or other issues. It’s a spiral to oblivion. Windows installer deals with this issue by storing the COM registration data as registry entries within the MSI file, so critically it doesn't need to run any custom code to add or remove these entries from the registry. Furthermore, this can be done with full transactional integrity.
Developers’ instinct is to avoid repeated code. Declaring COM registration data seems to violate the DRY Principle, when the registration data can be derived from assembly metadata. This is not the recommended technique, though. COM registration data is best harvested once (using RegAsm.exe /regfile) or hand crafted and added to the installer database as static registry declarations. I was dragged kicking and screaming to this conclusion, but having used this method for a while, I now realize that COM registration data is actually very stable and doesn’t really require any maintenance once added to the setup project.
Shared Components and Reference Counting: Avoid Gacutil.exe
Microsoft provides Gacutil.exe for development use but the guidelines clearly state that Gacutil should never be used on an end user system. This may seem like an arbitrary restriction, but guidelines always exist for a reason. In this case, it is because Gacutil.exe has no knowledge of Windows Installer reference counting. Shared components are therefore likely to be uninstalled prematurely, leaving a broken system. Shared assemblies in the GAC should always be installed by Windows Installer, never by Gacutil. I often hear developers complaining that Windows installation is arcane and unpredictable, while they blithely ignore Microsoft’s guidelines about not using GacUtil, amongst other things. When guidelines are ignored, you’ve built in a latent bug.
Image may be NSFW.
Clik here to view.
Wix: Windows Installer XML
My preferred installer technology is Wix (a contraction of Windows Installer XML). Wix began life as a pet VBScript project of Rob Mensching, a Microsoft employee and in 2004 became the first product ever released by Microsoft as open-source, the source code being hosted on Sourceforge. Wix takes XML files as input, those XML files statically declare the contents, layout and sequence of actions to be performed by the installation package. The Wix compiler transforms the XML to a Windows Installer (.msi) file. An add-on for Visual Studio (called Votive) provides XML schema, IntelliSense and integration with the MSBuild system used by Visual Studio. The Wix project therefore becomes just another project to be edited, committed to version control and so on, just like C# and VB projects. Votive supports project references that simplify some tasks, although Wix is perfectly usable without Votive, I find it a big productivity booster.
So why do I like Wix? Partly, its because of the Visual Studio integration that lets me treat my installer project the same as my source code, commit to version control and build as part of my solution. Partly its because Wix is fairly low level and has a close correspondence to the generated MSI database. The analogy would be coding web sites directly in HTML vs. using a tool like FrontPage. High level tools are OK up to a point, for simple tasks, but as soon as the going gets tricky, what you need is low level control. Wix exposes every feature of Windows Installer and lets me create exactly the behaviour I want. It guides me into doing things declaratively (i.e. not relying on any code to run during setup). Using Wix has also helped me to study and understand how Windows Installer works, as a result I am better equipped to author installation packages. Give Wix a go on your next project.