Attempting to remove compression from an MSI causes its files to disappear - wix

I'm trying to build an MSP patch for a program that's currently deployed using an MSI package. I've been able to successfully generate a PCP file using the following (slightly-redacted) WiX code, based on an example from the WiX documentation.
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<PatchCreation
Id="D25F1136-2BEE-4A82-9236-7261067D4BDC"
CleanWorkingFolder="yes"
OutputPath="XYZ.pcp"
WholeFilesOnly="yes"
>
<PatchInformation />
<PatchMetadata
AllowRemoval="no"
Classification="Major Upgrade"
Description="XYZ Update"
DisplayName="XYZ Update"
ManufacturerName="XYZ"
MoreInfoURL="http://www.contoso.com/qr/XYZ.html"
TargetProductName="$(var.ProductName)"
/>
<Family Name="PrsnPtch">
<UpgradeImage Id="LatestImage" SourceFile="new_msi_dir\XYZ.msi">
<TargetImage Id="OldImage" Order="1" SourceFile="old_msi_dir\XYZ.msi" />
</UpgradeImage>
</Family>
<PatchSequence PatchFamily="XyzPatch" Supersede="yes" />
</PatchCreation>
</Wix>
and a build script set up to call msimsp.exe.
msimsp -s "%PCPDIR%\XYZ.pcp" -p "%PCPDIR%\XYZ.msp" -l "%PCPDIR%\patch.log"
Unfortunately, the above command fails with the following error message:
ERROR: UpgradedImages.MsiPath '[...]\XYZ.msi' is marked as having
compressed files (PID_WORDCOUNT property of Summary Information
stream). PatchWiz is unable to patch files compressed in a cabinet.
So I tried building an uncompressed version of the MSI by adding a CompressionLevel attribute to the MediaTemplate element in its WXS file.
<MediaTemplate EmbedCab="yes" CompressionLevel="none" />
For building the MSI itself, this is a success. The resulting file is 3.7 times the size of the uncompressed original, and 7-Zip shows the files therein as having a compression method of “None”. And I am able to successfully install the program on a VM.
The problem is that then I still get the “PatchWiz is unable to patch files compressed in a cabinet” error when running msimsp.
The culprit seems to be the Compressed attribute on the WXS Package element:
<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine"
Description="XYZ Installer"/>
So naturally, I tried changing Compressed="yes" to Compressed="no" (with no other changes to this WXS file). And this “works” as far as allowing msimsp to not give any errors.
But it introduces the serious problem that the MSI file is “empty”, its size having been reduced from about 3.6 MB to a mere 280 KB. Viewing the archive in 7-Zip shows that it does not contain any of the files that it's supposed to install. Viewing it in Orca shows a Media table with one row containing the values DiskId=1 and LastSequence=19, but nothing in the Cabinet column (the original MSI had an auto-generated “#cab1.cab” here).
Why would changing the Compression attribute make the CAB file go missing? How do I fix it?

This patch creation process does not work by using MSI files with embedded cabs or external cabs. It uses administrative images that you create using msiexec /a ..... This creates an MSI file together with all the external loose files in their appropriate directories. The patch creation process uses two of these admin images to generate the patch, the msp file, and it compares each corresponding file in each image to create the delta between the two products.
An administrative install is not an "install" - it's mainly just file extraction from the MSI into loose files in directories. This also marks the resultant MSI file as having "no files" in it - it's just the tables - so the patch creation process will complain if it finds this or anything indicating the files are in a cab. It needs separate loose files.
Once you have two administrative images the patch creation process (using a PCP file) runs against the two images comparing each file for changes and generating an actual patch, an msp file.
(Note that there is an IgnoreMissingSrcFiles setting in the PCP TargetImages table that allows missing files to be ignored while the patch is being created - it would make no sense to have this setting if the patch creation process used CAB files because all the files would always be there. )

Related

Wix toolset build optimization for repeatable builds

