Delivery Process of Application to Windows Platform – simple, reliable and repeatable

Table of Contents
- Introduction
- Background
- What kind of software do we deliver?
- Preparing executables
- Preparing DB scripts
- Writing an installer
- Configuring delivery process
- Remarks
- Conclusion
- History
Introduction
The intention of this article is to provide one option to deliver software targeting Windows platform. The install approach in this article is command line based which means it can be used in Continuous Delivery practice. You will learn how to install/uninstall application to different environments and how to manage the configuration during the installation because every environment requires a unique configuration.

Background
The delivery process is not so easy. I guess everyone knows the stage when you finish writing application and you try to deliver it. Depending on the target environment it starts to crash on different issues like non existing E drive, wrong connection string, wrong addresses to APIs and so on. After a while you build up some mechanism that allows you to manually specify the values during the installation. Like installation UI Wizards. Then you end up spending a lot of time to fill up the Wizards again and again, installation after installation.
Now it comes to automate to process. Make it easy, simple, reliable and repeatable. And this process I’m going describe now.
What kind of software do we deliver?
Could be any software targeting Windows Platform. In this article I decided to use a simple console application with a simple database as described in the two following chapters. I assume in most cases it is needed to deploy an application and the database. The practice used in this article works fine no matter how complicated the installer is. It will definitely work fine for example for the installer from this article.
Preparing executables
For demonstration in this article I choose a simple “Hello world” application called AwesomeApp. The application consists of a single file Program.cs which possesses the following lines of code:
using System;
namespace AwesomeApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Awesome Hello World!");
}
}
}
The application has two configuration modes: Debug and Release. For the deployment is used the Release mode of configuration which means the following line of code to compile the solution:
msbuild /t:ReBuild /p:Configuration=Release AwesomeApp.sln
As you can see the first part the executable part of delivery package is straightforward and easy to compile. The second part, however, could be a bit more interesting because it is related to DB.
Preparing DB scripts
Regarding the database deployment I decided to use the Database Management Studio which is included in Visual Studio 2010. So I created a database project with the name Awesome.Database. There is a single table in it called AwesomeTable.

The database project has also just two configuration modes: Debug and Release. For deployment here is also used the Release mode. The command to compile the project and get the script for creating database is follow:
msbuild /t:DBDeploy /p:Configuration=Release Awesome.Database.dbproj
However, creating of the DB script does not relay just on the previous command. We need to configure more than that to achieve demanded result. If you open the .dbproj file in your favorite text editor you may see the PropertyGroup element of configuration for Release mode.
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>.\sql\release\</OutputPath>
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
<TargetConnectionString>
</TargetConnectionString>
<TargetDatabase>
</TargetDatabase>
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
<SuppressWarnings>
</SuppressWarnings>
<DeploymentConfigFile>Properties\Database.sqldeployment</DeploymentConfigFile>
<SqlCommandVariablesFile>Properties\Database.sqlcmdvars</SqlCommandVariablesFile>
<DeployToDatabase>False</DeployToDatabase>
</PropertyGroup>
As you can see on the previous Listing there are several variables you can set up. For example TargetConnectionString and TargetDatabase if you wish to create the script according to the existing database or SqlCommandVariablesFile to set up default values for variables in your SQL scripts. The most important value in this part is DeployToDatabase which means that the deployment script will be created but the deployment itself will not be performed. Usually we have several configuration modes because we deploy a database to several servers. I recommend you to create a special configuration for your deployment package. However, in this article I stick with Release configuration.
Writing an installer
In the two previous chapters we prepared an executable file and a SQL script file. Those two files are the application to deploy. So let’s write a simple installer for that. The installer can look as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<Product Id="{DACD6B11-B50F-43B0-9FCB-46779FDC7AA3}"
Name="Awesome.App"
Language="1033"
Version="1.0.0.0"
Manufacturer="My"
UpgradeCode="{99B33695-E79A-4957-8C70-DAB6B2CD1C7A}">
<Package InstallerVersion="200" Compressed="yes" />
<Property Id="ALLUSERS" Value="1" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<!-- Check for .NET 4.0 -->
<PropertyRef Id="NETFRAMEWORK40FULL" />
<Condition Message="This application requires .NET Framework 4.0 or greater.">
<![CDATA[Installed OR (NETFRAMEWORK40FULL AND NETFRAMEWORK40FULL = "#1")]]>
</Condition>
<!-- The primary directory structure -->
<Directory Id="TARGETDIR" Name="SourceDir">
<!-- The install folder. -->
<Directory Id="ProgramFilesFolder">
<!-- The specific folder for this product -->
<Directory Id="AWESOME_DIR" Name="Awesome.App">
</Directory>
</Directory>
</Directory>
<!-- Defining features. -->
<Feature Id="Complete" Title="Awesome Application"
Description="Paper Advice Engine Application."
Display="expand"
Level="1"
ConfigurableDirectory="AWESOME_DIR" >
<!-- Specify the component which belongs to this feature. -->
<ComponentRef Id="AWESOME_CMP" />
</Feature>
<DirectoryRef Id="AWESOME_DIR">
<Component Id="AWESOME_CMP" Guid="{96E75B26-530E-4F4D-A95F-14385BF85511}">
<File Id="Exe_File" Source=".\bin\Release\AwesomeApp.exe" KeyPath="yes" Vital="yes" />
<File Id="Sql_File" Source="..\Awesome.Database\sql\Release\Awesome.Database.sql" Vital="yes" />
</Component>
</DirectoryRef>
</Product>
</Wix>
The installer is really very simple. As you can see at the bottom of the file there is the component with two files: AwesomeApp.exe and Awesome.Dabase.sql. Those files are copied to the target installation folder and that is the only think the installer does.
If you have the WIX 3.5 installed on your computer, you can create the installer by the following commands:
candle -ext WiXNetFxExtension Installer.wxs
light -ext WiXNetFxExtension –out Awesome.Setup.msi Installer.wixobj
However for building an installer in this example use the following command.
msbuild installer.build
Configuring delivery process
At last, here comes the interesting part of delivery process, it is configuration. Since we have installer package prepared, we can wrap it into a bat file which configures the installer for proper environment before executing. This can be seen in the following fixture.

