I'm trying to build an installer that includes a number of features and I'm using heat to harvest a directory of files for each feature.
My source directory structure looks something like this:
HarvestDir
\FeatureA
\FeatureImpl.dll
\FeatureImpl2.dll
\FeatureB
\FeatureImpl.dll
\FeatureImpl2.dll
So I execute heat.exe for each feature to create a fragment for each feature but I get basically identical fragments e.g.
[...] Source="SourceDir\FeatureImpl.dll"
[...] Source="SourceDir\FeatureImpl2.dll"
What I really want is something like this:
[...] Source="SourceDir\FeatureA\FeatureImpl.dll"
[...] Source="SourceDir\FeatureA\FeatureImpl2.dll"
and
[...] Source="SourceDir\FeatureB\FeatureImpl.dll"
[...] Source="SourceDir\FeatureB\FeatureImpl2.dll"
I could use -var to specify a separate variable to represent the source location for each feature, but then I'd have to pass values for these variables into the wixproj (and I'm going to have ~10 features).
So, is there any way I can include a relative path in my harvested fragment?
Rather than using heat on each Feature* folder, you could just harvest the whole feature layout folder that you have created and have heat transform the results. I'm assuming that each subfolder is a feature. It's a simple matter of creating a ComponentGroup for each one that references all the components under it.
You'd then use the component groups like this:
<Feature Id="FeatureA">
<ComponentGroupRef Id="FeatureA"/>
</Feature>
<Feature Id="FeatureB">
<ComponentGroupRef Id="FeatureB"/>
</Feature>
Heat command (I actually use the HarvestDirectory target in WiX 3.7):
Heat.exe dir FeatureLayout -dr FeatureLayoutDir -srd -ag -sfrag -t FeatureLayout.xslt -out obj\Debug\__dir.wxs
FeatureLayout.xslt:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="wix"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="wix:Wix">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:for-each select="wix:Fragment/wix:DirectoryRef/wix:Directory">
<wix:Fragment>
<wix:ComponentGroup Id="{#Name}">
<xsl:for-each select="descendant::wix:Component">
<wix:ComponentRef Id="{#Id}"/>
</xsl:for-each>
</wix:ComponentGroup>
</wix:Fragment>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You can either assign your source paths in .wixproj in DefineConstants or you can assign them in a WIX include file but either way you will have to use "var" option to specify the variable being used for your sources.
If you want to use DefineConstant in wixproj then you will have to do something like
<Target Name="BeforeBuild">
<PropertyGroup>
<DefineConstants>BINFOLDER=..\PATH\TO\SOURCE\$(Configuration)</DefineConstants>
</PropertyGroup>
<HeatDirectory OutputFile="output.wxs" Directory="..\PATH\TO\SOURCE\$(Configuration)" DirectoryRefId="INSTALLFOLDER" ComponentGroupName="COMPONENTGROUPNAME" ToolPath="$(WixToolPath)" PreprocessorVariable="var.BINFOLDER" />
</Target>
In Heat task make sure you add any additional attributes that you might need. In case you have added a reference to your source project in the .wixproj then you may directly use the project reference variables in the "PreprocessorVariable" attribute. For example instead of var.BINFOLDER use var.MyProject.TargetDir.
If you want to use a WIX include file (.wxi) then declare your variables in an include file for example Variables.wxi:
<?xml version="1.0" encoding="UTF-8"?>
<Include>
<?define PATH1="PATH\TO\SOURCE"?>
<?define PATH2="$(var.PATH1)\$(Configuration)"?>
</Include>
Now you will need to pass in PATH1 and PATH2 using the -var in the heat command line. Once you have your .wxs file you will then need to include the Variables.wxi file in that using
<?include Variables.wxi ?>
in your .wxs. The problem with this way is that you will have to either add this include directive manually each time you generate your WIX source files or you will have to inject it using XSL Transformation.
hit properties for the setup project, this is the command i'm using:
"%WIX%\bin\heat.exe" dir "$(SolutionDir)\Export\Release" -suid -dr bin -srd -cg ExportComponentGroup -var var.sourcePath -ag -sreg -out "$(SolutionDir)\SetupProject\AppExportDir.wxs"
the only thing else you need to define in the build tab is:
check the check box for "define 'debug'preprocessor variable" and enter.
sourcePath=%SystemDrive%\App\Main\Export\Release\
this please make sure that you use the flags that you want like suid or -dr
for more information about the flags you have see here:
http://wix.sourceforge.net/manual-wix3/heat.htm
Related
Newbie to Wix in need of some help with patch making.
In my installer, it uses Heat.exe on an entire folder called "Assemblies" which contains a bunch of .dll files. I want to update one of these .dll files with one that has a higher version number (say from 1.1.0.0 to 4.0.1.0). The command for the heat is done before the installer is built and is below:
<HeatDirectory Directory="..\..\..\ProgressCode\Assemblies" OutputFile="$(ProjectDir)\Generated\Assemblies.wxs" PreprocessorVariable="var.AssembliesPath" DirectoryRefId="Assemblies" AutoGenerateGuids="true" Toolpath="$(WixToolPath)" ComponentGroupName="AssembliesComponentGenerated" SuppressFragments="true" SuppressRegistry="true" SuppressRootDirectory="true" Transforms="$(SolutionDir)\WixInstaller\RemoveAssembliesXml.xslt" />
These are also stored in the GAC but I have no idea how it does that.
The transform file (RemoveAssembliesXml.xslt) is below:
<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" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="compsToRemove"
match="wi:Component[(contains(wi:File/#Source,'Assemblies.xml'))]"
use="#Id"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[self::wi:Component or self::wi:ComponentRef] [key('compsToRemove',#Id)]" />
</xsl:stylesheet>
My patch file is pretty simple as I want it to pick up all the differences between the original and transformed msi.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Patch
AllowRemoval="yes"
Manufacturer="Test"
DisplayName="Sample Patch"
Description="Small Update Patch"
Classification="Update"
>
<Media Id="5000" Cabinet="Patchy.cab">
<PatchBaseline Id="Patchy"/>
</Media>
</Patch>
</Wix>
It picks up the new files I want to add but not the changed .dll file. Is there anything specific I need to do to force it to find and update the .dll?
I'm trying to create a Bootstrapper installer that will install My app plus one third party applications needed to run my application.
The third party application is an .exe package with a lot of complementary files.
My question is, How do I include the third party application into my Bundle? Do I have too add all the complentary files (100+ files) as payloads?
the below code is how I have set up my Bundle so far, it is compiling but not installing, the log says that the bootstrapper .exe can't find my .msi (my app) nor the .exe (third party app)
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle Name="My_app"
Version="1.0.0.0"
Manufacturer="untitled"
UpgradeCode="4d2de235-2286-4b06-8cfa-3f6ff3174244">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" />
<Chain>
<MsiPackage
Id="MyApp_msi"
SourceFile ="MyApp\MyApp.msi"
Vital ="yes"/>
<PackageGroupRef Id="thirdParty_package" />
</Chain>
</Bundle>
<Fragment>
<PackageGroup Id="thirdParty_package">
<ExePackage
SourceFile="thirdParty\Setup.exe"
InstallCommand="/q /ACTION=Install"
RepairCommand="/q ACTION=Repair /hideconsole"
UninstallCommand="/q ACTION=Uninstall /hideconsole"/>
</PackageGroup>
</Fragment>
</Wix>
Yes, you have to list every Payload. It is convenient to put them in a PayloadGroup.
There are quite a few ways to generate a PayloadGroup. One way is to use/abuse heat to harvest a directory. This would be the same way as harvesting a directory for a Setup project.
As an example, let's package WiX's bin directory.
<ExePackage Id="MyPackageId" SourceFile="$(env.WiX)bin/dark.exe" Compressed="yes">
<PayloadGroupRef Id="MyPayloadGroupId"/>
</ExePackage>
If you are using MSBuild (including via Visual Studio), you can configure the harvest by adding something like this to the project file:
<ItemGroup>
<HarvestDirectory Include="$(WIX)/bin">
<ComponentGroupName>MyPayloadGroupId</ComponentGroupName>
<PreprocessorVariable>var.MyPayloadSourceDirectory</PreprocessorVariable>
<Transforms>FilesToPayloads.xsl</Transforms>
<SuppressRegistry>true</SuppressRegistry>
<!-- Hide from VS Solution Explorer -->
<InProject>false</InProject>
</HarvestDirectory>
</ItemGroup>
When the build runs it adds the output .wsx (in the obj) folder to the build. (You don't need to see it.)
Notice that it uses a pre-processor variable to give the actual location of the source files. To pass the value, define it in the project properties. Or, as XML in the .wixproj:
<DefineConstants>Debug;MyPayloadSourceDirectory=C:/Program Files (x86)/WiX Toolset v3.8/bin</DefineConstants>
Finally, the XSL transform (FilesToPayloads.xsl) that heat will apply to its normal harvest output:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns="http://schemas.microsoft.com/wix/2006/wi">
<xsl:template match="/">
<Wix>
<Fragment>
<xsl:apply-templates select="*" />
</Fragment>
</Wix>
</xsl:template>
<xsl:template match="//wix:DirectoryRef">
<PayloadGroup>
<xsl:attribute name="Id">
<xsl:value-of select="/wix:Wix/wix:Fragment/wix:ComponentGroup/#Id"/>
</xsl:attribute>
<xsl:apply-templates select="*" />
</PayloadGroup>
</xsl:template>
<xsl:template match="//wix:File">
<Payload>
<xsl:attribute name="SourceFile">
<xsl:value-of select="#Source"/>
</xsl:attribute>
</Payload>
</xsl:template>
</xsl:stylesheet>
It's a rather trivial transliteration of File to Payload and the surrounding DirectoryRef to a PayloadGroup.
Agreed it is a pain to generate the payload manually for installers with many files!
Here is a small powershell script to generate the payload xml given a path. It will generate guids to use as id, recurse the folder and reproduce the folder structure.
if($args.Length -eq 0) {
Write-Host "Error: No argument. Supply path to payload folder as script argument."
Exit 1
}
$path = $args[0]
foreach($file in Get-ChildItem -Name $path -File -Recurse)
{
Write-Host ("<Payload Id=`"" + ([guid]::NewGuid()) + "`" SourceFile=`"" + (Join-Path -Path $path -ChildPath $file) + "`" Name=`"" + $file + "`" />")
}
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>
I'm a bit confused about the interaction of these tools at the moment and I need a little helping ironing out my appraoch (or being told I'm being daft).
I have a VS2010 solution with two WiX projects. I have a a .WXI file in a solution folder that contains a number of global variables that I would like to use in multiple projects (more will be added in the future).
I include the .WXI file in my main Product.wxs project using the standard syntax like this:
<?include ..\Shared\Common.wxi?>
This is working great and all the variables are available. However as part of this build process I also need to run heat.exe in order to generate a large fragment automatically on each rebuild.
I use a command like this:
"Heat.exe" dir %sourcedir% -sfrag -sreg -suid -gg -ke -template fragment -out %wixfile% -dr INSTALLFOLDER -cg MyInstall -var var.MyFileSource
In my included variables file I have this:
<?define MyFileSource= "$(var.SolutionDir)..\..\..\..\mydir\bindist" ?>
However I get an error like this:
Undefined preprocessor variable '$(var.MyFileSource)'.
As you would expect as the fragment file does not contain a line that references the file Common.wxi but the problem is this fragment file is going to be replaced each build prior to compilation so it's completely inpractical to hand edit this fragment in order to add it each time (not to mention impossible anyway).
My question is am I approaching this completely incorrectly? If not, what is the best way to get this working correctly?
What is confusing is that there is the -var switch to create a variable reference in the fragment file but no way to then enable the variable referencing from an include file (unless I've missed that somewhere in the docs).
Instead of placing the variables in a separate wxi file and include it in all possible places, you can supply candle.exe with the variables you need. You can do this either from command line (using -dVarName=VarValue syntax), or with any of the supported build tools, NAnt or MSBuild.
I can see the following advantages:
the WiX authoring are not polluted with <?include?> stuff here and there
you don't have to mess the heat harvested authoring
variables defined once from the command line are available in any wxs file of your project
I also have such an include-file that centralizes my configuration during the build. As I also use variables in the files produced by heat this is very convenient, as I have to define them just once.
To be able to use these variables also in the heat-output you can transform the output created by heat using a xslt-transformation. Just add -t myTransformFile.xslt to the command-line of heat, so the output will be transformed according to the stylesheet defined in myTransformFile.xslt.
In the myTransformFile.xslt-file just add the following, which will basically copy everything and also add the Include-directive:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns="http://schemas.microsoft.com/wix/2006/wi"
exclude-result-prefixes="wix">
<xsl:template match="wix:Wix">
<xsl:copy>
<!-- The following enters the directive for adding the Common.wxi include file to the dynamically generated file -->
<xsl:processing-instruction name="include">$(sys.CURRENTDIR)..\Shared\Common.wxi</xsl:processing-instruction>
<xsl:apply-templates select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You have to adjust the path to your Common.wxi of course so that it can be found at build-time.
Edit: forgot a part of the XSLT-file that copied also other elements.
I have a WiX installer to install and start a service. However, all the examples I find place the ServiceInstall tag directly below the file tag for the .exe file to be installed as a service.
I can’t do this as I am using heat to generate my file elements in a separate file. So my wix script looks like this:
<Directory Id="INSTALLLOCATION" Name="Email Generation Service">
<Component Id="SetupService" Guid="51E78696-80E0-4CDA-8F49-902C67CB129C">
<CreateFolder />
<ServiceInstall Id="ServiceInstaller"
Type="ownProcess"
Vital="yes"
Name="EmailGenerationService"
DisplayName="Email Generation Service"
Description="Service for generating Emails from Nexus"
Start="auto"
Account="LocalService"
ErrorControl="ignore"
Interactive="no">
</ServiceInstall>
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="EmailGenerationService" Wait="yes" />
</Component>
</Directory>
How can I tell WiX which file I want to install as a service?
I have used XSLT to set the KeyPath on all files to no, with the exception of the file I want to install, despite the fact that all files are in their own component. I am at a bit of a loss now :(
A service must be linked to a specific file. This is a Windows Installer limitation. So one way or another you need to create the ServiceInstall element under your EXE file element.
A solution would be to hard-code the EXE file instead of letting it be generated automatically.
Adding this as relevant information, I had a similar problem and used a similar solution to you, adding an xml transform. However, I used the transform to insert the service control/install elements into the heat-generated fragment. I've pasted my transform below, you may have to modify properties or remove items you don't need.
Some things to note:
The service control doesn't auto-start the service on install, I had
issues with assembly refs not being populated by the time the msi tried to invoke them
A key part of the XSLT adds
an "include" statement at the top of the heat.exe file so that I can
reference variables from my .wxi file
This assumes the variable name
you told heat to inject as your -var parameter is "var.SourcePath"
I never extracted the name of the .exe file or settings file out as
variables that can be injected into the transform because they were
fairly stable and it seemed...difficult
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns="http://schemas.microsoft.com/wix/2006/wi">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Template for the new ServiceInstall element -->
<xsl:param name="pServiceInstall">
<xsl:element name="ServiceInstall">
<xsl:attribute name="Id">SVINSTL_$(var.ServiceName)</xsl:attribute>
<xsl:attribute name="Description">$(var.ServiceInstallDescription)</xsl:attribute>
<xsl:attribute name="Account">$(var.SystemAccount)</xsl:attribute>
<xsl:attribute name="DisplayName">$(var.ServiceInstallDisplayName)</xsl:attribute>
<xsl:attribute name="ErrorControl">normal</xsl:attribute>
<xsl:attribute name="Name">$(var.ServiceName)</xsl:attribute>
<xsl:attribute name="Interactive">no</xsl:attribute>
<xsl:attribute name="Start">auto</xsl:attribute>
<xsl:attribute name="Type">ownProcess</xsl:attribute>
<xsl:attribute name="Vital">yes</xsl:attribute>
</xsl:element>
</xsl:param>
<!-- Template for the new ServiceControl element -->
<xsl:param name="pServiceControl">
<xsl:element name="ServiceControl">
<xsl:attribute name="Id">SVCTRL_$(var.ServiceName)</xsl:attribute>
<xsl:attribute name="Name">$(var.ServiceName)</xsl:attribute>
<xsl:attribute name="Stop">both</xsl:attribute>
<xsl:attribute name="Remove">uninstall</xsl:attribute>
<xsl:attribute name="Wait">yes</xsl:attribute>
</xsl:element>
</xsl:param>
<!-- Insert a ?include statement at the top of the fragment so it can use config variables -->
<xsl:template match="wix:Wix">
<xsl:copy>
<xsl:processing-instruction name="include">InstallerSettings.wxi</xsl:processing-instruction>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- Turn the file name of the executable into a variable so it can be targeted for shortcuts,etc -->
<xsl:template match="#Source[. ='$(var.SourcePath)\HARD_CODED_NAME_OF_PROJECT.exe']">
<xsl:call-template name="identity" />
<xsl:attribute name="Id">$(var.ServiceExecutableFileId)</xsl:attribute>
</xsl:template>
<!-- Insert the ServiceInstall and ServiceControl elements into the component with the exe file -->
<xsl:template match="//wix:File[#Source='$(var.SourcePath)\HARD_CODED_NAME_OF_PROJECT.exe']">
<xsl:call-template name="identity" />
<xsl:copy-of select="$pServiceInstall"/>
<xsl:copy-of select="$pServiceControl"/>
</xsl:template>
<!-- Identity template (copies everything as is) -->
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>