WiX 3 tutorial



WiX 3 Tutorial 1: Solution/Project structure and Dev resources

This is the first post about building a WiX 3 installer from zero. The reason I’ve decided to write this WiX series is that the good sources are quite hard to find and examples even harder.

The finished installer (at the end of the series) will be localized in 2 languages, have localized EULA check, product key validation via a custom action, updating (and killing the running app when updating) and wrapping both localized MSI’s into a bootstrapper that will enable you to choose the installation language and install prerequisites like .Net framework 3.5. Those are the general features and we’ll well dive into each feature in a separate post.

We’ll be using WiX 3.0.5419.0 in Visual Studio 2008. You can download WiX at theSourceForge Wix Site. I’m expecting that you’re familiar with basic WiX operations and how to make the WiX project.

To help you with your WiX development be sure to use the ORCA tool (download viaWin SDK or directly fromhere) for viewing/editing MSI tables and defaultMSI logging capabilities like “msiexec /i SuperForm.Installer.msi /l*v SuperFormLog.txt” which will write a very detailed log of what it’s doing at each step.

The application we’ll be installing is called SuperForm which solves all your label’s text color needs by using the awesome action of pressing a button.

The first thing we have to do is to create a new System Environment variable SuperFormFilesDir containing the path to the directory where you keep the files you’ll install on the users computer. We’ll be using this variable in the automated building of wxs fragment file that holds the correct directory/files structure. This is my preference in environments with multiple developers. It is highly unlikely that all developers have the same directory structure. If you work alone you can also take the “preprocessor variables” route described below in Project Properties –> Build. I’ll be using the System Environment variable approach.

Solution/Project structure


We have three projects of which the most important for us is the WiX project. Logically the project is divided into 7 folders and 1 main file.

  1. CustomActions: stores WiX fragments that define different custom actions (CA’s). You can have 1 fragment per CA or all CA’s under 1 fragment. I prefer fragment per CA.
  2. CustomDialogs: stores WiX fragments that define our custom built dialogs. there are 2 we’ll have: The overwritten existing EULA dialog and fully custom product key check dialog.
  3. Fragments: stores WiX fragments that are either auto generated by Heat.exe directory harvesting or manually built like inserting some values into the registry.
  4. Includes: stores WiX include files that hold pre processor variables. Wxi files must be included at the top of each wxs file you’re using variables in.
  5. Lang: stores localization stuff
    1. de-de: German installer. Stores German EULA, custom localization file and official German WiX translations.
    2. en-us: US English installer. Stores English EULA and custom localization file.
  6. Packages: output for the actual MSI’s. This replaces the bin folder. 
    1. de-de: stores the German MSI installer.
    2. en-us: stores the English MSI installer.
  7. Resources: stores different resources like icons, jpg’s, etc… used in the installer.
  8. SuperForm.wxs: master installer files where all the magic happens.

We also need to include some references:

  1. SuperForm.CustomAction: needed to run our custom actions stored in the SuperForm.CustomAction.dll project
  2. WixNetFxExtension: needed for conditional check id .Net 3.5 is installed and abort if not.
  3. WixUIExtension: needed to include and modify any UI elements. 
  4. WixUtilExtension: needed to run CloseApplication built in custom action.

All non WiX files (others than wsx, wxi, wxl) don’t really need to be included in the project but i like to include them for clarity. This way we see exactly what is in the installer from the project itself.