I don't know much about wix tools.
I have a project that needs to be packaged in many msi packages. The number of files is about 700. The difference between each msi package mainly in the architecture (x86, x64) and a small set of variable resources (no more than 5-10 files).
Full build takes more than one hour. Most of the time is taken by light.exe.
Is it possible to somehow cache the result of work in order to speed up this process, since each msi differs by a minimum set of files? Maybe I should organize the files in a special way to make this possible?
Computer and Disk: The obvious first: are you working of a fast disk on a good computer? Or are you pulling files from the network or writing to slow disks? 700 files is not that much. Are they very big? How many setups are you building? A good SSD or NVME disk should help - also some good CPU behind it.
Build Speed: I have this existing answer on build optimization: Speed up Build-Process of WiX-Installer - I would recommend you build a shared setup and install via a WiX Burn setup.exe bundle (Burn builds setup.exe executables that can install embedded MSI files or other setup-type files). A separate MSI that you build seldomly can work. Thoughts on setup cohesion and coupling.
WiX Help File: How To: Optimize build speed
Cab Cache & wixlibs: Please read this:
Reusing WIX components to speed up candle/light
What are .wixlibs and why would you use them?
Links:
Is there a way to speed up WiX builds?
How can I speed up MSI package install and uninstall?
You can define a single cabinet file for your shared files and individual cabinet files for unique sets of files. Use conditional compilation using defines to include only select cabinets in the MSI file.
<Media Id="1" Cabinet="Shared.cab" CompressionLevel="high" VolumeLabel="Shared binaries." EmbedCab="no" />
<Media Id="2" Cabinet="Set1.cab" CompressionLevel="high" VolumeLabel="Unique files 1" EmbedCab="no" />
<Media Id="3" Cabinet="Set2.cab" CompressionLevel="high" VolumeLabel="Unique files 2" EmbedCab="no" />
On the <File> element set the DiskId="" attribute to associate a single file with target cabinet.
Light should be able to use existing cabinets from cache when input files does not change between you different builds.

Patch creation fails (does not spot any differences) on build server

I have a automated Setup creation on our build server, and try to create a patch between the released version and the current build version.
Sadly i can't get it working.
This is my Patch.wxs:
<Patch AllowRemoval="no" Manufacturer="Company" DisplayName="Product Patch" Description="Patch" Classification="Update">
<Media Id="5000" Cabinet="RTM.cab">
<PatchBaseline Id="RTM"/>
</Media>
<PatchFamily Id="SampleFamily" Version="1.0.0.0" Supersede="yes">
<FeatureRef Id="ProductFeature"/>
</PatchFamily>
</Patch>
i use a postBuild to compile&link the patchfile:
"C:\Program Files (x86)\WiX Toolset v3.8\bin\candle.exe" $(ProjectDir)Patch.wxs -dDebug -dOutDir=$(TargetDir) -o Patch.wixobj
"C:\Program Files (x86)\WiX Toolset v3.8\bin\light.exe" $(TargetDir)Patch.wixobj -o $(TargetDir)Patch.wixmsp
Until here everything works fine i guess.
I create a transform using torch after a sucessfull build:
torch -p -xi release.wixpdb latestBuild.wixpdb -out diff.wixmst
The diff is created successfully.
Then pyro gives me an warning that no files are different:
pyro Patch.wixmsp -t RTM diff.wixmst -out patch.msp"
warning PYRO1079 : The cabinet 'RTM.cab' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.
The files are different if i install the msi files i get two different installations. but if i install the patch nothing changes. I played around with the PatchFamily but i can't get it working.
How can i get the Patch file including my changes?
The wix tools is seen to be failing to recognize file content change. To work properly do exactly as described in the WIX tutorial.
Here take special care to add version part in the source of the components, i.e 1.0 take component files from 1.0 source and say 2.0 takes source files from 2.0 root folder. This way file it is creating the patch as expected.
I found out what is causing this problem.
The Build server does not create a separate folder for each Build (just for the Drop, not for the Build itself) so at the time i create a patch both wixpdb files reference the same files of the build folder, thats why there are no differences found. I now changed the buildserver to create a administrative installation inside the drop folder and create a transform using the final msi files.
using the so created transform for pyro creates the msp as expected containing the changed files.

Windows Installer XML relative Path for Patch

