WiX harvest directories with heat - wix

I have a large project with numerous sub-directories I need to harvest files from. I have this entry in my product.wxs file:
<Feature Id="DefaultFeature" Title="Main Feature" Level="1">
<ComponentGroupRef Id="APP_DATA" />
</Feature>
At build time I run a bat file with a number of heat commands to harvest this directory, among others :
"%WIX%\bin\heat" dir ".\image\App_Data" -cg APP_DATA -gg -scom -dr
App_Data -gl -sfrag -srd -out ".\App_Data.wxs"
This generates a file App_Data.wxs in the same folder as the Product.wxs file. But when the build runs I get the following error :
Product.wxs(97): error LGHT0094: Unresolved reference to symbol
'WixComponentGroup:APP_DATA' in section
'Product:{FAD0EA15-49BC-4EF8-A440-87070E4FAC7A}'
I appears that the WiX project cannot find the "APP_DATA" component group in the file App_Data.wxs. How do I get it to do that? I would assume there is an entry in the ProjectName.wixproj file but I have not found what you do.
I have been searching the internet and found a thousand ways to set this up but none go into enough detail about how you get the WiX project to see files generated by heat during the build. I saw this post.
Which talks about editing the Target Name="BeforeBuild" section of the wixproj file. Is that the only way to get this to work?

You need to add App_Data.wxs to your wixproj just like you'd add a code-generated cs file to a csproj.
For example, if the App_Data.wxs is in the same folder as your wixproj, you'd add the following Compile element to your ItemGroup element in the wixproj.
ProTip: If you are using a SCM that defaults to read-only you'll need to check it out every time or set to always writable.
<ItemGroup>
<Compile Include="App_Data.wxs" />
</ItemGroup>

Related

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.

Installer conditionally picking up files

I'm building a Wix installer and I need two separate versions of said installer. One that picks up the latest development build of the project and one that picks up the latest release build. Currently my fragment looks like this:
<Property Id="Program.ReleaseBuild" Value="0" />
<?define ReleaseBuild = [Program.ReleaseBuild]?>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="InstallFolder">
<Component Id="TheExe" Guid="GUID_GOES_HERE">
<?if $(var.ReleaseBuild) = 1?>
<File Id="ProjectExe" Source="(Rel Project Path)/program.exe" />
<?else?>
<File Id="ProjectExe" Source="(Dev Project Path)/program.exe" />
<?endif?>
</Component>
</ComponentGroup>
</Fragment>
And I have a transform on the msi that transforms the file after build. But the problem is that the file is picked up on compile time not install time, so both version of the installer end up having the same file contained in them. Any idea how I can conditionally grab a dev file or a rel file in the same wix project?
If you want to create installation packages based on build quality (debug versus release), you can use two product configuration and select the source based on it. This way, you can run msbuild twice, one for each configuration. I don't understand the purpose of the transform you mentioned.
So here are steps you could take to accomplish this:
Create an empty solution.
Add your wixproj to it.
Add your csproj to it.
Add a reference of the csproj to the wixproj.
Modify your File[Source] to use the project reference, this way:
<File Source="$(var.MyProject.TargetPath)" Id="ProjectExe" />
The $(var.MyProject.TargetPath) will automatically get the exe from the correct path.
Create a batch file to run the msbuild twice, one for each configuration, with the following commands:
C:\> msbuild mySolution.sln /p:Configuration=Debug
C:\> msbuild mySolution.sln /p:Configuration=Release
The result will be two installation packages, one for each configuration.

Wix Cabinet Caching Not Working

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.

Where to place dlls for unmanaged libraries?