SuperForm.Install WiX project properties:

  1. Installer: No changes
  2. Build:
    1. Change “Output Path” to “Packages\”. This is needed to respect the project structure. You could use the default ($Configuration)\bin but I prefer not to. The ($Configuration) part is only useful if you’re building both 32 and 64 bit installers.
    2. If you don’t like adding a new System Environment variable you can do this instead. To “Define preprocessor variables” add the SuperFormFilesDir=pathToTheFolderWhereTheFilesToBeInstalledAre;”. Variables are delimited by semicolons (;).
  3. Build Events:
    1. To “Pre-build event” add "$(WIX)bin\heat.exe" dir "$(SuperFormFilesDir)" -cg SuperFormFiles -gg -scom -sreg -sfrag -srd -dr INSTALLLOCATION -var env.SuperFormFilesDir -out "$(ProjectDir)Fragments\FilesFragment.wxs". Heat.exe makes a wxs file with proper directory/file structure from the specified directory. We’ll take a detailed look into this in a future post.
    2. You can skip this for now but remember to add it later! To “Post-build event” add “$(SolutionDir)BootstrapperBuild.bat $(TargetDir) $(TargetFileName) $(SolutionDir)”. This will be used at the end to build the bootstrapper.
  4. Paths: No changes
  5. Tool settings: No changes

Resources for WiX development

  1. WiX Documentation – official docs, what else.
  2. Rob Mensching – creator and main contributor of WiX.
  3. WiX tutorial by Gábor DEÁK JAHN – this is an excellent tutorial that covers all aspects of WiX!
  4. From MSI to WiX by Alex Shevchuk – a Technet series on how to build an installer with WiX.
  5. WiX Mindcapers wiki – great wiki with lots of great stuff
  6. Joy of Setup by Bob Arnson – a setup blog with lots of good info.
  7. WiX tricks and best practices – StackOverflow wiki with some more links to best practices with WiX.


WiX 3 Tutorial 2: Understanding main WXS and WXI file

In the previous post we’ve taken a look at the WiX solution/project structure and project properties. We’re still playing with our super SuperForm application and today we’ll take a look at the general parts of the main wxs file, SuperForm.wxs, and the wxi include file. For wxs file we’ll just go over the general description of what each part does in the code comments. The more detailed descriptions will be in future posts about features themselves.

WXI include file

Include files are exactly what their name implies. To include a wxi file into the wxs file you have to put the wxi at the beginning ofeach .wxs file you wish to include it in. If you’ve ever worked with C++ you can think of the include files as .h files. For example if you include SuperFormVariables.wxi into the SuperForm.wxs, the variables in the wxi won’t be seen in FilesFragment.wxs or RegistryFragment.wxs. You’d have to include it manually into those two wxs files too.

For preprocessor variable $(var.VariableName) to be seen by every file in the project you have to include them in the WiX project properties->Build->“Define preprocessor variables” textbox.

This is why I’ve chosen not to go this route because in multi developer teams not everyone has the same directory structure and having a single variable would mean each developer would have to checkout the wixproj file to edit the variable. This is pretty much unacceptable by my standards. This is why we’ve added a System Environment variable named SuperFormFilesDir as is shown in the previousWix Tutorial post. Because the FilesFragment.wxs is autogenerated on every project build we don’t want to change it manually each time by adding the include wxi at the beginning of the file. This way we couldn’t recreate it in each pre-build event.

xml version="1.0" encoding="utf-8"?>
<Include>
  
  define MajorVersion="1" ?>
  define MinorVersion="0" ?>
  define BuildVersion="0" ?>
  
  define Revision="0" ?>
  
  define VersionNumber="$(var.MajorVersion).$(var.MinorVersion).$(var.BuildVersion).$(var.Revision)" ?>
  
  define UpgradeCode="YOUR-GUID-HERE" ?>
  
  define ResourcesDir="$(var.ProjectDir)\Resources" ?>  
  
  define ExeProcessName="SuperForm.MainApp.exe" ?>
Include>

For now there’s no way to tell WiX in Visual Studio to have a wxi include file available to the whole project, so you have to include it in each file separately.

Only variables set in “Define preprocessor variables” or System Environment variables are accessible to the whole project for now.

The main WXS file: SuperForm.wxs

We’ll only take a look at the general structure of the main SuperForm.wxs and not its the details. We’ll cover the details in future posts. The code comments should provide plenty info about what each part does in general.

Basically there are 5 major parts. The update part, the conditions and actions part, the UI install sequence, the directory structure and the features we want to include.

