Wix Bundle Source path and project stucture - wix

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 + "`" />")
}

Related

CMake and CPack WiX Standard Bootstrapper Application

according to CPack/WIX documentation
I can create *.msi installer
as described here
Feeding a custom wxs file to CPackWIX
But I need more complex scenario, where I have top level Bundle -> bootstrapper_installer.exe
which in turn run child *.msi installers:
https://wixtoolset.org/documentation/manual/v3/bundle/wixstdba/
It means like I need to run CPack twice firstly to generate product_installer.msi
second time to make main installer - bootstrapper_installer.exe with embedded product_installer.msi
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle Name="Source-Connect" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" UpgradeCode="69157fdc-819e-4e70-9fac-b2a0840a7a11">
<MsiPackage SourceFile="$(var.Product-Installer.TargetPath)" DisplayInternalUI="no" Compressed="yes">
<MsiProperty Name="LAUNCH_AFTER_INSTALL" Value="[LaunchAfterInstall]" />
<MsiProperty Name="FORCE_CLOSE_APPLICATION" Value="[CloseRunningApp]" />
</Chain>
</Bundle>
</Wix>
Is there way to achieve this using Cmake/CPack?

Wix: Creating Bootstrap .exe for .msp causes original version to uninstall

I am trying to create a Wix Bootstrap executable that contains an .msp patch file. I have generated the patch file using pyro.exe and the patch itself works absolutely fine and updates the required files correctly when ran by itself.
However we package all our .msi's in a Wix Bootstrap project with a custom user interface, which I have cloned for the patch files. However when running the executable this way it removes all the files from the install directory.
Has anyone experienced this issue before or am I doing something wrong? Thank you in advance, let me know if you need further code examples.
BootstrapBundle.wxs
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
xmlns:vi="http://schemas.visualinstaller.de/VisualInstallerWixExtension">
<Bundle Name="MyProgram" Version="1.0.0.1"
Manufacturer="Test"
UpgradeCode="GUID"
SplashScreenSourceFile="Resources\splash.bmp"
IconSourceFile="Resources\icon.ico">
<Update Location="http://test.laika42.com/UpdateInfo.xml"/>
<BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>
<PayloadGroupRef Id='VisualInstallerRuntimeFiles'/>
</BootstrapperApplicationRef>
<Variable Name="INSTALLFOLDER" bal:Overridable='yes'
Value='[ProgramFilesFolder]Test\MyProgram\'/>
<Chain>
<PackageGroupRef Id='NetFx45Web' /> <!-- Fails to build without this? -->
<MspPackage Id='PatchMsp' SourceFile='C:\Patches\Patch.msp' />
</Chain>
</Bundle>
</Wix>
Just had this problem myself. It seems that the UpgradeCode for the bundle must be different to the UpgradeCode for the MSI - the bundle will remove anything older with the same Bundle UpgradeCode, including the original full MSI. I have to say I find the Wix documentation less than illuminating. Bdum-tsh.
The important bits seem to be having different UpgradeCodes for the MSI, the MSI bootstrapper bundle and the patch bootstrapper bundle but keeping each one of the three the same going forward, and the RelatedBundle element.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<?include $(var.SolutionDir)\Installer\ProductDefs.wxi?>
<?include $(var.SolutionDir)\Installer\version.wxi?>
<!-- A DIFFERENT UpgradeCode to the main bundle, but consistent for all patches. I think. -->
<?define PatchBundleUpgradeCode = "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"?>
<!-- The UpgradeCode of the Bundle that was used to deploy the MSI originally. -->
<?define MSIBundleUpgradeCode = "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB"?>
<!-- ... both of which are different to the MSI UpgradeCode. -->
<Bundle Name="$(var.ProductName) $(var.ProductVersion)"
ParentName="$(var.ProductName)"
Version="$(var.ProductVersion)"
Manufacturer="$(var.Manufacturer)"
UpgradeCode="$(var.PatchBundleUpgradeCode)"
IconSourceFile="$(var.SolutionDir)\Installer\MyIcon.ico"
AboutUrl="https://www.somecompany.com/"
HelpUrl="https://www.somecompany.com/support/"
>
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication
LicenseFile="$(var.SolutionDir)\Bootstrapper\license.rtf"
LogoFile="$(var.SolutionDir)\Bootstrapper\Logo.jpg"
ShowVersion="yes"
SuppressOptionsUI="yes"
/>
</BootstrapperApplicationRef>
<OptionalUpdateRegistration Classification="Update" Name="$(var.ProductName) $(var.ProductVersion)"/>
<RelatedBundle Id="$(var.MSIBundleUpgradeCode)" Action="Patch" />
<Chain>
<MspPackage SourceFile="$(var.ProjectDir)\Release\$(var.ProductName) $(var.ProductVersion) Patch.msp" DisplayInternalUI="yes"/>
</Chain>
</Bundle>
</Wix>
Also, if you want to apply more than one minor patch, make sure your PatchMetadata element (in the patch.wxs, not the bundle.wxs) contains the following attribute otherwise the first patch will apply but subsequent ones won't.
MinorUpdateTargetRTM="1"