I am trying to create a Nuget package for a library that depends on ghostscript and therefore references gsdll32.dll - an unmanaged library. I can't just included that a standard dll reference. Where do I put this in the nuget directory structure?
Add a build folder to the package and, if the package for example has the id MyPackage, add a MSBuild target file called MyPackage.targets to this folder. It is important that the .targets file has the same name as the .nuspec file. In the .nuspec file you must have a section like this:
<files>
<file src="lib\*.*" target="lib" />
<file src="build\MyPackage.targets" target="build" />
</files>
This will add an MSBuild element in the project file pointing to the .targets file.
Furthermore, to only register the managed dlls, add a section like this:
<references>
<reference file="MyManaged.dll" />
</references>
The .targets file should look something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyPackageFiles" AfterTargets="AfterBuild">
<ItemGroup>
<MyPackageFiles Include="$(MSBuildThisFileDirectory)..\lib\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(MyPackageFiles)" DestinationFolder="$(OutputPath)" >
</Copy>
</Target>
</Project>
Now, all files - including unmanaged files - will be copied to the project output folder (e.g. \bin\debug) after the build.
The above reference can work, but it actually modifies your post build event to push files over, which may not actually fix your issue if you have the situation we did.
The issue we were having was a dependent DLL could not be registered, but had to exist side by side with another DLL which needed to be registered by nuget so it needed to exist in the lib directory but not be registered.
The nuspec reference now allows you to specify which DLLs in the lib directory get explicitly registered in the visual studio project now, you simply need to add into your nuspec file in the metadata area an explicit references list (if this does not exist the default behavior of nuget is to attempt to register everything under lib).
Here is an example nuspec file of what I mean:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>SomePackageID</id>
<version>1.0.1</version>
<title>Some Package Title</title>
<authors>Some Authors</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Blah blah blah.</description>
<references>
<reference file="ceTe.DynamicPDF.Rasterizer.20.x86.dll" />
</references>
</metadata>
<files>
<file src="\\SomeNetworkLocation\ceTe.DynamicPDF.Rasterizer.20.x86.dll" target="lib\ceTe.DynamicPDF.Rasterizer.20.x86.dll" />
<file src="\\SomeNetworkLocation\DPDFRast.x86.dll" target="lib\DPDFRast.x86.dll" />
</files>
</package>
As you can see, ceTe.DynamicPDF.Rasterizer.20.x86.dll needs to be registered, but DPDFRast.x86.dll simply needs to exist in that directory to support the other DLL and won't be registered but through some dynamic referencing magic will ultimately be copied over into the destination bin directory anyway because visual studio sees that the first DLL is dependent upon the second.
Here is the original nuspec reference.
Response on the Nuget forum: http://nuget.codeplex.com/discussions/352689
pranavkm:
The SQLCE package has a similar issue that we handle via PS
scripts. Checkout out the scripts at
https://bitbucket.org/davidebbo/nugetpackages/src/1cba18b864f7/SqlServerCompact/Tools.
I largely got this to work using Lars Michael's method, but one thing I needed to add comes from James Eby's answer. Visual Studio was trying to register all the dll's in my lib directory, so I added a references element to the metadata in the nuspec file to tell it to only register the managed dll:
<references>
<reference file="FANNCSharp.dll" />
</references>
Also in
<MyPackageFiles Include="$(MSBuildProjectDirectory)\..\Packages\MyPackage\lib\*.*"/>
I first tried the id of my package FANNCSharp-x64, but it needed the full package name: FANNCSharp-x64.0.1.4.
One problem I had was that the packages path wasn't always in the same place relative to the project file. The following worked for me:
Within the NuGet package, place your unmanaged DLLs in the lib\native folder.
Add the following script to the tools folder:
install.ps1
#This script creates or updates a PackagesPath property in the project file
param($installPath, $toolsPath, $package, $project)
$project.Save()
#Load the csproj file into an xml object
[xml] $xml = Get-Content -path $project.FullName
#grab the namespace from the project element
$nsmgr = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xml.NameTable
$nsmgr.AddNamespace('a',$xml.Project.GetAttribute("xmlns"))
#find or create the property
$property = $xml.Project.SelectSingleNode("//a:PropertyGroup//a:PackagesPath", $nsmgr)
if (!$property)
{
$property = $xml.CreateElement("PackagesPath", $xml.Project.GetAttribute("xmlns"))
$propertyGroup = $xml.CreateElement("PropertyGroup", $xml.Project.GetAttribute("xmlns"))
$propertyGroup.AppendChild($property)
$xml.Project.InsertBefore($propertyGroup, $xml.Project.ItemGroup[0])
}
#find the relative path to the packages folder
$absolutePackagesPath = (get-item $installPath).parent.FullName
push-location (split-path $project.FullName)
$relativePackagesPath = Resolve-Path -Relative $absolutePackagesPath
pop-location
#set the property value
$property.InnerText = $relativePackagesPath
#save the changes.
$xml.Save($project.FullName)
Add a targets file to the build folder. (Change "MyPackage" to the name of your package). Using a unique name for the target, like "CopyMyPackage", avoids conflicts with other packages trying to define the "AfterBuild" target. This targets file makes use of the $(PackagesPath) property defined by the above script.
MyPackage.targets
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyPackage" AfterTargets="AfterBuild">
<ItemGroup>
<MyPackageSourceFiles Include="$(PackagesPath)\MyPackage.*\lib\native\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(MyPackageSourceFiles)" DestinationFolder="$(OutputPath)" >
</Copy>
</Target>
</Project>
Finally, add a "MyPackageReadMe.txt" to the Content folder. This will enable the package to install.
See also: http://alski.net/post/2013/05/23/Using-NuGet-25-to-deliver-unmanaged-dlls.aspx
For .NET Core this is pretty straightforward if you know what runtime platform your native code targets. You might notice a folder called "runtimes" in the .NET Core build folder under the bin tree when you build. It looks something like this:
These folders are designed to hold any platform specific stuff, including unmanaged/native DLLs.
In your NuGet package add a the following under the "Files" section:
<file src="[source path for file in package]" target="runtimes\[platform]\native\[file name]" />
When executing the application, the runtime environment will look for unmanaged dlls in the corresponding platform directory.
If you want to target multiple platforms, just add another file entry for each platform.

Wix,Heat and Wxi File

I am using heat.exe to genrate file listing, I need to replace File/#Source="SourceDir"
so I am passing -var and directory name , but those variable are defined in my .wxi file
How can I include .wxi file in the heat generated wxs file . as this file will be generated each time i make a build.
Why replace SourceDir?
You can just pass in additional base folders to light with the -b switch and for all references of SourceDir, WiX will look in the base folders you've specified. Makes it easy to move things around between machines and only have to update a parameter in your build system, rather than editing an include file.
An update for wix 3.7, when you use HeatDirectory task in your wixproj instead of running heat.exe, you can PreprocessorVariable to set the SourceDir.
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\distribution"
PreprocessorVariable="myVar" <--- your variable name
OutputFile="HeatGeneratedFileList.wxs"
ComponentGroupName="HeatGenerated"
DirectoryRefId="INSTALLFOLDER"
AutogenerateGuids="true"
ToolPath="$(WixToolPath)"
SuppressFragments="true"
SuppressRegistry="true"/>
</Target>
More detail:
http://wix.sourceforge.net/manual-wix3/msbuild_task_reference_heatdirectory.htm
WIX HeatDirectory Task - Setting the preprocessorVariable