20 May 2012

Custom User Interface

This tutorial will demonstrate how to install a service and select the user account for the service at install time.

Introduction

Services are installed by calling the InstallService standard action which reads records in the ServiceInstall database table. Two of the fields in this table are StartName and Password and the values of these fields cannot be changed. Fortunately, these fields are of type Formatted, which gives us the ability to include property names in the values of these fields. These properties are then evaluated at install time, giving us a chance to set their values before the execute sequence is run.

We will use custom built dialogs along with custom actions to allow the user to choose the domain/account and password to use at install time. The configuration of the user interface is somewhat time consuming and it is recommended to load the sample project file, ACME Select User.xml, into MSIStudio and build the project.

Create the Custom Actions

We use two custom actions in this sample. These use pieces of code to query the system for the list of available domains, and the list of available users for thos domains. The FillDomains and FillUsersFromDomain functions are available in ExtensionDLL.dll that ships with MSIStudio. To be able to use these functions, it is necessary to create custom actions within the project file. In the CustomActions screen, create two custom actions of type 'Call DLL Function'.

Name one FillDomains, and point it to ExtensionDLL.dll and the function name FillDomains. Calling this function will insert temporary records into into the ListBox table, the property is hard-coded to XT_DOMAIN_NAME and each row will have a Value field set to an available domain. This will allow the user to place a list box control anywhere in the wizard sequence to display these domains and select one.

Name the other custom action FillUsers and make it also point to ExtensionDLL.dll and the function name FillUsersFromDomain. Calling this custom action from within the setup will insert records into the ListBox, one for each user for the domain specifies by the property XT_DOMAIN_NAME. Each record will have a property field set to XT_USER_NAME and a Value set to the name of the user. A ListBox control referencing XT_USER_NAME will allow the user to select a user into the XT_USER_NAME property. Used in conjunction with the FillDomains custom action, this gives the user the ability to select a user account on any machine on the network and use the properties elsewhere in the setup.

Add the Service

The service that we are going to install is called SimpleService.exe. It simply beeps every 10 seconds. Although not a typical use for a service, it is sufficient to demonstrate the service installation procedure. In the FileSystem view, add the file SimpleService.exe to the install folder. Right click on this file and select the Add Install Service option. This will create an Install Service object which we can configure. Set the name to be BeepService, set the Account Type to User Account, set the User Name to [XT_DOMAIN_NAME]\[XT_USER_NAME] and the Password to [XT_PASSWORD]. By specifying the account name and password like this we can use properties to set the values using the wizard before installing the service.

We are going to use the property name XT_DOMAIN_NAME for the domain, and the property XT_USER_NAME for the user account name. Windows Installer expects usernames of the form <domain>/<user>. In this case, we are going to specify the User Name as [XT_DOMAIN_NAME]\[XT_USER_NAME], and the password as [XT_PASSWORD]. The square brackets around the property names instructs the runtime that these are properties that need to be evaluated at install time.

Customize the User Interface

The next step is to add an additional dialog to get the information for these properties. For this we need to create a custom wizard page requesting the domain and user account to use along with the password. Double-click Dialogs in the workspace and select Add Dialog from the context menu. Add a couple of list boxes to the dialog. The first list box is for domains and set the property to XT_DOMAIN_NAME. Also add a text label above the list called 'Domain:'. For the second list box, set the property to XT_USER_NAME and the label above it to 'User:'. Add a button between the list boxes called Update. The user list will not update automatically when the domain is changed and this button will need to be pressed to refresh the user list. Finally add the password label and edit box underneath the list controls. Thats it for user interface elements, now comes the tricky bit, the control logic.

In order for the user list to be updated once we select a new domain, we need to run the custom action FillUsers and then reload the dialog with the updated list of properties. Add the following control events to the Update button. Create an event called EndDialog to close the dialog. Create a second event to call FillUsers. Finally, create a NewDialog event to recreats this dialog again loaded with the updated information. Unfortunately, Windows Installer doesn't allow a dialog to create an instance of the same dialog, so we have to make a copy of this dialog called SelectUserDialog2 and reference this in the NewDialog event. Likewise, the control event for SelectUserDialog2 should call SelectUserDialog.

The next step is to place the SelectUserDialog dialog box correctly into the wizard sequence. We only want to display our user selection dialog if we are actually installing the service. For this we can check the installation state of the component that controls the installation of the service. This can be done by evaluating the condition '$ServiceComponent=3'. The user selection should take place as the last activity in the user interface before commencing the install. Therefore, we need to place this condition on the Next buttons of the CustomizeSetup and SetupType dialogs. We also will want to be able to back into this dialog from the VerifyReady dialog so place this condition also on the Back button.

Modify the Sequence Tables

In this sample we do not need to make any modifications to the execute sequence table. The only changes are to the user interface sequence. Before the wizard is displayed, we would like to preload all of the domains that are available. This can cause a few seconds delay if the install machine is on a network. Before the WelcomeDialog is displayed, insert the custom action FillDomains. When the SelectUserDialog is initially displayed, the list of domains will be available.

Protecting Passwords

Windows Installer doesn't write the value of the Password field into the log file when installing the service. However, we also want to make sure that the password is not written when the property value list is dumped to the log also. To achieve this, set the Password field on the edit box to True. Also, set the value of MsiHiddenProperties to XT_PASSWORD. This will ensure that the password is never written out.

If the service fails to start after the installation completes and the service has been correctly installed, the problem may be that the user account does not have the right to run as a service. The user account must have the 'Log on as a Service' privilege assigned to it, and this is not usually done by default for a new user. To assign this privilege activate the Local Group Policy Editor by running gpedit.msc. Select the path Local Computer Policy\Computer Configuration\Windows Settings\Security Settings\Local Policies\User Rights Assignment. Select the privilege 'Log on as a Service' and edit the properties. Add the user account to this list. The service should now run.