xml version="1.0" encoding="UTF-8"?>

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  
  include $(sys.CURRENTDIR)Includes\SuperFormVariables.wxi ?>
  
  <Product Id="*" Name="!(loc.ProductName) $(var.VersionNumber)" Language="!(loc.LANG)" Version="$(var.VersionNumber)" Manufacturer="!(loc.ManufacturerName)" UpgradeCode="$(var.UpgradeCode)">
    
    <Package InstallerVersion="300" Compressed="yes" InstallScope="perMachine"/>
    <Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />    
    
    <Upgrade Id="$(var.UpgradeCode)">
      <UpgradeVersion OnlyDetect="yes" Minimum="$(var.VersionNumber)" IncludeMinimum="no" Property="NEWER_VERSION_FOUND" />
      <UpgradeVersion Minimum="0.0.0.0" IncludeMinimum="yes" Maximum="$(var.VersionNumber)" IncludeMaximum="no" Property="OLDER_VERSION_FOUND" />
    Upgrade>    
    
    <PropertyRef Id="NETFRAMEWORK35"/>    
    
    <Condition Message="!(loc.DotNetFrameworkNeeded)">
      [CDATA[Installed OR NETFRAMEWORK35]]>
    Condition>
    <Condition Message="!(loc.AppNotSupported)">
      [CDATA[Installed OR ((VersionNT >= 501 AND ServicePackLevel >= 2) OR (VersionNT >= 502))]]>
    Condition>
    
    <CustomAction Id="NewerVersionFound" Error="!(loc.SuperFormNewerVersionInstalled)" />
    <InstallExecuteSequence>
      
      <Custom Action="NewerVersionFound" After="FindRelatedProducts">
        [CDATA[NEWER_VERSION_FOUND]]>
      Custom>
      
      <RemoveExistingProducts After="InstallInitialize"/>
      
      <Custom Action="WixCloseApplications" Before="InstallInitialize" />
    InstallExecuteSequence>
    
    <util:CloseApplication Id="CloseSuperForm" CloseMessage="no" Description="!(loc.MustCloseSuperForm)" 
                           ElevatedCloseMessage="no" RebootPrompt="no" Target="$(var.ExeProcessName)" />
    
    <UIRef Id="WixUI_InstallDir" />
    <UI>
      
      <DialogRef Id="FilesInUse" />
      <DialogRef Id="MsiRMFilesInUse" />      
            
    UI>
    
    <Icon Id="SuperFormIcon.ico" SourceFile="$(var.ResourcesDir)\Exclam.ico" />
    <Property Id="ARPPRODUCTICON" Value="SuperFormIcon.ico" />
    
    
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="!(loc.ProductName)" />
      Directory>
    Directory>
    
    <Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
    
    <Feature Id="SuperFormFeature" Title="!(loc.ProductName)" Level="1">
      <ComponentGroupRef Id="SuperFormFiles" />
      <ComponentRef Id="cmpVersionInRegistry" />
      <ComponentRef Id="cmpIsThisUpdateInRegistry" />

    Feature>
  Product>
Wix>

For more info on what certain attributes mean you should look into the WiX Documentation.


WiX 3 Tutorial 3: Generating file/directory fragments with Heat.exe

In previous posts I’ve shown you our SuperForm test application solution structure and how themain wxs and wxi include file look like. In this post I’ll show you how to automate inclusion of files to install into your build process. For our SuperForm application we have a single exe to install. But in the real world we have 10s or 100s of different files from dll’s to resource files like pictures. It all depends on what kind of application you’re building. Writing a directory structure for so many files by hand is out of the question. What we need is an automated way to create this structure. Enter Heat.exe.

Heat is a command line utility to harvest a file, directory, Visual Studio project, IIS website or performance counters. You might ask what harvesting means? Harvesting is converting a source (file, directory, …) into a component structure saved in a WiX fragment (a wxs) file.