I'm using the Microsoft Team Foundation Server to manage and Deploy Setups for my Applications.
My Setups are WiX-Setups with relative Paths.
p.e.
Components
<Component Id="Anwendung.exe" Directory="INSTALLLOCATION" Guid="*">
<File Id="Anwendung.exe" KeyPath="yes" Source="$(var.SourceFiles)\Anwendung.exe" />
</Component>
Variables.wxi:
<Include>
<?define SourceFiles = "..\OutputFiles"?>
</Include>
Setups are building correctly.
The Problem: I'm using Torch and Pyro to generate Patches for my applications.
I'm using a pure Wix Patch Project with manipulated build events and additional linker information
(Pre Build - Torch)
(Post Build - Pyro)
(Linker additional Parameters - output as wixmsp)
When I build my Patch, I'm getting 579Failures.
(1x)
Error 776 The command ""C:\Program Files (x86)\WiX Toolset v3.8\bin\pyro.exe" Patch.wixmsp -out Patch.msp -t AnwendungBaseline diff.wixmst" exited with code 103.
C:\Program Files (x86)\MSBuild\Microsoft\WiX\v3.x\wix2010.targets
and (578x)
Error 322 The system cannot find the file '..\SourceFiles\Anwendung.exe'. C:\Patch\Client\Upgrade\Setup\ComponentMain.wxs
I'm sure it's not resolving the paths from the sourcesetups correctly.
Could anybody help me? Rob Arnson , Rob Mensching, Heath Steward? Please :)
To get around issues like this, you can write a custom build activity for TFS to update variables in your WiX config file with fully qualified paths. For instance, you can create a build activity that takes in the path to a config file, the name of a variable, and value for that variable, and then write the new value to the config file. You can keep the config file with relative paths checked into source control, but then the build will update the config file to use the fully qualified path just for the build.
If you are unfamiliar with creating custom build activities, there is a great blog series on it here. The link is for TFS 2010 but the process is similar for TFS 2012.

Copy file using Burn

I am using WiX Burn to make my installer, i am bundling one exe and one msi.
And the exe needs an properties file at the time of install.
Is there a way to copy the file using burn, i tried Payload but it is not working.
Can i know the location throguh any Bundle variable where my file is copied.
Thanks
Ravi S
Make sure you are specifying the properties file as the payload for the exe and not for the bootstrapper. For example, in your bundle, your chain may look something like this:
<Chain>
<MsiPackage SourceFile="MyInstaller.msi" Id="MyInstaller" Cache="yes"/>
<ExePackage SourceFile="MyExe.exe" Id="MyExe" Cache="yes">
<Payload SourceFile="OtherFile.properties" Id="Properties"/>
</ExePackage>
</Chain>
Also, as a sanity check, which version of WiX are you using? If you are using an older build (such as RC0), you could try updating to the latest weekly build.
Update:
In WiX 3.6 it does not appear that you can get the absolute path of a payload file. There are two bugs/feature request open right now regarding the issue that are deferred to WiX 3.7:
Add burn variable to cache path - ID: 3557446
Change working folder to the cache folder - ID: 3538846
One workaround would be to use burn to write your own bootstrapper application and then programmatically determine the working directory and set the appropriate parameters, but that would be a lot of work for this one issue.

Can a .msi file install itself (presumably via a Custom Action)?