The installer.bat file keeps the same code for all installation. You can decide how complicated it will be and how much it will be doing. In this example I wrote a bat file which installs/uninstalls the application and installs database if needed. The code of such functionality could be seen in the following listing.
@ECHO OFF
@echo
@echo ================================================================
IF [%1]==[/x] GOTO UNINSTALL
IF [%1]==[/?] GOTO HELP
IF NOT EXIST "%~f1" GOTO CNF_MISSING
:SET_VARIABLES
call "%~f1"
@echo Testing of sqlcmd.exe availability
%SQLCMD% /?
IF NOT %ERRORLEVEL% EQU 0 GOTO CMD_SQL_NOT_FOUND
cls
@echo
@echo ================================================================
@echo Configuring installation based on %~f1
SET LOG_FILE=Awesome.App.log.txt
:APP_INSTALL
@echo Awesome Application by me
@echo Installing Awesome app...
msiexec /q /i Awesome.Setup.msi AWESOME_DIR="%AWESOME_DIR%" /log %LOG_FILE%
IF NOT %ERRORLEVEL% EQU 0 GOTO APP_ERROR
@echo Awesome app installation succeeded
:DB_INSTALL
IF [%2]==[/skipDb] GOTO END
@echo Installing DB on server %SQLSERVER%...
%SQLCMD% -S %SQLSERVER% -E -i "%AWESOME_DIR%\Awesome.Database.sql" -f 65001
IF NOT %ERRORLEVEL% EQU 0 GOTO DB_ERROR
@echo DB installed
GOTO END
:UNINSTALL
@echo Uninstalling Awesome app...
msiexec /q /x {DACD6B11-B50F-43B0-9FCB-46779FDC7AA3}
@echo Uninstall finished.
GOTO END
:HELP
@echo Install Script for Awesome app
@echo Examples:
@echo install.bat cnf_server1.bat - installs the according to cnf_server1.bat configuration
@echo install.bat cnf_server1.bat /skipDb - installs application without DB.
@echo install.bat /x - uninstalls the application but not database.
@echo install.bat /? - help
GOTO END
:APP_ERROR
@echo Installation of Awesome application failed. See the log for more information: %LOG_FILE%
GOTO HELP
:DB_ERROR
@echo Creating Database failed.
GOTO UNINSTALL
:CMD_SQL_NOT_FOUND
@echo %SQLCMD% was not found. Please set up the variable to point out to this utility before executing.
GOTO HELP
:CNF_MISSING
@echo Configuration file is missing. Please specify configuration file as first parameter
GOTO HELP
:END
@echo ================================================================
@echo
I hope the code is not so complicated. If you run it with the /? option, you get the help. What I want to point out now is the code:
:SET_VARIABLES
call "%~f1"
which is calling the configuration script. The configuration script is passed as the first parameter to the install.bat and it setups the variables which are passed to the installer later on. Exactly in this part:
msiexec /q /i Awesome.Setup.msi AWESOME_DIR="%AWESOME_DIR%" /log %LOG_FILE%
As you probably noticed the AWESOME_DIR variable is not defined in the install.bat script. If you open one of the configuration files, you will see something what is listed in the following Listing:
REM -------------------------------------------------------------------------------
REM
REM Please set up the variables in this file
REM The value of the variable follows after = sign without any additional characters like "
REM even if you have a space in your value, do NOT use " to surround it.
REM -------------------------------------------------------------------------------
REM -----------------------------------------------
REM Target directory for installation. The Path where the application will be deployed.
SET AWESOME_DIR=C:\my_apps\awesome
REM -----------------------------------------------
REM DB server for database deploy
SET SQLSERVER=localhost
REM ------------------------------------------------
REM Continue with other variables like connection string and etc.
REM -----------------------------------------------
REM Path to sqlcmd.exe utility if necessary.
REM If the MSSQL utilities are installed correctly then this do not need to be changed otherwise please set up the right path.
REM The installation will automatically check if the sqlcmd.exe is available.
REM If not then it will not apply any changes to the target system.
SET SQLCMD=sqlcmd.exe
There are many aspects for configuration depending on the server. Target directory, connection string, registry values… Simply to say: a single configuration script for a single environment.
Remarks
You probably noticed that the uninstall process is not part of the introduced script. Well, open question.
You also can say that this approach is useful for delivering to servers but not for delivering to workstation and you require the UI during the installation. Like wizards for gathering variables from user during the installation. Well, that is not simply true. You can always build your installer with huge amount of UI screens and for the Continuous Delivery process just keep it quiet using /q option in msiexec application.
Conclusion
Happy saving time during building your apps :)
History
- 7 July 2011
- Initial release