From MSI to WiX, Part 9 - Patching
The main page for the series is here.
Introduction
I'll show a very simple sample of small update.
Directory structure for the sample
The directory structure I am using for this sample is:
Patching
SmallUpdate
RTM
QFE
Include
Patch
Project
TestApp
TestApp
TestLib
TestApp2
TestApp
TestLib
SmallUpdate folder contains the WiX source code and batch files for building and installing the installer databases. RTM contains the first version of the product and QFE - updated version. Include folder contains all files shared by both RTM and QFE. Patch folder contains the patch building WiX source.
Test application source and compiled code is stored under the Project folder. TestApp is the original version, and TestApp2 - is updated version.
Source code for original version of the product (RTM)
Our product will consist of one Windows Console application (TestApp folder) and one dll (TestLib folder).
Here is the code for the console application:
Program.cs:
using System;
using System.Collections.Generic;
using System.Text;
using TestLib;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass();
test.WriteLine();
}
}
}
AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestApp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestApp")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("10578ea5-d18c-41b1-bbf6-ccc9d4d695f2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
Source code for the dll:
TestClass.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace TestLib
{
public class TestClass
{
private string greeting;
public TestClass()
{
greeting = "Hello, World!";
}
public TestClass(string greeting)
{
this.greeting = greeting;
}
public void WriteLine()
{
Console.WriteLine(greeting);
}
}
}
AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestLib")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestLib")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5b0112f9-befc-4957-99f8-8ef51d20d3cf")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
Our TestClass from the dll has two constructors: default constructor sets the value of the greeting varibale to it's default value, but users are free to use the second constructor to set the greeting message to whatever they like.
Console application is using the default constructor, so the message we will see on the screen is the default message of the TestClass class.
WiX shared code
Definitions.wxi:
<Include>
<!-- Package all files in one cab file -->
<Media Id="1" Cabinet="$(var.SkuName).cab" EmbedCab="yes" />
<!-- Common conditions -->
<!-- .Net Framework 2.0 is required -->
<Condition Message='.NET Framework 2.0 must be installed prior to installation of this product.'>
Installed OR MsiNetAssemblySupport >= "2.0.50727"
</Condition>
</Include>
Here I have the declaration of the Media element and one Launch Condition demanding that .NET Framework 2.0 must be installed on the target system.
UpgradeData.wxi:
<Include>
<!-- Never change the UpgradeCode -->
<?define UpgradeCode="{3485E6A2-A1F3-4329-8BB5-ED8FFCF283D4}"?>
<?define Manufacturer="Microsoft"?>
<?define ProductCode="{5C32A3BD-3BA3-43AF-951F-1077E84B00DC}"?>
<?define PackageCode="{????????-????-????-????-????????????}"?>
<?define ProductVersion="1.0.0" ?>
</Include>
Here are the values for properties related to upgrade.
en-us.wxl:
<?xml version="1.0" encoding="utf-8" ?>
<WixLocalization>
<String Id="LangId">1033</String>
<String Id="Codepage">1252</String>
<String Id="PackageDescription">TestApp application</String>
<String Id="ProductName">TestApp</String>
<String Id="Comments">TestApp application sample to demonstrate the QFE patching</String>
</WixLocalization>
Here I have localized values for installation package properties.
WiX source code for the installer for original version of our product
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='https://schemas.microsoft.com/wix/2003/01/wi'>
<?define SkuName = "TestApp"?>
<!-- Path variables -->
<?define APPROOT = "D:\MyLearning\WiX\Patching"?>
<?define PROJECT = "$(var.APPROOT)\Project\TestApp\TestApp\bin\Debug"?>
<?include ..\Include\UpgradeData.wxi ?>
<Product Id='$(var.ProductCode)'
Name='$(loc.ProductName)'
Language='$(loc.LangId)'
Version='$(var.ProductVersion)'
Codepage='1252'
Manufacturer='$(var.Manufacturer)'
UpgradeCode='$(var.UpgradeCode)'>
<Package Id='$(var.PackageCode)'
Description="$(loc.PackageDescription)"
Comments='$(loc.Comments)'
Manufacturer='$(var.Manufacturer)'
InstallerVersion='200'
Languages='$(loc.LangId)'
SummaryCodepage='$(loc.Codepage)'
Compressed='no'
AdminImage='no'
Platforms='Intel'
ReadOnly='yes'
ShortNames='no'
Keywords='Installer,MSI,Database' />
<?include ..\Include\Definitions.wxi ?>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='LocalAppDataFolder'>
<Directory Id='INSTALLDIR' Name='TestApp'>
<Component Id='$(var.SkuName)' Guid='{835A4136-B01E-4F8B-8EA7-5D6F69B07A83}'>
<File Id='TestAppExe' DiskId='1' KeyPath='yes' Checksum='yes' Vital='yes'
Name='TestApp.exe'
Assembly='.net' AssemblyManifest='TestAppExe' AssemblyApplication='TestAppExe'
Source='$(var.PROJECT)\TestApp.exe' />
</Component>
<Component Id='TestLibDll_Component' Guid='{5BC55186-170E-475C-B77A-D80581FC88EC}'>
<File Id='TestLibDll' Name='TestLib.dll' DiskId='1' KeyPath='yes' Vital='yes'
Assembly='.net' AssemblyManifest='TestLibDll' AssemblyApplication='TestLibDll'
Source='$(var.PROJECT)\TestLib.dll' />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id='Complete' Level='1'>
<ComponentRef Id='$(var.SkuName)' />
<ComponentRef Id='TestLibDll_Component' />
</Feature>
</Product>
</Wix>
There is nothing special in here except one attribute:
<Package Id='$(var.PackageCode)' ... Compressed='no' ...
In order to create a patch we must have uncompressed source. One way of getting it is to create an administartive install and create a patch out of an administrative image. For this sample I am setting the Compressed attribute to no, which means that by default, none of the files will be embedded into the installer database. That can be overridden on the file by file basis by adding Compressed attribute to the File element, but I am not going to do that in this sample.
Here are commands to build the installer database:
d:\Wix\candle.exe RTM.wxs
d:\Wix\light.exe -out TestApp.msi RTM.wixobj -loc ..\Include\en-us.wxl
Updated application source code (QFE)
Program.cs:
using System;
using System.Collections.Generic;
using System.Text;
using TestLib;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass("QFE version");
test.WriteLine();
}
}
}
Notice that now I am using non-default constructor providing my custom greetings message.
AssemblyInfo.cs:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestApp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestApp")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("10578ea5-d18c-41b1-bbf6-ccc9d4d695f2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
Notice that both AssemblyVersion and AssemblyFileVersion attributes have changed their values from "1.0.0.0" to "1.0.1.0".
Thare are no changes in the dll source code.
WiX source code for the installer for updated version of our product
There are no changes to the original WiX source code except this one:
<?define PROJECT = "$(var.APPROOT)\Project\TestApp2\TestApp\bin\Debug"?>
That changes the path to the updated version.
Because this is a small update, the only property we need to change is the Revision Number Summary property (or, PackageCode, or, in WiX, the Id attribute of the Package element).
Now we have both original (RTM) and updated (QFE) versions of installer databases built. It is time to create a patch creation script.
Patch creation script (Patch.wxs)
Here is the source of Patch.wxs. This file is located in the Patch folder.
<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='https://schemas.microsoft.com/wix/2003/01/wi'>
<?define SkuName = "TestApp"?>
<!-- Path variables -->
<?define APPROOT = "D:\MyLearning\WiX\Patching\SmallUpdate"?>
<?define RTM = "$(var.APPROOT)\RTM"?>
<?define QFE = "$(var.APPROOT)\QFE"?>
<?include ..\Include\UpgradeData.wxi ?>
<PatchCreation Id='{F8D2A922-EFE7-4BBF-8942-73A487453C2F}'
AllowMajorVersionMismatches='no'
AllowProductCodeMismatches='no'
CleanWorkingFolder='no'
WholeFilesOnly='no'
Codepage='1252' >
<PatchInformation Description="TestApp 1.0.0 Patch"
Keywords='Installer'
Comments='testApp comments'
Manufacturer='$(var.Manufacturer)'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252' />
<PatchMetadata Description="TestApp 1.0.0 Patch"
DisplayName='TestApp 1.0.0 Patch'
TargetProductName='TestApp 1.0.0'
ManufacturerName='$(var.Manufacturer)'
MoreInfoURL='www.acme.com'
Classification='Hotfix'
AllowRemoval='yes' />
<Family Name='Patch101' DiskId='2' MediaSrcProp='PatchSrcPropName' SequenceStart='1000'>
<UpgradeImage Id='PatchQFE' SourceFile='$(var.QFE)\TestApp.msi'>
<TargetImage Id='PatchRTM' Order='1' IgnoreMissingFiles='no' SourceFile='$(var.RTM)\TestApp.msi' />
</UpgradeImage>
</Family>
<TargetProductCode Id='$(var.ProductCode)' />
</PatchCreation>
</Wix>
Use the following commands to build the Patch.msp:
d:\wix\candle.exe Patch.wxs
d:\wix\light.exe Patch.wixobj
Msimsp.exe -s Patch.pcp -p Patch.msp -l Patch.log
Now, install the original version of the product. Run the installed application. On my computer it is installed in C:\Users\alexshev\AppData\Local\TestApp folder. You should see the "Hello, World!" mesage.
Install the patch by running this command from the Patch folder:
msiexec /p Patch.msp REINSTALL=ALL REINSTALLMODE=omus
Run TestApp.exe again. This time it should print "QFE version" message.
Open Add/Remove Programs in the Control Panel applet. Turn on the view of the installed updates for the installed products. Find the TestApp application in the list of installed applications. You should see one update named "TestApp 1.0.0 Patch". Right-click on it and select "Uninstall". Continue with uninstallation. Run TestApp.exe again. You will see "Hello, World!" message again.
Anonymous
August 06, 2008
Alex, if you can cover also the new Wix patching that would be just great!Anonymous
June 24, 2009
Alex I am trying to produce a patch file. I created the patch file and when i run the Error.msi it shows be file missing errors which i hav'nt encountered before. I have set Compressed='no' under Package. I am also using Mergemodule in this package. Does the Merge Module support patch works? I hav'nt encountered any kind of file missing errors earlier, the only difference is I have set the Compressed='no' under Package. Does this change the behaviour of the Installer Package? If so then what about the vital files which are for the program. Where should we include those? Any suggestions?Anonymous
September 10, 2009
How do I add a custom action that is part of the upgrade MSI to the patch??Anonymous
January 24, 2011
Giving a package ID results in the following warning. Is it safe to ignore it? Warning 1 The Package/@Id attribute has been set. Setting this attribute will allow nonidentical .msi files to have the same package code. This may be a problem because the package code is the primary identifier used by the installer to search for and validate the correct package for a given installation. If a package is changed without changing the package code, the installer may not use the newer package if both are still accessible to the installer. Please remove the Id attribute in order to automatically generate a new package code for each new .msi file. C:TFSMainRxIdentifierSetupProduct.wxs 10 1 SetupInspectRxBinariesOnlyAnonymous
April 18, 2011
I created a sample patch be following the above steps and its work fine. I've created the patch file(.msp) for my product. But after installing the patch, the changes are not reflected. Using ORCA(Open MSI file and the Transform->View Patch), i can able to see the changed files, directories. Please help me to find out the reason.