Wix Cabinet Caching Not Working - wix

I can't seem to get wix cabinet caching to work.
I have
<PropertyGroup>
<CabinetCreationThreadCount>3</CabinetCreationThreadCount>
<CabinetCachePath>cabs</CabinetCachePath>
<ReuseCabinetCache>True</ReuseCabinetCache>
</PropertyGroup>
in the wixproj.
<Media Id="1" Cabinet="contents.cab" EmbedCab="yes" CompressionLevel="mszip"/>
<Media Id="2" Cabinet="static.cab" EmbedCab="yes" CompressionLevel="mszip"/>
in the wxs
and a component that I know 100% never ever changes marked with
<Component DiskId="2" ...
I see the cab files generated in the cabs directory, but each time I build, I see the modified time of the cab file change, which suggests that it's regenerated the cabinet instead of reusing the one from the cache.
Using Wix 3.6
How can I get this working or debug the problem further?

Are you building or rebuilding (i.e. /t:build or /t:rebuild)? The WiX import will remove generated files when rebuilding. It's also possible that this is happening even if you are simply building. Generate a verbose log by passing /v:diag to MSBuild but be sure to redirect to a file like so:
msbuild /t:build /v:diag
If that does not show the CAB file(s) being deleted, I recommend checking out http://wixtoolset.org for their support channel.

Related

Wix Installer Going to Wrong Path on Command Line with Admin Privilege

I built a simple installer in Wix which will place a couple of data files in a specific folder in a preexisting product installation so that the user doesn't need to know anything about the product's installation in order to update their data files. The product stores its installation path in an environment variable (ENVVAR) which I'm using here to calculate the path of its NewData subfolder.
When I double-click the .msi or run it from the command line (msiexec /i filename.msi) it works perfectly and the files show up in C:\ProductPath\NewData. However, if it's installed with elevated privileges (msiexec /a filename.msi) the files go to the root of D:\ (which isn't even the same drive the product is installed on.)
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<?include InstallVariables.wxi ?>
<Product Id="*"
Name="Product Name"
Manufacturer="My Company"
Version="$(var.Version)"
UpgradeCode="guidgoeshere"
Language="1033">
<Package Description="Description $(var.Version)" Comments="Install package for my product."
InstallerVersion="300" Compressed="yes" InstallScope="perMachine"/>
<Media Id="1" Cabinet="Cabname.cab" EmbedCab="yes" CompressionLevel="high"/>
<SetDirectory Id="PATHMAP" Value="[%ENVVAR]\NewData" Sequence="first" />
<!-- Describe the folder layout here. -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="PATHMAP" FileSource="..\New Files">
<Component Id="File1" Guid="guidgoeshere">
<RemoveFile Id="Remove_File1File" Name="$(var.File1Pattern)" On="both" />
<File Id="File1File" Name="$(var.File1)" Vital="yes" KeyPath="yes" />
</Component>
<Component Id="File2" Guid="guidgoeshere">
<RemoveFile Id="Remove_File2File" Name="$(var.File2Pattern)" On="both" />
<File Id="File2File" Name="$(var.File2)" Vital="yes" KeyPath="yes" />
</Component>
</Directory>
</Directory>
<Feature Id="FeatureId" Title="New Files for a Feature" Level="1" >
<ComponentRef Id="File1"/>
<ComponentRef Id="File2"/>
</Feature>
</Product>
</Wix>
Note that the file removal in the components is intentional; if there is an existing version of either file (which may have a slightly different file name -- not my choice) I want to remove and replace it. The patterns used to do so are in the include file and are working properly.
The command line: msiexec.exe /a filename.msi will not trigger installation with elevated privileges, but rather an administrative installation. Follow the link for a description - it is important that you do for a complete description. Essentially an administrative installation is just an extraction of embedded files in the MSI to make a network installation image from where people can run a regular installation of the MSI (better explained in the linked answer above) - administrative installations don't install anything at all - it is a mere extraction.
You should be able to control the output directory of the administrative installation by providing a TARGETDIR like this: msiexec.exe /a filename.msi TARGETDIR=C:\MyOutputFolder\. Your MSI is probably lacking a basic GUI to show the administrative installation's dialog sequence - which makes the extraction happen without any parameters specified (hence you output to the largest drive on the box by default). You might want to consider linking a standard dialog set such as <UIRef Id="WixUI_Mondo" /> for your MSI. You can see a step-by-step description of how to do this here: WiX installer msi not installing the Winform app created with Visual Studio 2017. This will give your setup basic, standard GUI for both regular installation and administrative installation. Very useful I think - you should also set your own license agreement - I have updated the linked answer to include that.
I think this is the end of the answer for you. Installing with /a isn't installation with elevated rights - essentially - it is just an extraction of files. But do link in that default GUI to make your MSI more standard and better overall.
A couple of comments on the environment variable approach. I have never stored anything like that in environment variables. I usually just write to my own location in HKLM and read back from there either via a custom action or using MSI's built in search feature (preferably the latter - it is much better to rely on built-in MSI features. I am a little sloppy with read-only custom actions at times, but very much against read-write custom actions. You can see why here: Why is it a good idea to limit the use of custom actions in my WiX / MSI setups? - a digression I guess). WiX can easily define these searches and set the search result to your property: Define Searches Using Variables.
Maybe a quick link to "The WiX toolset's "Remember Property" pattern" by Rob Mensching (WiX creator). This is quite old now, there may be a new, smarter way to do this that I am not aware of yet.
If I were you, I would rather download these updated files from the network rather than deploy them like this into a "data folder" using Windows Installer. Deployment of user data files has always been problematic with MSI with its complex file overwrite rules and "quirks". Though perhaps not entirely related to your use case, here is a description of other approaches you can use to deploy data files for your application - perhaps in a more reliable fashion: Create folder and file on Current user profile, from Admin Profile. Maybe have a quick skim at least.
There are two types of environment variables: user environment variables (set for each user) and system environment variables (set for everyone).
By default, a child process inherits the environment variables of its parent process. Programs started by the command processor inherit the command processor's environment variables.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682653(v=vs.85).aspx
Probably, you can check machine users and if product is installed for all users point to system environment variable in Local Machine.