There are 2 options you can use:

  1. Create a static wxs fragment with Heat and include it in your project. The pro of this is that you can add or remove components by hand. The con is that you have to do the pro part by hand. Automation always beats manual labor.
  2. Run heat command line utility in a pre-build event of your WiX project. I prefer this way. By always recreating the whole fragment you don’t have to worry about missing any new files you add. The con of this is that you’ll include files that you otherwise might not want to.

There is no perfect solution so pick one and deal with it. I prefer using the second way. A neat way of overcoming the con of the second option is to have a post-build event on your main application project (SuperForm.MainApp in our case) to copy the files needed to be installed in a special location and have the Heat.exe read them from there. I haven’t set this up for this tutorial and I’m simply including all files from the default SuperForm.MainApp \bin directory.

Remember how we created a System Environment variable called SuperFormFilesDir? This is where we’ll use it for the first time. The command line text that you have to put into the pre-build event of your WiX project looks like this:

"$(WIX)bin\heat.exe" dir "$(SuperFormFilesDir)" -cg SuperFormFiles -gg -scom -sreg -sfrag -srd -dr INSTALLLOCATION -var env.SuperFormFilesDir -out "$(ProjectDir)Fragments\FilesFragment.wxs"

After you install WiX you’ll get the WIX environment variable. In the pre/post-build events environment variables are referenced like this: $(WIX). By using this you don’t have to think about the installation path of the WiX. Remember: for 32 bit applications Program files folder is named differently between 32 and 64 bit systems. $(ProjectDir) is obviously the path to your project and is a Visual Studio built in variable.

You can view all Heat.exe options by running it without parameters but I’ll explain some that stick out the most.

  1. dir "$(SuperFormFilesDir)": tell Heat to harvest the whole directory at the set location. That is the location we’ve set in our System Environment variable.
  2. –cg SuperFormFiles: the name of the Component group that will be created. This name is included in out Feature tag as is seen inthe previous post.
  3. -dr INSTALLLOCATION: the directory reference this fragment will fall under. You can see the top level directory structure inthe previous post.
  4. -var env.SuperFormFilesDir: the name of the variable that will replace the SourceDir text that would otherwise appear in the fragment file.
  5. -out "$(ProjectDir)Fragments\FilesFragment.wxs": the full path and name under which the fragment file will be saved.

If you have source control you have to include the FilesFragment.wxs into your project but remove its source control binding. The auto generated FilesFragment.wxs for our test app looks like this:

xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Fragment>
        <ComponentGroup Id="SuperFormFiles">
            <ComponentRef Id="cmp5BB40DB822CAA7C5295227894A07502E" />
            <ComponentRef Id="cmpCFD331F5E0E471FC42A1334A1098E144" />
            <ComponentRef Id="cmp4614DD03D8974B7C1FC39E7B82F19574" />
            <ComponentRef Id="cmpDF166522884E2454382277128BD866EC" />
        ComponentGroup>
    Fragment>
    <Fragment>
        <DirectoryRef Id="INSTALLLOCATION">
            <Component Id="cmp5BB40DB822CAA7C5295227894A07502E" Guid="{117E3352-2F0C-4E19-AD96-03D354751B8D}">
                <File Id="filDCA561ABF8964292B6BC0D0726E8EFAD" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.exe" />
            Component>
            <Component Id="cmpCFD331F5E0E471FC42A1334A1098E144" Guid="{369A2347-97DD-45CA-A4D1-62BB706EA329}">
                <File Id="filA9BE65B2AB60F3CE41105364EDE33D27" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.pdb" />
            Component>
            <Component Id="cmp4614DD03D8974B7C1FC39E7B82F19574" Guid="{3443EBE2-168F-4380-BC41-26D71A0DB1C7}">
                <File Id="fil5102E75B91F3DAFA6F70DA57F4C126ED" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.vshost.exe" />
            Component>
            <Component Id="cmpDF166522884E2454382277128BD866EC" Guid="{0C0F3D18-56EB-41FE-B0BD-FD2C131572DB}">
                <File Id="filF7CA5083B4997E1DEC435554423E675C" KeyPath="yes" Source="$(env.SuperFormFilesDir)\SuperForm.MainApp.vshost.exe.manifest" />
            Component>
        DirectoryRef>
    Fragment>
