Wix Harvest: Same Component/File ID when files are in different folders - wix

So I may not be doing this correct, but here it goes:
I have one application with references to 4 SQL Server assemblies
App must work against SQL 2008 and 2010.
The only way I've gotten this to work is, to have my app reference a 'generic' path for my SQL Assemblies. Then in my MSBuild project, I copy the 2008 assemblies to the 'generic' folder and compile my app. I do this again for the 2012.
I have a folder like Tools\Release\V2008 and Tools\Release\V2010. These folders have all the EXE and required DLLs (including the 4 sql server). I run HEAT against these folders.
However, when I run heat against each folder, with each having the same directory ID but different Component, I get 2 wxs files, each have the same files (expected) but each the component and file ids are identical in the 2 wxs files.
Example:
MSBuild Command:
<Exec Command=""$(WixTools)\heat.exe" dir $(DeploymentRoot)\Tools\V2008 -dr TOOLS -cg Tools2008Component -var var.Tools2008Path -gg -scom -sreg -sfrag -srd -o $(heatOutputPath)\cmp2008ToolsFrag.wxs"/>
WXS File
<DirectoryRef Id="TOOLS">
<Component Id="cmp04831EC1F8BB21C028A7FC875720302F" Guid="*">
<File Id="fil09727A8BFD32FDCE7C743D6DD2008E7C" KeyPath="yes" Source="$(var.Tools2008Path)\AL3Util.exe" />
</Component>
MSBuild Command:
<Exec Command=""$(WixTools)\heat.exe" dir $(DeploymentRoot)\Tools\V2012 -dr TOOLS -cg Tools2012Component -var var.Tools2012Path -gg -scom -sreg -sfrag -srd -o $(heatOutputPath)\cmp2012ToolsFrag.wxs"/>
WXS file
<DirectoryRef Id="TOOLS">
<Component Id="cmp04831EC1F8BB21C028A7FC875720302F" Guid="*">
<File Id="fil09727A8BFD32FDCE7C743D6DD2008E7C" KeyPath="yes" Source="$(var.Tools2012Path)\AL3Util.exe" />
</Component>
How can I get each WXS file to have unique component and file IDs?
Or - How can I do this better :)
Thanks!

The ID will be the same because you are using -srd, suppress root directory. In this case, the path used for generating the ID will be only the filename, generating the same ID for files with same name.
You have two alternatives:
1) Use a transform right when you execute heat to harvest the files with -t.
2) Use XslTransform task (.NET 4) after harvesting to rename the ID to one like File_2012_AL3Util and File_2008_AL3Util.
You can apply this XSL to your file. In the example below, the element will be removed if it matches 'MyFile' for file name and 'MyID' for directory id.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wi="http://schemas.microsoft.com/wix/2006/wi">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Matches both directory name and file name. -->
<!-- Matches any Component that has its #Directory with same #Id as Directory 'MyID'. -->
<!-- Function ends-with does not work with heat. -->
<xsl:template match="//wi:Component[#Directory=//wi:Directory[#Name='MyID']/#Id and substring(wi:File/#Source, string-length(wi:File/#Source) - string-length('MyFile') + 1) = 'MyFile']" />
</xsl:stylesheet>

Related

How to extract NuGet contentFiles to a specific directory?