How to make Wix Heat.exe retain custom file id?

I have a ASP.NET core app for which I am creating a WIX installer. I am using Heat to generate all the files:
<!-- Remove read-only attribute -->
<Exec Command="attrib -R %(ProjectReference.Filename).wxs" Condition="'%(ProjectReference.WebProject)'=='True'" />
<ItemGroup>
<LinkerBindInputPaths Include="%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\publish\" />
</ItemGroup>
<!-- Generate a WiX installer file using Heat Tool -->
<HeatDirectory OutputFile="%(ProjectReference.Filename).wxs"
Directory="%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\publish\"
DirectoryRefId="INSTALLFOLDER"
ComponentGroupName="%(ProjectReference.Filename)"
AutogenerateGuids="True"
SuppressCom="True"
SuppressFragments="True"
SuppressRegistry="True"
ToolPath="$(WixToolPath)"
Condition="'%(ProjectReference.WebProject)'=='True'" />
Which puts entries like the following in my WebApp.sxs file:
<Component Id="cmp64BF6D207C595218157C321E631ED310" Guid="*">
<File Id="myExe" KeyPath="yes" Source="SourceDir\MyExe.exe" />
</Component>
Issue is, I amended the Id attribute, so that I could bind to the version in Product.wxs:
<Product Id="*"
Name="..."
Manufacturer="..."
Version="!(bind.fileVersion.myExe)"
Language="1033"
UpgradeCode="143521a5-99df-4594-9d71-b028cddb9ed8">
How can I make it so that heat keeps the same Id for this file? Yet at the same time add any new files?
The basic idea is to filter out the component of "MyExe.exe" from the Heat output, then manually add it back to the main WiX authoring, where we can specify a constant File/#Id attribute.
The filtering can be done by passing a XSLT file to Heat:
<HeatDirectory Transforms="$(ProjectDir)RemoveMyExeComponent.xslt" ... />
RemoveMyExeComponent.xslt could look as follows (idea from this answer, but simplified for current question). In this file, replace MyExe.exe with the actual name of your EXE and replace - 8 by the length of the file name, decremented by one.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
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"
version="1.0"
exclude-result-prefixes="xsl wix">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<!--
Find the component of the main EXE and tag it with the "ExeToRemove" key.
Because WiX's Heat.exe only supports XSLT 1.0 and not XSLT 2.0 we cannot use `ends-with( haystack, needle )` (e.g. `ends-with( wix:File/#Source, '.exe' )`...
...but we can use this longer `substring` expression instead (see https://github.com/wixtoolset/issues/issues/5609 )
-->
<xsl:key
name="ExeToRemove"
match="wix:Component[ substring( wix:File/#Source, string-length( wix:File/#Source ) - 8 ) = 'MyExe.exe' ]"
use="#Id"
/> <!-- In this expression "-8" is the length of "MyExe.exe" - 1 because XSLT uses 1-based indexes, not 0-based indexes. -->
<!-- By default, copy all elements and nodes into the output... -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- ...but if the element has the "ExeToRemove" key then don't render anything (i.e. removing it from the output) -->
<xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'ExeToRemove', #Id ) ]" />
</xsl:stylesheet>
Now that we have removed the generated EXE component, we can manually add it back to the WiX authoring, giving us full control over all attributes:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="WixHeatConstantExeFileId" Language="1033" Version="!(bind.fileVersion.myExe)" Manufacturer="zett42" UpgradeCode="PUT-GUID-HERE">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="WixHeatConstantExeFileId" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<ComponentGroupRef Id="HarvestedComponents" />
</Feature>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="WixHeatConstantExeFileId" />
</Directory>
</Directory>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- Manually add back the EXE component that was filtered out from Heat output. -->
<Component Id="myExe" Guid="*">
<File Id="myExe" KeyPath="yes" Source="SourceDir\MyExe.exe" />
</Component>
</ComponentGroup>
</Product>
</Wix>
Works like a charm in my test project!
Use SuppressRootDirectory="true" (-srd option) in Heat. That way Heat ignores the full path and just uses the file name to generate IDs. As long as the filename is the same, the ID will always be the same.
Then instead of replacing the ID generated by heat, replace 'myExe' in your .wxs file with the ID generated by heat.
See
Wix Harvest: Same Component/File ID when files are in different folders

WiX Burn: Reading LaunchTarget from Registry

I'm new with WiX, and I'm trying to have my Bootstrapper launch my installed application when it completes. To accomplish this, I'm using
<Variable Name="LaunchTarget" Value="path_to_exe"/>
However, it is not easy for me to get the path to the executable. The reason for this is because I'm using <Chain> to install some pre-requisites and then an msi that actually installs my exe.
So to do this, the msi is writting the path to a known location in the registry, and then the bootstrapper reads it and uses it.
The problem is that when the bootstrapper reads the registry, the msi hasn't run yet, so it is unable to run the executable at the end.
Here's my WiX, if it helps:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Bundle Name="My Installation" UpgradeCode="a8964402-f3fc-4878-aafd-31ecda6b685e" Version="1.0.0.0">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication LicenseFile="EULA.rtf"
ThemeFile="theme.xml"
SuppressOptionsUI="yes" />
</BootstrapperApplicationRef>
<Chain>
<PackageGroupRef Id="NetFx40Redist"/>
<ExePackage Id="OpenSSL" SourceFile="pre-requesite.exe" />
<MsiPackage Id="myInstall" SourceFile="mySetup.msi" />
</Chain>
<util:RegistrySearch Root="HKLM"
Key="Software\myProgram"
Value="myEXEPath"
Variable="myEXEPath"
Result="value"
Format="raw" />
<Variable Name="LaunchTarget" Value="[myEXEPath]"/>
</Bundle>
</Wix>
So, in short, I'm trying to have the RegistrySearch run AFTER the MsiPackage installs. Can this be done? If not, what alternatives do I have?
As I side note, if I manually fill in the registry value before installation, everything works fine. This means that besides the order things are running in, everything is working fine.
RegistrySearches run during the Detect operation. Custom BAs could run Detect after Apply, but that's not really an option since you're using the WixStandardBootstrapperApplication.
Lucky for you, WiX v3.9 added support for running the LaunchTarget already elevated, with the requirement that the path to the target .exe be in the registry under HKLM. So you would do this:
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication LicenseFile="EULA.rtf"
ThemeFile="theme.xml"
SuppressOptionsUI="yes"
LaunchTargetElevatedId="MyAEEId" />
</BootstrapperApplicationRef>
<ApprovedExeForElevation Id="MyAEEId"
Key="Software\myProgram" Value="myEXEPath" />
Edit:
It looks like you are required to set LaunchTarget as well. Why doesn't your bundle know where it will be? You can just put in gibberish for LaunchTarget (WixStdBA will try the registry location first), but can't you use built-in variables and/or MsiProperty elements to locate the exe?

Harvesting multiple directories in WiX

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