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
Related
I've followed the official Major Upgrade guide and I seem to be missing something.
Here is my MCVE:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Codepage="1252" Language="1033" Manufacturer="Bla Corporation"
Name="Bla" UpgradeCode="PUT-GUID-HERE" Version="31.00.0000">
<Package Comments="Contact: Refael Sheinker, refael.sheinker#bla.com." Description="Bla"
InstallerVersion="500"
Compressed="yes"
InstallScope="perMachine"
Keywords="Installer,MSI,Database" Languages="1033" Manufacturer="Bla Corporation" Platform="x64" />
<Media Id="1" Cabinet="my_application.cab" EmbedCab="yes" />
<MajorUpgrade AllowDowngrades="no"
AllowSameVersionUpgrades="no"
Disallow="no"
IgnoreRemoveFailure="no"
MigrateFeatures="yes"
Schedule="afterInstallInitialize"
DowngradeErrorMessage="A later version of [ProductName] is already installed" />
<Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONROOTDIRECTORY" />
<UIRef Id="WixUI_InstallDir" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="PROGRAMFILESSUBDIR" Name="Bla">
<Directory Id="APPLICATIONROOTDIRECTORY" Name="BlaInternal" />
</Directory>
</Directory>
</Directory>
<DirectoryRef Id="APPLICATIONROOTDIRECTORY">
<Component Id="tenlira.ini" Guid="*">
<File Id="tenlira.ini" Source="..\ConfigurationFile\x64\tenlira.ini" KeyPath="yes" />
</Component>
</DirectoryRef>
<Feature Id="MainApplication" Title="TenLira" Level="1">
<ComponentRef Id="tenlira.ini" />
</Feature>
</Product>
</Wix>
All it does is simply installing a single file as an example. So far, so good. Now, all I do is add another Component and File and off course the corresponding ComponentRef in Feature. I specifically leave the Version as is: 31.00.0000. What I expected is that the new installer will not perform a Major Upgrade, but it does. Why? Also, there is now 2 entries in Add/Remove Programs.
Please help me find out what am I missing here. Thanks. Refael.
UPDATE:
Posting the question got me to reread the documentation again and I discovered that the AllowSameVersionUpgrades thingy in the MajorUpgrade element should be set to yes. This time there is only one entree in the Add/Remove Programs, but it still performs Major Upgrade. Why?
UPDATE: Here is a list to help debug failing major upgrades by identifying the most common problems: Common causes of failed major upgrades
Major Upgrade - "The Old, Manual Way"
I guess you are hitting an oddity that may not be handled entirely as expected by the WiX MajorUpgrade element by combining the auto-generated product GUID, the AllowSameVersionUpgrades set to yes and keeping the version number the same.
I can't see any obvious way to set the MinInclusive attribute in WiX's MajorUpgrade element - I could be mistaken, there might be a way I am unaware of. For what it is worth, I am not too keen on allowing "same version upgrades".
However, you could try to "use the old way" to author the Upgrade table using the "older elements" Upgrade and UpgradeVersion. The MajorUpgrade element is essentially a "convenience" feature to set up your major upgrades easily, and I believe it works for most users. Bob Arnson has a blog explaining the introduction of the MajorUpgrade element. This blog also shows a sample of how to do things "manually" with the "older elements" Upgrade and UpgradeVersion (do check it out).
I made a quick mock-up that might do what you want, it is just a "rough draft" - can't make any guarantees. I use preprocessor defines to set some variables that can be referenced in the WiX source file - as a C++ developer this is a piece of cake for you so I won't waste time explaining it - the source should make sense:
<?define MyProductVersion = "31.00.0000" ?>
<?define MyProductCode = "PUT-GUID-HERE" ?>
<?define MyUpgradeCode = "PUT-GUID-HERE" ?>
<!--Recommendation: set a path variable that you can redirect at will to a new release folder (new build output folder): -->
<!-- <?define MyBasePath = "C:\Projects\MyApp\Release\31.00.0000\" ?> -->
<!-- SAMPLE:
<Component Win64="yes" Feature="MainApplication">
<File Source="$(var.MyBasePath)\myapp.exe" />
</Component> -->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="$(var.MyProductCode)" Codepage="1252" Language="1033" Manufacturer="Bla Corporation"
Name="Bla" UpgradeCode="$(var.MyUpgradeCode)" Version="$(var.MyProductVersion)">
<Package Comments="Contact: Refael Sheinker, refael.sheinker#bla.com." Description="Bla"
InstallerVersion="500"
Compressed="yes"
InstallScope="perMachine"
Keywords="Installer,MSI,Database" Languages="1033" Manufacturer="Bla Corporation" Platform="x64" />
<Media Id="1" Cabinet="my_application.cab" EmbedCab="yes" />
<!-- Major upgrade -->
<Upgrade Id="$(var.MyUpgradeCode)">
<!-- Downgrade Protection -->
<UpgradeVersion Minimum="$(var.MyProductVersion)" OnlyDetect="yes" IncludeMinimum="yes" Property="DOWNGRADE_DETECTED" />
<!-- Major Upgrade Configuration -->
<UpgradeVersion IncludeMinimum="no" Maximum="$(var.MyProductVersion)" IncludeMaximum="no" MigrateFeatures="yes" Property="UPGRADE_DETECTED" />
</Upgrade>
<!-- Major Upgrade: Schedule RemoveExistingProducts -->
<InstallExecuteSequence>
<!-- Potential scheduling (after): InstallValidate, InstallInitialize, InstallExecute, InstallExecuteAgain, InstallFinalize -->
<RemoveExistingProducts After="InstallInitialize" />
</InstallExecuteSequence>
<!--Launch Condition: Abort setup if higher version found-->
<Condition Message="!(loc.NewerVersionDetected)">
NOT DOWNGRADE_DETECTED
</Condition>
<Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONROOTDIRECTORY" />
<UIRef Id="WixUI_InstallDir" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="PROGRAMFILESSUBDIR" Name="Bla">
<Directory Id="APPLICATIONROOTDIRECTORY" Name="BlaInternal" />
</Directory>
</Directory>
</Directory>
<DirectoryRef Id="APPLICATIONROOTDIRECTORY">
<Component Id="Test.ini" Guid="PUT-GUID-HERE" Win64="yes" Feature="MainApplication">
<CreateFolder Directory="APPLICATIONROOTDIRECTORY" />
<IniFile Id="SomeSetting" Action="addLine" Directory="APPLICATIONROOTDIRECTORY" Key="Setting1" Name="Test.ini" Section="MySection" Value="Some Setting" />
<IniFile Id="OtherSetting" Action="addLine" Directory="APPLICATIONROOTDIRECTORY" Key="Setting2" Name="Test.ini" Section="MySection" Value="Other Setting" />
</Component>
</DirectoryRef>
<Feature Id="MainApplication" Title="TenLira" Level="1">
<!--<ComponentRef Id="tenlira.ini" />-->
</Feature>
</Product>
</Wix>
Now the !(loc.NewerVersionDetected) has to be explained. This is a localized string (for delivering your setup in different languages). To use it, right click your WiX project in Visual Studio and go: Add New Item... => Localization File => Add. As the localization file is added, your output MSI will also now go into a en-us folder under your main output location (Debug or Release).
In the localization file, add:
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="NewerVersionDetected">A later version of [ProductName] is already installed.</String>
</WixLocalization>
And you should now be able to add new strings to this file and easily translate your whole setup using such language files.
Also add the WiX GUI extension. Right click "References". Add Reference... => Browse to WixUIExtension.dll => Double click this file, and press OK. Normal folder to find the file is: C:\Program Files (x86)\WiX Toolset v3.11\bin.
INI-Files
I just want to mention that INI files should ideally be installed via the IniFile table (entries are treated as atomic key-value pairs allowing advanced merging of keys and values for existing INI files), and not via the File table (the file is treated as a regular file either overwriting the whole existing file or leaving it in place - not enforcing any new values). The WiX element corresponding to the MSI IniFile table is naturally the IniFile element.
An ad-hoc sample:
<Component Id="Test.ini" Guid="PUT-GUID-HERE" Win64="yes" Feature="MainApplication">
<CreateFolder Directory="APPLICATIONROOTDIRECTORY" />
<IniFile Id="SomeSetting" Action="addLine" Directory="APPLICATIONROOTDIRECTORY" Key="Setting1" Name="Test.ini" Section="MySection" Value="Some Setting" />
<IniFile Id="OtherSetting" Action="addLine" Directory="APPLICATIONROOTDIRECTORY" Key="Setting2" Name="Test.ini" Section="MySection" Value="Other Setting" />
</Component>
Links:
Adding entries to MSI UpgradeTable to remove related products
It does a major upgrade because both MSIs have the same UpgradeCode and you have now specified AllowSameVersionUpgrades, so it does the upgrade where it didn't before.
Your build generates a new ProductCode every time, so each MSI is a new product, so you will get it installed twice if it doesn't do an upgrade and once if it does. You may have some assumption about the way upgrades work that you haven't spelled out.
I had the same problem where Version is same, but the Id is different creating multiple entries in Add/Remove programs.
My simple fix was to set AllowSameVersionUpgrades="yes".
<MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of $(var.ServiceName) is already installed." />
I'm following a Wix tutorial that recommends creating a setup library project and writing this in Product.wxs:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="HelloWix" Language="1033" Version="1.0.0" Manufacturer="SoftAgility" UpgradeCode="2CA1BA0A-99C0-445C-8C72-083431F757DE">
<Package Description="Simple Demo" Compressed="yes" />
<MediaTemplate EmbedCab="yes"/>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="HelloWix" />
</Directory>
</Directory>
<Component Id="TextFileComponent" Directory="INSTALLFOLDER">
<File Source="C:\temp\MyFile.txt" />
</Component>
<Feature Id="MainFeature" Title="HelloWix" Level="1">
<ComponentRef Id="TextFileComponent" />
</Feature>
</Product>
</Wix>
This project is meant to setup a simple hello world type of setup in order to show the simplest piece of code needed to get a working installation package, with minimal requirements. Build is successful. Question is: where is the .msi generated?
Well, solution is I unloaded the wixproj and changed this line:
<OutputType>Library</OutputType>
to
<OutputType>Package</OutputType>
If you know of other methods, please add them. Thanks!
I created a library in WiX with a single ComponentGroup, Component and File, this way:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<ComponentGroup Id="MyComponentGroup" Directory="WindowsFolder">
<Component Id="MyComponent" Guid="...">
<File Id="MyFile" Source="file.txt" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
Then I created a WiX project, added a reference to this library (as a project reference) and I'm using this code:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="My Product" Language="1033"
Version="1.0.0.0" Manufacturer="Myself"
UpgradeCode="xxxxx">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<Feature Id="Feature_Product" Title="Main Product"
Level="1" Absent="disallow" Description="Core functionality.">
<ComponentGroupRef Id="MyComponentGroup" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="WindowsFolder" />
</Directory>
</Fragment>
</Wix>
But I'm receiving an error of Unresolved reference. Is there anything I'm missing? Or do libraries only work with UI and not files?
EDIT:
I added the complete code.
And it works if I use a FeatureRef and I move the Feature to the library, but I would like to keep the Feature in the main project and only have the Component in the library.
You haven't posted your entire error, but the error I get when using your example is:
Unresolved reference to symbol 'Media:1' in section 'Fragment:'
The unresolved reference is a missing media element; the File needs to be stored in a cabinet and it defaults to File/#DiskId="1" which is why it's looking for "Media:1". You need to tell WiX where to store the installation files. If you're using WiX 3.7, add this into your <Package> element:
<MediaTemplate EmbedCab="yes" />
If you're using an earlier version:
<Media Id="1" Cabinet="Cab1.cab" EmbedCab="yes" />
This is an example of what I'm trying to do:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="SetupProject1" Language="1033" Version="1.0.0.0" Manufacturer="MySoftware" UpgradeCode="d2192e52-f4f6-461c-9d8e-eb66067df09a">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="SetupProject1" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="[Manufacturer] SetupProject1" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
<Component Id="CMP_NEW">
<File Id="FILE_NEW" Source="New Text Document.txt" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
The folder that created in ProgramFiles is [Manufacturer] SetupProject1 and not MySoftware SetupProject1. Do I do it right? If not, how can I do it please?
Create a variable $(var.Manufacturer) and use it like so:
Define like this: <?define Manufacturer = "MySoftware"?>
<Product Id="*" Name="SetupProject1" Language="1033" Version="1.0.0.0" Manufacturer="$(var.Manufacturer)" UpgradeCode="d2192e52-f4f6-461c-9d8e-eb66067df09a">
<Directory Id="INSTALLFOLDER" Name="$(var.Manufacturer) SetupProject1" />
That will work..:)
The problem I have is that you can't set the preprocessor variable to a value defined during the installation, say in the UI. I'm trying to set the directory name to a property that is given by the User.
So far the key way I've seen to get around this is to use a custom action to change the Directory datatable to the value of the property you would otherwise reference. Just make sure you time the custom action so that it doesn't happen before the directory is normally set, otherwise it might be overwritten.
I'm still looking for something a little prettier however.
I am struggling with creating a patch purely using WIX and I was hoping if someone could guide me in the right direction.
I have a few hundred source files and I run heat against them to create a harvest file followed by creating a package using candle and light.
I need to change a few configuration files and I create a 2nd package with the changes.
Using Torch and pyro I create the .wixmst file and then when trying to create the msp file, pyro complains with the following error.
pyro.exe : error PYRO0252 : No valid transforms were provided to attach to the patch. Check to make sure the transforms you passed on the command line have a matching baseline authored in the patch. Also, make sure there are differences between your target and upgrade.
my question really is: what should patch.wxs contain?
Here is what my patch.wxs looks like:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Patch
AllowRemoval="yes"
Manufacturer="sample llc"
MoreInfoURL="sample.com"
DisplayName="Env Patch"
Description="Env Specfic Patch"
Classification="Update"
>
<Media Id="5000" Cabinet="RTM.cab">
<PatchBaseline Id="RTM" />
</Media>
<PatchFamilyRef Id="EnvPatchFamily" />
</Patch>
<Fragment>
<PatchFamily Id='EnvPatchFamily' Version='1.0.0.0' ProductCode="PUT-GUID-HERE" Supersede='yes' >
**********************************************
What component Ref should I put in here
heat creates a component group and I can't
put ComponentGroupRef in here
**********************************************
</PatchFamily>
</Fragment>
</Wix>
I am using Wix patching as described in this link:
http://wix.sourceforge.net/manual-wix3/wix_patching.htm
However, it doesn't consider source wix file created using heat.
Can someone tell me what am I doing wrong here?
Hitesh,
For me heat creates a component group like this:
<Fragment>
<ComponentGroup Id="MyFiles">
<ComponentRef Id="cmp2AA1A30564C621322ECB3CDD70B1C03C" />
<ComponentRef Id="cmp788C978F16E473D4FD85720B5B75C207" />
</ComponentGroup>
</Fragment>
heat command:
"%WIX%\bin\heat.exe" dir slndir\bin\Release -cg MyFiles -gg -scom -sreg -sfrag -srd -dr INSTALLDIR -out ..\Wix\MyFiles.wxs -var var.BinOutputPath -nologo -v -ke -t wixtransform.xsl
And in patch.wxs:
<Fragment>
<PatchFamily Id='ProductPatchFamily' Version='1.3.0.0' Supersede='yes'>
<ComponentRef Id="cmp2AA1A30564C621322ECB3CDD70B1C03C" />
<ComponentRef Id="cmp788C978F16E473D4FD85720B5B75C207" />
</PatchFamily>
</Fragment>
Take care: there is no ProductCode attribute in PatchFamily tag
I would also like to mention that PatchFamily elements are optional when building a patch, and are intended to allow fine grained control over exactly what will get patched. In most cases I find that I want to include all differences between 2 versions of an MSI when building a patch, in which case I omit the PatchFamily altogether. In your case, the resulting patch WXS would look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Patch
AllowRemoval="yes"
Manufacturer="sample llc"
MoreInfoURL="sample.com"
DisplayName="Env Patch"
Description="Env Specfic Patch"
Classification="Update"
>
<Media Id="5000" Cabinet="RTM.cab">
<PatchBaseline Id="RTM" />
</Media>
</Patch>
</Wix>
I hope this answer helps anyone with a similar question, that is not wanting to manually construct patch families, not wanting to manually includeComponentRef every time they need to build a patch.
I faced the same issue, the fix for this error is to add the GUID to the component and it should remain same for both the versions of msi.
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="WixPatch" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="WixPatch" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER" >
<Component Id="File1" **Guid="3A64BE7A-BBEC-40AD-8319-45C602734146"**>
<File Source="D:\V2\File1.txt" Name="File1" KeyPath="yes" DiskId="1" Id="F1"/>
</Component>
</ComponentGroup>
</Fragment>