What I've tried so far:
I've got the following .nuspec file, defining my package (simplified for better readability):
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
.
.
.
<dependencies/>
<contentFiles>
<files include="any\any\config\*.*" buildAction="None" copyToOutput="true" flatten="false"/>
</contentFiles>
</metadata>
<files>
<file src="bin\Assembly.dll" target="lib\net461" />
<file src="bin\Assembly.pdb" target="lib\net461" />
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
</files>
</package>
As you can see, it contains a compiled Assembly along with it's debug symbols file as well as two content Files in a sub directory.
This results in the following compilation output, where the Assembly.dll is extracted to the output directory as well as the config sub directory (with the two cf.* files in it):
Question:
What I want to do is to move the sub directory config one step up in the directory tree, so it sits next to the output directory - basically maintaining the structure of the file input in the .nuspec file (assuming bin is the output directory of my .csproj):
How can I tell the NuGet package the exact location where I want the contentFiles to be extracted to?
You can achieve this by creating a MSBuild target that moves your config folder one level above the project build output.
Add a .targets file with the following content to your NuGet's build\net461 folder:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Sets up items for target MoveConfigFiles, so that they can be used as Inputs and Outputs. -->
<Target Name="PrepareMoveConfigFiles" AfterTargets="Build">
<!-- ItemGroup wrapped in Target to delay evaluation of contained Items -->
<ItemGroup>
<!-- Contains source locations of files to be moved -->
<ConfigSourceFiles Include="$(OutputPath)\config\**\*.*" />
<!-- Contains target locations of files to be moved -->
<ConfigTargetFiles Include="#(ConfigSourceFiles->'%(FullPath)'->Replace('\config\', '\..\config\'))" />
</ItemGroup>
</Target>
<!-- Moves files from $(OutputPath)\config to $(OutputPath)\..\config,
then deletes $(OutputPath)\config. -->
<Target Name="MoveConfigFiles" AfterTargets="PrepareMoveConfigAndSqlFiles" Inputs="#(ConfigSourceFiles)" Outputs="#(ConfigTargetFiles)">
<Move SourceFiles="#(ConfigSourceFiles)" DestinationFiles="#(ConfigTargetFiles)" />
<RemoveDir Directories="$(OutputPath)\config" />
</Target>
</Project>
The target PrepareMoveConfigFiles is executed after the Build target (which guarantees the NuGet's contents exist in build output), and sets up items used as Inputs and Outputs of the next MoveConfigFiles target. (The items are set inside this target to ensure they are evaluated after the Build target has completed. If the item definitions would be placed below the Project level, they would be evaluated earlier and thus would potentially be empty because the files of the content folder have not yet been deployed.)
The ConfigSourceFiles item just contains the source files, and ConfigTargetFiles takes the paths of all ConfigSourceFiles and replaces \config\ with \..\config\, which leads to the desired target locations.
The MoveConfigFiles target then uses the Move and RemoveDir tasks to accomplish the movement of files and deletion of the original config folder.
It is a weird question...
You have several things to do a nuspec file;
The main are "the nuget packages".
Your nuspec line:
<file src="bin\Assembly.dll" target="lib\net461" />
<file src="bin\Assembly.pdb" target="lib\net461" />
I don't understand Assembly.pdb to distribute it, but is not important.
The goal of this lines are distribute yout dlls in your projects.
When you compile your solution (in your solution dir) it search for this dlls in same root application directory or else check if are register on system (gac mainly).
On the other hand other kind of lines are the "content" into nuget
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
This is additional content but maybe it may not will be used in the project.
If you want a route system you can use content, but you could have problems using the dlls.
So, I suppose that maybe you would this:
<file src="bin\Assembly.dll" target="contentFiles\any\any\bin" />
<file src="bin\Assembly.pdb" target="contentFiles\any\any\bin" />
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
And other nuspec example (I would not do this, but, it is possible to do it):
<files>
<file src="content\bin\youtBinayFile" target="content\bin\youtBinayFile" />
<file src="content\Config\YourConfigFile" target="content\Config\YourConfigFile" />
<file src="readme.txt" target="readme.txt" />
</files>
I hope I've helped

Build target tools path

I am currently packaging a nuget package for my code generator project and I have gotten so far as to include an executable into the tools directory and a build target into the process.
Partial from the nuspec
<files>
<file src="cgbr.targets" target="build\cgbr.targets" />
<file src="cgbr.json" target="content\cgbr.json" />
<file src="..\bin\CGbR.Lib.dll" target="lib\CGbR.Lib.dll" />
<file src="..\bin\cgbr.exe" target="tools\cgbr.exe" />
</files>
Content of the cgbr.targets file
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BeforeBuild">
<Exec Command="cgbr.exe $(ProjectDir)"/>
</Target>
</Project>
Now when I install the package I see that it is included into the build process. Unfortunately the path to cgbr.exe is invalid and I am a little stuck. Of course I could use $(SolutionDir)packages\CGbR.0.3\tools\cgbr.exe but than I would have to modify it every time I change the version.
To clarify: I need the path to my packages tools path.
Edit: Found a related post
You probably want a relative path to the tool from the targets file. There are a number of predefined properties in msbuild. Perhaps the most useful for these scenarios is MSBuildThisFileDirectory which returns the full path to the directory of the current proj file. An example:
<Exec Command=""$(MSBuildThisFileDirectory)..\tools\cgbr.exe" "$(ProjectDir)""/>

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: How do I run heat.exe with a multi-folder setup to get multiple WXS files?

I have a source file setup something like this: C:\Base, and in Base is FolderA, FolderB, FolderC, etc.... I want to run "heat dir" on each sub-folder in Base, so that I get one WXS file containing one ComponentGroup for each folder.
If I run heat (from C:)
heat dir Base\FolderA -cg FolderAGroup -gg -scom -sreg -sfrag -dr -var var.SourceDir INSTALLDIR -out Components-FolderA.wxs
(or 'heat dir FolderA ...' from C:\Base)
(Note that I'll run heat once per FolderA, FolderB, etc... from a script. I don't expect one heat statement to take care of all folders.)
Components-FolderA.wxs looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLDIR">
<Directory Id="dirA..." Name="FolderA">
<Component Id="cmpF..." Guid="{GUID-HERE}">
<File Id="filB..." KeyPath="yes" Source="$(var.SourceDir)\RandomFile.txt" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="FolderAGroup">
<ComponentRef Id="cmpF..." />
</ComponentGroup>
</Fragment>
</Wix>
The problem with this is that Source="$(var.SourceDir)\RandomFile.txt uses whatever I specify as SourceDir. So if I designate C:\Base as SourceDir, the file RandomFile.txt can't be found because it's looking for C:\Base\RandomFile.txt, not C:\Base\FolderA\RandomFile.txt. I can't specify C:\Base\FolderA as SourceDir because FolderB, FolderC, etc... will be wrong. So how do I tell heat to insert 'FolderA\' in between '$(var.SourceDir)\' and 'RandomFile.txt'?
I have looked at Harvesting multiple directories in WiX, but the first answer is too much manual work (we're lookong for almost complete automation) and the second answer puts everything in one file under one ComponentGroup, which would be a maintenance nightmare as we have thousands of files and dozens of folders and sub-folders to be installed.

error PYRO0227 when trying to create WiX patch

I'm trying to create a patch using WiX 3.6 following this example containing 2 C# projects (executable and library). But I'm getting this error:
warning PYRO1079 : The cabinet 'RMT.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.
error PYRO0227 : The transform being built did not contain any differences so it could not be created.
Executed commands:
set w="c:\Program Files (x86)\WiX Toolset v3.6\bin\"
%w%torch.exe -p -xi 1.0.0.0\PatchMe.Installer.wixpdb 1.1.1.1\PatchMe.Installer.wixpdb -out Patch\Diff.wixmst
%w%candle.exe Patch.wxs
%w%light.exe Patch.wixobj -out Patch\Patch.WixMsp
%w%pyro.exe Patch\Patch.WixMsp -out Patch\Patch.msp -t RTM Patch\Diff.wixmst
Directories "1.0.0.0" and "1.1.1.1" contain output of two different versions of same projects (changed AssemblyVersion and some code changes).
Patch.wxs file:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include Variables.wxi ?>
<Patch AllowRemoval="yes"
Manufacturer="$(var.Manufacturer)"
DisplayName="$(var.ProductName) $(var.ProductVersion)"
Description="Small Update Patch"
Classification="Update"
TargetProductName="$(var.ProductName)"
>
<Media Id="5000" Cabinet="RMT.cab">
<PatchBaseline Id="RTM">
</PatchBaseline>
</Media>
<PatchFamilyRef Id="SamplePatchFamily"/>
</Patch>
<Fragment>
<PatchFamily Id="SamplePatchFamily" Version="$(var.ProductVersion)" Supersede="yes">
<ComponentRef Id="cmp981D9885AA29DD578D66D32ED919EBFB"/>
<ComponentRef Id="cmpD5E6EA59DB565F052E0217CB3248DAE5"/>
</PatchFamily>
</Fragment>
</Wix>
ComponentRef Id's refers to component fragments create by heat.exe harvest of projects mentioned earlier.
Any idea, what could be a problem and why transform doesn't contain any changes?
I think this may be a bug in 3.6. I tried for several hours to get this to work with version 3.6.3303.1, but I always got the PYRO1079 error. I finally downgraded to version 3.5.2519.0, and the error has not reoccurred.
I had to give up my MediaTemplate node and the Directory attribute of my ComponentGroup node after downgrading. I don't know if this was part of the solution or not (i.e. this might have fixed the problem instead of the downgrade).