WiXing Lyrical (Part 2)
April 18, 2012 Leave a comment
Picking up from where we left off previously with the product.wxs file, next we come to the Media element.
<Media Id=”1″ Cabinet=”media1.cab” EmbedCab=”yes” />
This is the default media entry created by Votive. The files to be installed are constructed into a single cabinet file which is embedded within the MSI.
Specifying the Install Location
Following the media is the directory structure we want to install the application into. A set of directory elements are nested to describe the required structure.
<Directory Id=”TARGETDIR” Name=”SourceDir”>
<Directory Id=”dirAderant” Name=”AderantExpert”>
<Directory Id=”dirEnvironmentFolder” Name=”[EXPERTENVIRONMENTNAME]” >
<Directory Id=”INSTALLLOCATION” Name=”ExpertAssistantInstaller”>
<!–additional components go here –>
</Directory>
<Directory Id=”ProgramMenuFolder”>
<Directory Id=”ApplicationProgramsFolder” Name=”Aderant”>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
The TARGETDIR is the root directory for the installation and by default is set to the SourceDir. We then set-up the directory structure underneath \AderantExpert\Environment\ExpertAssistantInstaller. The environment folder is set to the value in the EXPERTENVIRONMENTNAME property. The INSTALLLOCATION Id specifies where the files will be installed to. If you want to install into the Program Files folder, see here.
In addition to specifying the target location for the install files, a folder is added to the Program Menu folder for the current user, the ApplicationProgramsFolder reference is used later in the script when setting up the start menu items.
Updating an XML File
It is possible to use components to action additional steps and tie the KeyPath to the containing directory structure. The KeyPath is used by the installer to determine if an item exists so if the containing directory structure exists the actions do not run. In my sample the red comment above is a place holder for a couple of components similar to.
<Component Id=”cmpConfigEnvironmentName” Guid=”????” KeyPath=”yes”>
<util:XmlFile Id=”xmlConfigEnvironmentName”
Action=”setValue”
ElementPath=”/configuration/appSettings/add[\[]@key=’EnvironmentName'[\]]/@value”
File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
Value=”[EXPERTENVIRONMENTNAME]”
/>
</Component>
The component is responsible for updating the ExpertAssistant.exe.config Xml file with a property from the MSI. The util extension library provides a XmlFile function which can read and write to a specified Xml file. The element path is a formatted field and therefore, square brackets in the XPath must be escaped. We have three updates to the exe.config to make and so end up with three components, for ease of management these are then wrapped in a ComponentGroup:
<ComponentGroup Id=”ExpertAssistantCO.ConfigSettings”>
<ComponentRef Id=”cmpConfigEnvironmentName”/>
<ComponentRef Id=”cmpConfigExpertSharePath”/>
<ComponentRef Id=”cmpConfigLocalInstallationPath”/>
</ComponentGroup>
Adding a Start Menu Item
A common requirement is to add a shortcut for the installed application to the Start Menu. There is an odd twist here as we are using a perUser install. The StartMenu item is unique to each user installing the software and therefore a registry key is required to track the KeyPath. The registry key must be in the HKEY_CURRENT_USER hive and a logical location is Software\VendorName\ApplicationName\.
<DirectoryRef Id=”ApplicationProgramsFolder”>
<Component Id=”ApplicationShortcut” Guid=”????” >
<Shortcut Id=”ApplicationStartMenuShortcut”
Name=”Expert Assistant”
Description=”Expert Assistant”
Target=”[INSTALLLOCATION]\ExpertAssistantCO.exe”
/>
<RegistryValue Root=”HKCU” Key=”Software\Aderant\ExpertAssistant_[EXPERTENVIRONMENTNAME]” Name=”installed” Type=”integer” Value=”1″ KeyPath=”yes”/>
<RemoveFolder Id=”ApplicationProgramsFolder” On=”uninstall”/>
</Component>
</DirectoryRef>
Along with the Shortcut, we also tie a RemoveFolder action to the registry KeyPath so that the folder containing the shortcut is removed during an uninstall.
Remove Custom Registry Key On Uninstall
It is possible to have specific actions occur only during an uninstall to clean up, we have this need to remove a registry key that maybe set-up by the application. To achieve this we schedule a Registry action to ‘removeKeyOnUninstall’. Again, this action is perUser and therefore tied to a KeyPath in the HKEY_CURRENT_USER registry hive.
<DirectoryRef Id=”ApplicationProgramsFolder”>
<Component Id=”RemoveRunRegistryEntry” Guid=”????”>
<RegistryValue Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run” Name=”ADERANT.ExpertAssistant” KeyPath=”yes” Type=”string” Value=””/>
<Registry Action=”removeKeyOnUninstall” Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run\ADERANT.ExpertAssistant” />
</Component>
</DirectoryRef>
Launch an Exe after Installation
After the installation or repair of the MSI is complete, we’d like the MSI to run the executable that we’ve just installed on to the machine. To do this we need to invoke a custom action.
<CustomAction Id=”LaunchApplication” BinaryKey=”WixCA” DllEntry=”WixShellExec” Impersonate=”yes” />
The custom action executes the command held in the WixShellExecTarget property that we specified near the beginning of the wxs file. The custom action then needs to be scheduled to run after InstallFinalize:
<InstallExecuteSequence>
<Custom Action=”LaunchApplication” After=”InstallFinalize” >NOT(REMOVE ~=”ALL”)</Custom>
</InstallExecuteSequence>
In our case we don’t want the action to execute if we are removing the software, only on install and repair. Therefore we specify the condition ‘NOT(REMOVE ~=”ALL”)’, more details can be found here.
NOTE: Before setting this condition, the entry in the Add Remove Programs control panel would not automatically be deleted on uninstall, it would only disappear after a manual refresh. If the uninstall process returns a code other than 0, an error has occurred and so the refresh is not triggered. To see this was the case, I enabled verbose logging via msiexec and removed the MSI using the command line. The log showed that the custom action was failing because the path didn’t exist – because we had just removed it. The non-zero return code was logged.
Pulling It All Together
The final part of the script declares a feature – an installable unit. In our case we have a single feature which installs everything.
<Feature Id=”ExpertAssistant”
Title=”Expert Assistant”
Level=”1″>
<ComponentGroupRef Id=”ExpertAssistantCO.Binaries” />
<ComponentGroupRef Id=”ExpertAssistantCO.Content” />
<ComponentGroupRef Id=”ExpertAssistantCO.ConfigSettings” />
<ComponentRef Id=”ApplicationShortcut” />
<ComponentRef Id=”RemoveRunRegistryEntry” />
</Feature>
</Product>
</Wix>
Here the various component groups we’ve declared come into play, rather than listing out every feature individually we can reference the logical component group.
And we are done. We don’t require a UI for our installer and so I’ve not looked into that in any depth. WiX does fully support defining an installation wizard UI and even supports custom UI.
The last piece of the WiX tooling is the Deployment Foundation Toolkit which I’ll save for the next post.
Full product.wxs script for reference (with the GUIDs taken out) to close…
<?xml version=”1.0″ encoding=”UTF-8″?>
<Wix xmlns=”http://schemas.microsoft.com/wix/2006/wi” xmlns:util=”http://schemas.microsoft.com/wix/UtilExtension” RequiredVersion=”3.5.0.0″>
<Product Id=”?”
Name=”ExpertAssistant”
Language=”1033″
Codepage=”1252″
Version=”8.0.0.0″
Manufacturer=”Aderant”
UpgradeCode=”?”>
<Package InstallerVersion=”200″
Compressed=”yes”
Manufacturer=”Aderant”
Description=”Expert Assistant Installer”
InstallScope=”perUser”
/>
<Property Id=”EXPERTSHAREPATH” Value=”\\MyShare\ExpertShare” />
<Property Id=”EXPERTENVIRONMENTNAME” Value=”MyEnvironment” />
<Property Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\Environment\Applications” />
<Property Id=”ARPPRODUCTICON” Value=”icon.ico” />
<Property Id=”ARPNOMODIFY” Value=”1″ />
<Property Id=”WixShellExecTarget” Value=”[#filExpertAssistantCOexe]”/>
<SetProperty Id=”dirEnvironmentFolder” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]” After=”CostInitialize”/>
<SetProperty Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]\Applications” After=”CostInitialize” />
<Property Id=”EnableUserControl” Value=”1″ />
<Icon Id=”icon.ico” SourceFile=”$(var.ExpertAssistantCO.TargetDir)\Expert_Assistant_Icon.ico”/>
<Media Id=”1″ Cabinet=”media1.cab” EmbedCab=”yes” />
<Directory Id=”TARGETDIR” Name=”SourceDir”>
<Directory Id=”dirAderant” Name=”AderantExpert”>
<Directory Id=”dirEnvironmentFolder” Name=”[EXPERTENVIRONMENTNAME]” >
<Directory Id=”INSTALLLOCATION” Name=”ExpertAssistantInstaller”>
<Component Id=”cmpConfigEnvironmentName” Guid=”?” KeyPath=”yes”>
<util:XmlFile Id=”xmlConfigEnvironmentName”
Action=”setValue”
ElementPath=”/configuration/appSettings/add[\[]@key=’EnvironmentName'[\]]/@value”
File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
Value=”[EXPERTENVIRONMENTNAME]”
/>
</Component>
<Component Id=”cmpConfigExpertSharePath” Guid=”?” KeyPath=”yes”>
<util:XmlFile Id=”xmlConfigExpertSharePath”
Action=”setValue”
ElementPath=”/configuration/appSettings/add[\[]@key=’ExpertSharePath'[\]]/@value”
File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
Value=”[EXPERTSHAREPATH]”
/>
</Component>
<Component Id=”cmpConfigLocalInstallationPath” Guid=”?” KeyPath=”yes”>
<util:XmlFile Id=”xmlConfigLocalInstallationPath”
Action=”setValue”
ElementPath=”/configuration/appSettings/add[\[]@key=’LocalInstallationPath'[\]]/@value”
File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
Value=”[EXPERTLOCALINSTALLPATH]”
/>
</Component>
</Directory>
<Directory Id=”ProgramMenuFolder”>
<Directory Id=”ApplicationProgramsFolder” Name=”Aderant”>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<ComponentGroup Id=”ExpertAssistantCO.ConfigSettings”>
<ComponentRef Id=”cmpConfigEnvironmentName”/>
<ComponentRef Id=”cmpConfigExpertSharePath”/>
<ComponentRef Id=”cmpConfigLocalInstallationPath”/>
</ComponentGroup>
<DirectoryRef Id=”ApplicationProgramsFolder”>
<Component Id=”ApplicationShortcut” Guid=”?” >
<Shortcut Id=”ApplicationStartMenuShortcut”
Name=”Expert Assistant”
Description=”Expert Assistant”
Target=”[INSTALLLOCATION]\ExpertAssistantCO.exe”
/>
<RegistryValue Root=”HKCU” Key=”Software\Aderant\ExpertAssistant_[EXPERTENVIRONMENTNAME]” Name=”installed” Type=”integer” Value=”1″ KeyPath=”yes”/>
<RemoveFolder Id=”ApplicationProgramsFolder” On=”uninstall”/>
</Component>
</DirectoryRef>
<DirectoryRef Id=”ApplicationProgramsFolder”>
<Component Id=”RemoveRunRegistryEntry” Guid=”?”>
<RegistryValue Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run” Name=”ADERANT.ExpertAssistant” KeyPath=”yes” Type=”string” Value=””/>
<Registry Action=”removeKeyOnUninstall” Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run\ADERANT.ExpertAssistant” />
</Component>
</DirectoryRef>
<CustomAction Id=”LaunchApplication” BinaryKey=”WixCA” DllEntry=”WixShellExec” Impersonate=”yes” />
<InstallExecuteSequence>
<Custom Action=”LaunchApplication” After=”InstallFinalize” >NOT(REMOVE ~=”ALL”)</Custom>
</InstallExecuteSequence>
<Feature Id=”ExpertAssistant”
Title=”Expert Assistant”
Level=”1″>
<ComponentGroupRef Id=”ExpertAssistantCO.Binaries” />
<ComponentGroupRef Id=”ExpertAssistantCO.Content” />
<ComponentGroupRef Id=”ExpertAssistantCO.ConfigSettings” />
<ComponentRef Id=”ApplicationShortcut” />
<ComponentRef Id=”RemoveRunRegistryEntry” />
</Feature>
</Product>
</Wix>