Wix>

The $(env.SuperFormFilesDir) will be replaced at build time with the directory where the files to be installed are located. There is nothing too complicated about this. In the end it turns out that this sort of automation is great!

There are a few other ways that Heat.exe can compose the wxs file but this is the one I prefer. It just seems the clearest. Play with its options to see what can it do. It’s one awesome little tool.


WiX 3 tutorial by Mladen Prajdić navigation

  • WiX 3 Tutorial: Solution/Project structure and Dev resources
  • WiX 3 Tutorial: Understanding main wxs and wxi file
  • WiX 3 Tutorial: Generating file/directory fragments with Heat.exe 
  • WiX 3 Tutorial: Custom EULA License and MSI localization


Using Project References and Variables

The WiX project supports adding project references to other projects such as VB and C#. This ensures that build order dependencies are defined correctly within the solution. In addition, it generates a set of WiX preprocessor variables that can be referenced in WiX source files and preprocessor definitions which are passed to the compiler at build time.

To add a project reference to a WiX project:

  1. Right-click on the References node of the project in the Solution Explorer and choose Add Reference....
  2. In the Add Reference dialog, click on the Projects tab.
  3. Select the desired project(s) and click the Add button, and then press OK to dismiss the dialog.

Supported Project Reference Variables

Once a project reference is added, a list of project variables becomes avaliable to be referenced in the WiX source code. Project reference variables are useful when you do not want to have hard-coded values. For example, the $(var.MyProject.ProjectName) variable will query the correct project name at build time even if I change the name of the referenced project after the reference is added.

The following demonstrates how to use project reference variables in WiX source code:


The WiX project supports the following project reference variables:

Variable name

Example usage

Example value

var.ProjectName.Configuration

$(var.MyProject.Configuration)

Debug or Release

var.ProjectName.FullConfiguration

$(var.MyProject.FullConfiguration)

Debug|AnyCPU

var.ProjectName.Platform

$(var.MyProject.Platform)

AnyCPU, Win32, x64 or ia64

var.ProjectName.ProjectDir

$(var.MyProject.ProjectDir)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\

var.ProjectName.ProjectExt

$(var.MyProject.ProjectExt)

.csproj

var.ProjectName.ProjectFileName

$(var.MyProject.ProjectFileName)

MyProject.csproj

var.ProjectName.ProjectName

$(var.MyProject.ProjectName)

MyProject

var.ProjectName.ProjectPath

$(var.MyProject.ProjectPath)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\MyApp.csproj

var.ProjectName.TargetDir

$(var.MyProject.TargetDir)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\

var.ProjectName.TargetExt

$(var.MyProject.TargetExt)

.exe

var.ProjectName.TargetFileName

$(var.MyProject.TargetFileName)

MyProject.exe

var.ProjectName.TargetName

$(var.MyProject.TargetName)

MyProject

var.ProjectName.TargetPath

$(var.MyProject.TargetPath)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\MyProject.exe

var.ProjectName.Culture.TargetPath

$(var.MyProject.en-US.TargetPath)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MyProject\bin\Debug\en-US\MyProject.msm

var.SolutionDir

$(var.SolutionDir)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MySolution\

var.SolutionExt

$(var.SolutionExt)

.sln

var.SolutionFileName

$(var.SolutionFileName)

MySolution.sln

var.SolutionName

$(var.SolutionName)

MySolution

var.SolutionPath

$(var.SolutionPath)

C:\users\myusername\Documents\Visual Studio 2010\Projects\MySolution\MySolution.sln

Note: var.ProjectName.Culture.TargetPath is only available for projects that have multiple localized outputs (e.g. MSMs).



你可能感兴趣的:(ASP.NET)