in the File System view, each file or output has a Condition property that can be specified. If this
condition fails, the file is not installed on the target machine. In each of these cases the syntax of
the Condition property must be valid for the MsiEvaluateCondition function that is called as
part of the installation process. This function accepts standard comparison operators, such as
equals (=), not equals (<>), less than (<), and greater than (>), as well as Boolean operators NOT,
AND, OR, and XOR. There are also some predefined Windows installer properties that can be
included in the condition property. The following is a subset of the full list, which you can find in
the documentation for the Windows Installer SDK:
.
ComputerName: Target computer name
.
VersionNT: Version of Windows on the target computer
.
VersionNT64: The same value as VersionNT but this property is only set for 64-bit operating
systems
.
ServicePackLevel: The service pack that has been installed
.
LogonUser: The username of the current user
.
AdminUser: Whether the current user has administrative privileges
.
COMPANYNAME: The company name, as specified in the installation wizard
.
USERNAME: The username, as specified in the installation wizard
One of the main reasons for creating an installer is to make the process
of deploying an application much smoother. To do this, you need to create
a simple user interface into which an end user can specify values. This
might be the installation directory or other parameters that are required
to configure the application. Clearly, the fewer steps in the installer the
easier the application will be to install. However, it can be better to
prompt for information during the installation than for the user to later
sit wondering why the application is not working. The User Interface
view, shown in Figure 48-13, enables you to customize the screens that
the user sees as part of the installation process.
fiGure 48-13
970 .
chaPter 48 pAckAging And deploymenT
Two user interfaces are defined in this view: the standard installation and an Administrative
install (not visible). Both processes follow the same structure: Start, where you typically collect
information from the user before installing the product; Progress, used for providing a visual cue as
to the installation’s progress; and End, at which point the user is presented with a summary of the
installation. The Administrative install is typically used when a network setup is required, and can
be invoked by calling msiexec with the /a flag.
You can customize either of the installation processes by adding and/or removing dialogs from the
user interface tree. To add a new dialog, right-click any of the three stages in the installation process
and select Add Dialog from the context menu. This displays a list of the predefined dialogs from
which you can choose. Each of the dialogs has a different layout; some are used for accepting user
input and others are used to display information to the user. Input controls are allocated a property
identifier so that the value entered during the installation process can be used later in the process.
For example, a checkbox might be used to indicate whether the tools for a product should be
installed. A condition could be placed on an output in the File System view so the tools are installed
only if the checkbox is enabled.
adding custom actions
It is often necessary to perform some actions either before or after the application is installed. To
do this, you can create a custom action to be executed as part of the install or uninstall process.
Adding a custom action entails creating the code to be executed and linking the appropriate installer
event so that the code is executed. Custom actions use an event model similar to what Windows
components use to link the code that you write to the appropriate installer event. To add a custom
action to an installer event, you need to create a class that inherits from the Installer base class.
This base class exposes a number of events for which you can write event handlers. Because writing
custom installer actions is quite a common task, the Add New Item dialog includes an Installer
Class template item under the General node. The new class (added to the SharedResources project)
opens using the component designer, as shown in Figure 48-14.
fiGure 48-14
From the Events tab of the Properties window, select the installer event for which you want to add
an event handler. If no event handler exists, a new event handler will be created and opened in the
code window. The following code is automatically generated when an event handler is created.
A simple message box is inserted to notify the user that the AfterInstall event handler has
completed:
Windows installers .
971
c#
using System.ComponentModel;
using System.Configuration.Install;
using System.Windows.Forms;
public partial class InstallerActions
{
public InstallerActions()
{
InitializeComponent()
}
private void InstallerActions_AfterInstall(object sender, InstallEventArgs e)
{
MessageBox.Show("Installation process completed!");
}
}
Code snippet SharedResources\InstallerActions.cs
Vb
Imports System.ComponentModel
Imports System.Configuration.Install
Imports System.Windows.Forms
Public Class InstallerActions
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Private Sub InstallerActions_AfterInstall(ByVal sender As Object, _
ByVal e As InstallEventArgs) _
Handles Me.AfterInstall
MessageBox.Show("Installation process completed!")
End Sub
End Class
Code snippet SharedResources\InstallerActions.vb
As with forms and other components, the rest of this class is stored in a designer class file where
the partial InstallerActions class inherits from the Installer class and is attributed with the
RunInstaller attribute. This combination ensures that this class is given the opportunity to
handle events raised by the installer.
The InstallerActions class you have just created was added to the SharedResources assembly.
For the events to be wired up to the InstallerActions class, the installer needs to know that there
is a class that contains custom actions. To make this association, add the SharedResources assembly
972 .
chaPter 48 pAckAging And deploymenT
to the Custom Actions view for the deployment project by right-clicking any of the nodes shown in
Figure 48-15 and selecting Add Custom Action from the context menu. In this case, you want to
wire up the SharedResources. In Figure 48-15, this association has been made only for the Install
action. If you want to wire up the Custom Action class for all of the actions, you need to add the
custom action to the root Custom Actions node.
fiGure 48-15
To complete this discussion, understand that it is important to be able to pass information collected
from the user during the Start phase of the installation process to the custom action. Unfortunately,
because the custom action is invoked after the installer has finished, you have to use a special
channel to pass installer properties to the custom action event handler. In the Custom Actions view
(refer to Figure 48-15), select Properties Window from the right-click context menu for the Primary
output node. The CustomActionData property is used to define name/value pairs that will be sent
through to the custom installer. For example, you might have /PhoneNumber= “+1 425 001 0001”,
in which case you can access this value in the event handler as follows:
c#
private void CustomActions_AfterInstall(object sender, InstallEventArgs e)
{
MessageBox.Show("Number: " + this.Context.Parameters["PhoneNumber"].ToString());
}
Code snippet SharedResources\CustomActions.cs
Vb
Private Sub CustomActions_AfterInstall(ByVal sender As Object, _
ByVal e As InstallEventArgs) _
Handles Me.AfterInstall
MessageBox.Show("Number: " & Me.Context.Parameters("PhoneNumber").ToString)
End Sub
Code snippet SharedResources\CustomActions.vb
Of course, hard-coded values are not a good idea and it would be better if this were a user-specified
value. To use a property defined in the installer user interface, replace the specified string with the
property identifier in square brackets. For example, /PhoneNumber=[TXTPHONENUMBER] would
include the text in the TXTPHONENUMBER textbox.
Windows installers .
973
the service installer
You can create an installer for a Windows Service the same way you would create an installer
for a Windows application. However, a Windows Service installer not only needs to install the
files into the appropriate location, but it also needs to register the service so it appears in the services
list. You can do this using the ServiceInstaller and ServiceProcessInstaller components
from the System.ServiceProcess namespace (you’ll probably need to add these to the Toolbox,
because they are not visible by default). An instance of each of these components needs to be
dragged onto the designer surface of a custom installer, as shown in Figure 48-16.
fiGure 48-16
The ServiceInstaller class is used to specify the display name (the name of the service as it
will appear in the Windows services list), the service name (the name of the service class that will
be executed when the service is run), and the startup type (whether it is manually started or
automatically started when Windows starts up). For each service you want to install, you need to
create a separate instance of the ServiceInstaller class, specifying a different display and service
name. Only a single instance of the ServiceProcessInstaller class is required, which is used
to specify the account information that the service(s) will run as. In the following example, the
InstallerForService constructor specifies that the class Service1 should be installed as a service,
and that it should automatically start using the NetworkService account:
c#
[RunInstaller(true)]
public partial class InstallerForService : System.Configuration.Install.Installer
{
const string SERVICE_DISPLAY_NAME = "My Generic Service";
const string START_AFTER_INSTALL = "STARTAFTERINSTALL";
const string NET_PROCESS_NAME = "Net";
const string NET_START = "Start \"{0}\"";
const int NET_WAIT_TIMEOUT = 5000;
const string NET_WAIT_ERROR = "WARNING: Process took longer than " +
"expected to start, it may need to be restarted manually";
public InstallerForService()
974 .
chaPter 48 pAckAging And deploymenT
{
InitializeComponent();
serviceInstaller1.DisplayName = SERVICE_DISPLAY_NAME;
serviceInstaller1.ServiceName = typeof(Service1).ToString();
serviceInstaller1.StartType = ServiceStartMode.Automatic;
serviceProcessInstaller1.Account = ServiceAccount.NetworkService;
}
private void InstallerForService_AfterInstall(object sender, InstallEventArgs e)
{
var startString = Context.Parameters[START_AFTER_INSTALL];
if (startString == "") return;
var shouldStart = Boolean.Parse(startString);
if (!shouldStart) return;
var proc = Process.Start(CreateNetStartProcessInfo());
if (!proc.WaitForExit(NET_WAIT_TIMEOUT))
MessageBox.Show(NET_WAIT_ERROR);
}
private ProcessStartInfo CreateNetStartProcessInfo()
{
return new ProcessStartInfo(NET_PROCESS_NAME,
String.Format(NET_START, SERVICE_DISPLAY_NAME))
{
WindowStyle = ProcessWindowStyle.Hidden
};
}
}
Code snippet Windows Service\InstallerForService.cs
Vb
Public Class InstallerForService
Const SERVICE_DISPLAY_NAME = "My Generic Service"
Const START_AFTER_INSTALL = "STARTAFTERINSTALL"
Const NET_PROCESS_NAME = "Net"
Const NET_START = "Start ""{0}"""
Const NET_WAIT_TIMEOUT = 5000
Const NET_WAIT_ERROR = "WARNING: Process took longer than " &
"expected to start, it may need to be restarted manually"
Public Sub New()
MyBase.New()
’This call is required by the Component Designer.
Windows installers .
975
InitializeComponent()
’Add initialization code after the call to InitializeComponent
ServiceInstaller1.DisplayName = SERVICE_DISPLAY_NAME
ServiceInstaller1.ServiceName = GetType(Service1).ToString
ServiceInstaller1.StartType = ServiceStartMode.Automatic
ServiceProcessInstaller1.Account = ServiceAccount.NetworkService
End Sub
Private Sub InstallerForService_AfterInstall(ByVal sender As System.Object,
ByVal e As System.Configuration.Install.InstallEventArgs) _
Handles MyBase.AfterInstall
Dim startString = Context.Parameters(START_AFTER_INSTALL)
If startString = "" Then Return
Dim shouldStart = Boolean.Parse(startString)
If Not shouldStart Then Return
Dim proc = Process.Start(CreateNetStartProcessInfo())
If Not proc.WaitForExit(NET_WAIT_TIMEOUT) Then
MessageBox.Show(NET_WAIT_ERROR)
End If
End Sub
Private Function CreateNetStartProcessInfo() As ProcessStartInfo
Dim startInfo = New ProcessStartInfo(NET_PROCESS_NAME,
String.Format(NET_START, SERVICE_DISPLAY_NAME))
startInfo.WindowStyle = ProcessWindowStyle.Hidden
Return startInfo
End Function
End Class
Code snippet Windows Service\InstallerForService.vb
Also included in this listing is an event handler for the AfterInstall event that is used to start
the service on completion of the installation process. By default, even when the startup is set to
automatic, the service will not be started by the installer. However, when uninstalling the service,
the installer does attempt to stop the service.
The user interface for this deployment project includes a Checkboxes (A) dialog using the User
Interface view for the project. Refer to Figure 48-13 for a view of the default user interface. Right-