I wand to construct an MSI which, in its installation process, will deploy itself along with its contained Files/Components, to the TargetDir.
So MyApp.msi contains MyApp.exe and MyAppBootstrapperEmpty.exe (with no resources) in its File Table.
The user launches a MyAppBootstrapperPackaged.exe (containing MyApp.msi as a resource, obtained from the internet somewhere, or email or otherwise). MyAppBootStrapperPackaged.exe extracts MyApp.msi to a temp folder and executes it via msiexec.exe.
After the msiexec.exe process completes, I want MyApp.msi, MyBootstrapperEmpty.exe (AND MyApp.exe in %ProgramFiles%\MyApp folder so MyApp.exe can be assured access to MyApp.msi when it runs (for creating the below-mentioned packaged content).
MyAppBootstrapper*.exe could try and copy MyApp.msi to %ProgramFiles%\MyApp folder, but would need elevation to do so, and would not allow for its removal via Windows Installer uninstall process (from Add/Remove Programs or otherwise), which should be preserved.
Obviously (I think it's obvious - am I wrong?) I can't include the MSI as a file in my Media/CAB (chicken and egg scenario), so I believe it would have to be done via a Custom Action before the install process, adding the original MSI to the MSI DB's Media/CAB and the appropriate entry in the File table on the fly. Can this be done and if so how?
Think of a content distribution model where content files are only ever to be distributed together with the App. Content is produced by the end user via the App at run time, and packaged into a distributable EXE which includes both the App and the content.
MyApp's installer must remain an MSI, but may be executed by a Bootstrapper EXE. The installed MyApp.exe must have access to both MyApp.msi and EXE is to be "assembled" at runtime by the App from a base (empty) MyAppBootstrapper.exe, which is also installed by the MSI, and the content created by the end-user. The EXE's resource MSI must be the same as that used to install the App which is doing the runtime packaging.
WIX is not to be installed with MyApp.
There can be no network dependencies at run-/packaging- time (i.e. can't do the packaging via a Webservice - must be done locally).
I am familiar with (and using) Custom Actions (managed and unmanaged, via DTF and otherwise).
Add an uncompressed medium to your wxs like this:
<Media Id='2'/>
And then create a component with a File element like this:
<File Source='/path/to/myinstaller.msi' Compressed='no' DiskId='2' />
This will make the installer look for a file called "myinstaller.msi" on the installation medium, in the same folder as the msi that is being installed. The source path above should point to a dummy file, it is only there to appease wix.
Edit: The following sample test.wxs demonstrates that it works. It produces a test.msi file which installs itself to c:\program files\test. Note that you need to put a dummy test.msi file in the same folder as text.wxs to appease wix.
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Name='ProductName'
Id='*'
Language='1033'
Version='0.0.1'
Manufacturer='ManufacturerName' >
<Package
Keywords='Installer'
Description='Installer which installs itself'
Manufacturer='ManufactererName'
InstallerVersion='100'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252'/>
<Media Id='1' Cabinet='test.cab' EmbedCab='yes'/>
<Media Id='2' />
<Directory Id='TARGETDIR' Name="SourceDir">
<Directory Id='ProgramFilesFolder'>
<Directory Id='TestFolder' Name='Test' >
<Component Id="InstallMyself">
<File Source="./test.msi" Compressed="no" DiskId="2" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Complete'
Display='expand'
Level='1'
Title='Copy msi file to program files folder'
Description='Test'>
<ComponentRef Id="InstallMyself" />
</Feature>
</Product>
</Wix>
Having one .MSI package launch another .MSI package from "within" itself is called a nested install, and it's bad juju (see Rule 20). Windows Installer has some global data that it uses to manage the current install, and it doesn't handle well multiple installs at the same time. For the same reason, if you start one install and then try to start another while the first is still in progress, you'll usually see a pop-up to the effect of "another install in progress, please wait until it's done".
You can have a program, usually called a bootstrapper (I think that's what you're referring to) which is itself not an install package, but which contains an install package (such as an .MSI or an .EXE) as a resource, possibly compressed. The action of the bootstrapper program is to extract/expand the resource to a file, commonly in a %TEMP% directory, then either launch the extracted .EXE or run MSIEXEC on the extracted .MSI. The bootstrapper can contain multiple resources and extract+install them one by one, if you need to install prerequisites before the main package. Or you can ship multiple packages as separate files, and have the bootstrapper execute/install them directly from the distribution media one by one, or copy them down to the target machine and run the series of install from there, or...
WiX itself does not get installed, no. It's a tool with which .MSI packages can be built. The WiX project has on its wishlist a generic bootstrapper program, but it hasn't been implemented yet. There are other bootstrappers available, e.g. this one.
You won't need a custom action -- in fact, since the bootstrapper isn't itself a Windows Installer installation package, "custom action" has no meaning to it. And, if you're familiar enough with CAs to know about managed/unmanaged/DTF, then you know enough to avoid custom actions whenever you can. (grin)
I think it's much easier for your bootstrapper to extract MSI file to some predefined location rather than to the temp folder. For example, to C:\Documents and Settings\All Users\Application Data\My Company\My Product Install Cache. After installation finishes bootstrapper would leave MSI file sitting there. If at some stage user decides to reinstall your product Windows Installer will be able to locate source MSI file.
Also, add path to this file to RemoveFile table so that it gets deleted on uninstall. You can use RemoveFile element in WiX for that.
So if I understand, then I think I would have the app create a transform (MST) that has the content files and apply that to the base MSI. I'm still not convinced that I understand though. :)
I'd configure the MSI cache path to a known location.
Then at runtime if you need to "edit" the MSI use VBScript or similar.
But still, I ask WHY!?!
I am also working on a way to deploy multiple MSI files. I have a bootstrapper.exe program that bundles the MSI files and runs them one at a time. This solves my problem for most cases.
The case it does not solve is GPO (Global Policy Object) distribution of the install. GPO requires a dot-msi file to run an install.
To do this here's what I did which almost solved the problem (but not quite). I put the dot-msi files in the file table of an installer and put my bootstrapper in the binary table and run it from a custom action inserted after InstallFinalize in the InstallExecuteSequence. Of course the bootstrapper won't be able to run other MSI's because the top level MSI holds the _MSIExecute mutex.
It was pretty easy to get a little further. I made the bootstrapper return control to the top level installer and continute. And then I added a WaitForSingleObject call to wait for the top level install to finish, and the bootstrapper can then continue to finish the install.
My problem is that the GPO installation happens at boot time and the top level install completes before the sub installers are done and GPO reboots the machine.
The toplevel install also returns a success status when the install may actually fail later on.
I'm still looking for a way to block the top level install from completing until after the bootstrapper completes.