Include all Files in Bin folder in Wix installer

I'm new in Wix, I succefully create an MSI installer for my project, but my Bin folder have a lot of DLL's files with EXE main file, I want to include all these files with the installer
I found THIS solution, that seems right but unfortunately I can not accomplish this solution in my Wix file, Here's my Wix file:
<Product Id="*" Name="Setup"
Language="1033" Version="1.0.1.0"
Manufacturer="ORDER MS"
UpgradeCode="a4f0a0d0-ae64-4f62-9bb3-efa7e75072e0">
<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine" />
<MajorUpgrade Schedule="afterInstallInitialize"
DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="Setup" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<ComponentRef Id="ApplicationShortcutDesktop" />
<ComponentRef Id="ApplicationShortcut" />
</Feature>
<Icon Id="Icon.exe" SourceFile="$(sys.CURRENTDIR)\icon.ico"/>
<Property Id="ARPPRODUCTICON" Value="icon.exe" />
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent">
<File Source="$(var.Order.TargetPath)" />
</Component>
<Component Guid="A7C42303-1D77-4C70-8D5C-0FD0F9158EB4" Id="CopyComponent">
<CopyFile Id="SomeId"
SourceProperty="SOURCEDIRECTORY"
DestinationDirectory="CopyTestDir" SourceName="*" />
</Component>
</ComponentGroup>
I get this Error:
Error 1 ICE18: KeyPath for Component: 'CopyComponent' is Directory: 'INSTALLFOLDER'. The Directory/Component pair must be listed in the CreateFolders table.
This solution works with WIX 3.11.
To harvest an entire directory, you can use Heat from the WIX toolset. With Heat we can automatically include all files from a given source directory on every build. To do that we first need to edit the Setup.wixproj:
Define the Harvestpath for Heat:
<PropertyGroup>
<DefineConstants>HarvestPath=...\Deploy</DefineConstants>
</PropertyGroup>
Heat will create a .wxs file. So we need to add this file to the compile ItemGroup:
<ItemGroup>
<Compile Include="Product.wxs" /> <!-- This will be your default one -->
<Compile Include="HeatGeneratedFileList.wxs" /> <!-- This is the Heat created one -->
</ItemGroup>
Then execute Heat in the BeforeBuild build target:
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\Deploy"
PreprocessorVariable="var.HarvestPath"
OutputFile="HeatGeneratedFileList.wxs"
ComponentGroupName="HeatGenerated"
DirectoryRefId="INSTALLFOLDER"
AutogenerateGuids="true"
ToolPath="$(WixToolPath)"
SuppressFragments="true"
SuppressRegistry="true"
SuppressRootDirectory="true" />
</Target>
This will generate the HeatGeneratedFileList.wxs every time the WIX installer is built. The directory ..\Deploy has to be set to the directory of the files to include.
The only thing we have to do to include these files in our installer is to edit the main .wxs file (like Product.wxs in this example). Heat will create a ComponentGroup with the given name from above. This component needs to be referenced in the Feature section of the Product.wxs:
<Feature Id="ProductFeature" Title="DiBA Tool" Level="1">
<...>
<ComponentGroupRef Id="HeatGenerated" />
</Feature>
I think you've gotten into a bit of a muddle, if you don't mind me saying so.
CopyFile will copy a file will copy a file from one place on a target machine (the machine where the install is being installed, not your development computer) to another folder on the same machine. I don't think this is what you want.
As Brian suggested you can use Heat to scan a folder and generate some code for you. You can use that in one of two ways:
As a development aid
Run the tool with this kind of command:
heat dir ".\My Files" -gg -sfrag -template:fragment -out directory.wxs
Then, take directory.wxs and use it as source code.
In the build pipeline
You can use the Heat tool in the build pipeline, so that compiling the install will generate the code.
Contrary to Brian's suggestion, if you are using MSBuild I would suggest the HarvestDirectory target. Here's what I have where I am doing something similar:
<HarvestDirectory Include="$(MyHarvestDirectory)">
<InProject>false</InProject>
<PreprocessorVariable>var.MyHarvestDirectory</PreprocessorVariable>
<ComponentGroupName>MyComponentGroup</ComponentGroupName>
<DirectoryRefID>MY_DIRECTORY_ID</DirectoryRefID>
</HarvestDirectory>
This will feed an item into the HarvestDirectory target and make sure it's all handled in the correct way. This code just goes into an ItemGroup in your .wixproj. (If you're not sure how to tweak your project file, check out this video)
Of course, this assumes you are using MSBuild (or Visual Studio, because that uses MSBuild).
Caveat
Having said all this, if the main issue is simply that there are lots of files, I would simply say knuckle down and just define the list. Use the Heat tool as a scaffold if you like, but there's no substitute for just learning the language and working with it. Trying to do things with auto generated code can introduce subtle issues, and it's always simpler to work with a static list.
I do something similar to what you require here during my installation. I need to copy the contents of a folder with 1000+ files in it (the help files).
What I did to solve this is the following:
In the Installer.wixproj I defined the following:
<Target Name="BeforeBuild" >
<Exec Command=""$(WixToolPath)Heat.exe" dir "$(MSBuildThisFileDirectory)\$(Configuration)\bin" -ag -cg BinDir -dr BIN -template fragment -sreg -sfrag -srd -var var.BinDir -o "$(MSBuildThisFileDirectory)\Components\Bin.wxs"" Condition="!Exists('$(MSBuildThisFileDirectory)\Components\Bin.wxs')" />
</Target>
And this will run heat on the $(Configuration)\bin\ dir and generate a wxs file including ALL the files in the bin dir.
This way if you add or delete any binaries in your bin dir it will automatically get picked up when you rebuild your installer (if the Bin.wxs file doesn't exist).
Now you need to make sure you define the variable "BinDir" for wix which points to the bin dir on the build machine. You also need to add the Bin.wxs file to your wixproj as a link (when adding existing file there's a tiny arrow drop down on "Add". Click that and select "Add as link".)
I think there's an actual heat target somewhere in wix but I haven't looked through that enough to know how to use it yet.
As an alternative to the other answers:
There is a Visual Studio extension called Wax (GitHub, Visual Studio Marketplace):
It provides an alternative way to handle all the required files with GUI.
It may be not as sophisticated as command-line tools like heat, but it is much simpler and can be a nice and friendly tool in the beginning, when you just start learning WiX - and want a quickstart.

WiX occasionally throwing cab file error in embedded cab project

I have a WiX installer which, every now and then, will produce an install package which will throw the error
"The cabinet file 'cab1.cab' required for this installation is corrupt and cannot be used. This could indicate a network error, an error reading from the CD-ROM, or a problem with this package"
I have EmbedCab="yes" in my markup
<MediaTemplate EmbedCab="yes" />
Is there a way to prevent this from happening?
I am using WiX Toolset v3.9.1208.0
Thank you

TeamCity building MSP files

For background: I've got quite a nice TeamCity setup; containing a ci build and a release build which uses WiX to build my installers and patch all the version numbers. When I do a new release build, I'd like to automatically create MSP patches against a previous set of installers. I'm thinking either tagged RTM in TeamCity, or as a list of version numbers.
The approach I'm leaning towards is creating a separate config and getting the msi artifacts of all the previous builds that fit the criteria (tag or version number). Tag would seem a lot neater, but I can't see anything in the documentation about how you use it?
I've got a script to build the MSP patch, but it relies on a PCP file which needs to be edited in ORCA to describe the patch.
In terms of editing the PCP, is there anything else I can use other than the ORCA to edit? I've been looking at moving to the WiX method here: http://wix.sourceforge.net/manual-wix3/patch_building.htm which looks promising.
Does anyone know if you can access artifacts in TeamCity by Tag in the same or another build?
Does anyone have any other insights into automatically building/chaining MSP patch files in TeamCity?
You can build the .PCP file using the PatchCreation element in the WiX toolset. That will probably give you the necessary flexiblity necessary to create the customized .PCP files.
Sorry, don't use TeamCity.
Sorry, don't use TeamCity. :)
To add to Rob's answer:
#2. TeamCity can retrieve items by tag:
http://servername:8080/httpAuth/app/rest/buildTypes/id:bt13/builds?status=SUCCESS&tag=RTM
#3. I used the PatchCreation element (Rob suggested above) in the WiX toolset and he's right its flexible enough for this. Here is an outline of what I've built, it all seems to work quite well in testing,
The teamcity project has a number of build parameters, they are:
New version number - default as changeme, so if its not been changed it breaks the build.
Old version number - as above
New build repo - this is the buildtypeid, look at querystring for your project and it will have buildTypeId=btXX. The XX is the number that should be supplied here.
Old build repo - as above
The teamcity project has the following steps:
MSBuild runner to run build.msbuild (see below)
Run Candle on the patch.wxs to create a patch.wixobj file
Run Light on patch.wixobj to create a patch.pcp
Unpack new version (command: msiexec /q /a new.msi) -
Unpack old version (command: msiexec /q /a old.msi) - choose a different working dir
Create patch (command: msimsp -s patch.pcp p hotfix-%system.msiOldVersion%-%system.msiNewVersion%.msp -l patch.log
MSBuild to Create patch.pcp
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--<Import Project="references\MSBuild.Community.Tasks.Targets"/>-->
<UsingTask AssemblyFile="references\MSBuild.Community.Tasks.dll" TaskName="WebDownload"/>
<UsingTask AssemblyFile="references\MSBuild.Community.Tasks.dll" TaskName="TemplateFile"/>
<Target Name="Build">
<!-- preconditions for build -->
<Error Condition="'$(msiOldVersion)' == 'changeme'" Text="Use run custom build, setting the client version of the msi"/>
<Error Condition="'$(msiOldVersion)' == ''" Text="Use run custom build, setting the client version of the msi"/>
<Error Condition="'$(msiNewVersion)' == 'changeme'" Text="Use run custom build, setting the new version of the msi"/>
<Error Condition="'$(msiNewVersion)' == ''" Text="Use run custom build, setting the new version of the msi"/>
<Message Text="Old Version: $(msiOldVersion)"/>
<Message Text="New version: $(msiNewVersion)"/>
<!-- download files from teamcity... -->
<WebDownload FileUri="http://server:8080/httpAuth/repository/download/bt$(msiOldRepo)/trunk/Path/bin/Release/en-us/installer-v-v.$(msiOldVersion).msi" UserName="download" Password="abcdefgh" FileName="downloads/oldversion.msi" />
<WebDownload FileUri="http://server:8080/httpAuth/repository/download/bt$(msiNewRepo)/trunk/Path/bin/Release/en-us/installer-v.$(msiNewVersion).msi" UserName="download" Password="abcdefgh" FileName="downloads/newversion.msi" />
<!-- fill in blanks in patch.wxs -->
<ItemGroup>
<Tokens Include="oldVersion">
<ReplacementValue>$(msiOldVersion)</ReplacementValue>
</Tokens>
<Tokens Include="newVersion">
<ReplacementValue>$(msiNewVersion)</ReplacementValue>
</Tokens>
</ItemGroup>
<TemplateFile Template="template.wxs" OutputFileName="patch.wxs" Tokens="#(Tokens)"/>
</Target>
Template.wxs used by MSBuild script
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<PatchCreation
Id="deadbeef-dead-beef-dead-beefdeadbeef"
CleanWorkingFolder="no"
OutputPath="patch.pcp"
WholeFilesOnly="no">
<PatchInformation
Description="Small Update Patch"
Comments="Small Update Patch"
Manufacturer="Your Manufacturer"/>
<PatchMetadata
AllowRemoval="yes"
Description="Hotfix"
ManufacturerName="Your Manufacturer"
MoreInfoURL="http://yourwebsite.com"
TargetProductName="Your Product Name"
Classification="Hotfix"
DisplayName="Hotfix - TBC"/>
<Family DiskId="5000"
MediaSrcProp="Sample"
Name="Sample"
SequenceStart="5000">
<UpgradeImage SourceFile="downloads\newunpack\newVersion.msi" Id="SampleUpgrade">
<TargetImage SourceFile="downloads\oldunpack\oldVersion.msi" Order="2"
Id="SampleTarget" IgnoreMissingFiles="no" />
</UpgradeImage>
</Family>
<PatchSequence PatchFamily="SamplePatchFamily"
Supersede="yes" />
</PatchCreation>
</Wix>

How is ReuseCabinetCache used in a WIX install

Similiar question: Reusing WIX components to speed up candle/light
My project has the very same problem as the one referenced; a very large static database that never changes is compressed into the msi every time a build is required. I would like to do as the question asks: reuse a pre-compressed cab file to speed up the build time.
I started doing as the answer suggests, using the cabCache property. I added the following to the .wixproj:
<CabinetCachePath>cabs</CabinetCachePath>
<ReuseCabinetCache>True</ReuseCabinetCache>
I then seperated the static data into a fragment:
<Fragmet>
<Media Id="2" Cabinet="static.cab" EmbedCab="no" />
<Component Id="staticCab" Guid="..." >
Files ...
</Component>
And the fragment was referenced in the feature:
<ComponentRef Id="staticCab" />
This created the cab file, but left it empty. My next thought was the use a Merge Module. I created the module:
<Module Id="StaticModule" Language="1033" Version="1.0.0.0" >
<Package ...>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="MergeRedirectFolder" Name=".">
<Component Id="StaticFiles" Guid="...">
Files...
</ -- End all XML Tags
And then merged it:
<Directory Id="StaticDir" Name="static">
<Merge Id="StaticModule" Language="1033" src="..\Static\bin\Release\static.msm" />
</Directory>
<Feature ...>
<MergeRef Id="StaticModule"/>
</Feature>
Even after all this, the CabinetCache is still being rebuilt every time.
I guess my question would be what is the correct way to use ReuseCabinetCache. I'm still learning WIX, so I apologize if the answer is evident. I just am not sure how to use it.
Edit: A MergeModule would not be ideal since there is no need to share that logic with other msi's. It is only useful to this single project to a single development team.
I'm going to go ahead and answer my own question since it turned out to be so simple.
Change the .wxiproj to have these properties in
<CabinetCachePath>cabs</CabinetCachePath>
<ReuseCabinetCache>True</ReuseCabinetCache>
Add a media to the .wxs install
<Media Id="2" Cabinet="static.cab" EmbedCab="yes" />
In the Directory tag where you store the static files, add DiskId="2".
This will do a couple of things. First you are telling Wix that you wan't to store the cabinets in a path to reuse the cabinets. Creating a new cabinet and only storing static data in it (or data that doesn't change often) will cause Wix to use the cached version of the cabinet. Wix validates the cabinets by ensuring that:
The number of files in the cached cabinet matches the number of files being built.
The names of the files are all identical.
The order of files is identical.
The timestamps for all files all identical.
(Source: http://wix.sourceforge.net/manual-wix3/optimizing_builds.htm)
No wonder I couldn't find any documentation on it. Its so easy to do it should have been apparent to me.
Update: Also, multiple threads are used to build multiple cabinets. Creating multiple cabinets will improve the speed even more.