When I first started thinking about improving the Windows deployment process in the enterprise, my assumption was that the “right” solution had to be based on Microsoft Installer (MSI). After all, MSI is the Microsoft-supported standard for installing applications, and we should strive to follow the standards and conventions established for our platform, right? I certainly expect shrink-wrap Windows software to be distributed via an MSI. Why should I expect anything less of my own team’s software?
There are many tools out there for creating MSIs. Third party tools like InstallShield have had a long run. The default option has always been the Visual Studio “setup project”, which “just worked” in most situations after pointing it at an executable project. However, it has drawbacks, including lack of flexibility and the fact that it can only be compiled by Visual Studio, not MSBuild. Its biggest drawback is that Microsoft completely removed support for the setup project with Visual Studio 2012, in favor of better support for WiX (Windows Installer XML). WiX both benefits and suffers from the fact that it isn’t quite as “magical” as the old setup project. It certainly offers more flexibility, but it no longer “just works”. It forces you to understand how MSI actually works, and when you start understanding how MSI actually works, you start realizing just how inappropriate it is for handling agile enterprise deployments.
Without getting too far into the weeds, the fundamental flaw with MSI is that it is based upon a complete catalog of every file that is involved in the deployment. You must tell the MSI, explicitly, about every single dll and content file that makes up the application, and exactly how each file should be mapped to the target file system during deployment. It makes some sense that Microsoft does it this way. MSI provides a standard mechanism for installing, uninstalling, and patching applications. To support this, Windows needs to track individual files, not directories – the uninstall process can’t simply blow away a directory, it must only remove the files in a directory that were placed there by the installation.
However, the extra complexity of specifying every file explicitly adds a lot of friction to the process of creating an installable application. In order to make your application runnable from Visual Studio, you already need to get your references and configurations set up so that Visual Studio creates the deployment directory as a by-product of the build (typically the “bin” directory of the “start-up project”). Therefore, your solution and project configurations already contain the recipe for creating your application’s deployable artifact. Specifying this information again in your MSI authoring tool is unnecessary repetition and a possible source of errors. The main goal of your build and deploy process should be to make the developer’s environment as similar to the production environment as possible. If the developer is running off the deployable artifact created by VS, and the user is running from one created by the MSI, you can easily run into differences between the two. A newly required DLL or resource could be missing from the MSI, or worse, an old version could be present in the MSI that could cause incorrect behavior in production. These issues are hard to catch for developers, because they are not developing against the same deployable artifact as the production users. Often, a developer makes a change in VS without realizing (or possibly just forgetting) that a corresponding change must be made to the MSI.
Why not just eliminate this problem altogether by cutting out the middleman? Instead of packaging your application into an MSI, simply publish the output directory of your Visual Studio project. XCOPY that directory to the target machine’s program files directory, and you are done.
Are there drawbacks to this approach? Sure. Life is full of tradeoffs. Let’s consider the potential drawbacks:
- You lose the hooks to do “other stuff” that an installer provides. In a lot of ways, this is actually a good thing, because you really shouldn’t have to do any “other stuff” (beyond the XCOPY) to get your application to work. There should not be a need to hack up the registry, for example. Sometimes, “other stuff” really is necessary, such as installing an application as a windows service or setting it up as web application. To handle these items, try making your applications “self-installing” by building the setup logic into the application itself. The setup logic can be invoked with a special command line flag. This setup logic can be easily shared across all your applications, providing a consistent and simple deploy/install mechanism for operations.
- Applications deployed via a simple XCOPY won’t show up in the “Add/Remove Programs” dialog (appwiz.cpl) and can’t be uninstalled from there. I consider this to be the biggest drawback of XCOPY deployment. However, in practice, I’ve found that I don’t particularly miss seeing my applications in appwiz, provided that they are all installed installed according to the standard convention (%PROGRAMFILES%\COMPANY_NAME\APP_NAME), which makes them easy to find by simply browsing the file system. Similarly, uninstalling should be as simple as deleting the directory containing the application. If you took the above advice, and created shared logic to handle setup tasks within your application, this can be extended to handle uninstall logic (again invoked by a command line argument). Keeping this consistent across all your applications will make uninstallation just as simple (if not more so) than it would be under appwiz.
- XCOPY deployment relies on the ability to completely remove the application’s installation directory as part of the uninstall. This would cause data or configuration files that reside in the application’s installation directory to be removed during an upgrade. To alleviate this issue, adopt the best practice of not writing data or configuration files to the installation directory. Only modify the installation directory and its contents during an install or uninstall operation — never during a normal “run” of the software. User data and/or configuration files should be written to the %APPDATA% directory, instead. The files will then be preserved across an XCOPY-based upgrade.
- MSI prevents multiple versions of the same application from being installed on one machine at the same time, and you lose this “protection” with XCOPY deployment. I view this as a benefit, because the ability to have multiple versions of an application installed on a single machine can be useful for development, QA, and quick rollbacks in production.
While I would not advocate XCOPY deployment for shrink wrap software, I think it is the right answer in the enterprise. I was skeptical due to the apparent “un-professional” nature of this technique, but I have truly learned to love it. XCOPY reduces friction and risk while encouraging best practices. Overall, it has been a win-win for me.