Ok. This has solved the problem for me.
Force linker to be called with named bind paths
I added the following item group with items pointing to the two packages.
<ItemGroup>
<BindInputPaths Include="..\MyProject1\obj\Debug\Package\PackageTmp">
<BindName>MyProject1</BindName>
<InProject>false</InProject>
</BindInputPaths>
<BindInputPaths Include="..\MyProject2\obj\Debug\Package\PackageTmp">
<BindName>MyProject2</BindName>
<InProject>false</InProject>
</BindInputPaths>
</ItemGroup>
Remove the item group in the pre-build step containing the LinkerBindInputPaths element.
Verify this in the Visual Studio output window. The Light.exe command line should now have named bind paths.
-b "MyProject1=D:\Projects\...\MyProject1\obj\Debug\Package\PackageTmp"
-b "MyProject2=D:\Projects\...\MyProject2\obj\Debug\Package\PackageTmp"
Use named bind paths in harvest output
I tried to get the HeatDirectory task to generate this output but in the end just used a XSLT transformation to update the SourceDir part to use the bindpath variable.
Update Wix project to direct Heat output to tmp file prior to piping through XSLT transform.
<HeatDirectory OutputFile="%(ProjectReference.Filename)-temp.wxs"
Directory="%(ProjectReference.RootDir)%(ProjectReference.Directory)obj\$(Configuration)\Package\PackageTmp\"
DirectoryRefId="%(ProjectReference.Name)InstallFolder"
ComponentGroupName="%(ProjectReference.Name)_Project"
AutogenerateGuids="true"
SuppressCom="true"
SuppressFragments="true"
SuppressRegistry="true"
SuppressRootDirectory="true"
ToolPath="$(WixToolPath)"
Condition="'%(ProjectReference.WebProject)'=='True'" />
<XslTransformation XmlInputPaths="%(ProjectReference.Filename)-temp.wxs"
XslInputPath="XslTransform.xslt"
OutputPaths="%(ProjectReference.Filename).wxs"
Condition="'%(ProjectReference.WebProject)'=='True'" />
Include the XSLT Transformation into the Wix project (XslTransform.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="@Source['SourceDir']" >
<xsl:attribute name="Source">
<xsl:variable name="projectName">
<xsl:value-of select="/wix:Wix/wix:Fragment/wix:ComponentGroup/@Id"/>
</xsl:variable>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="." />
<xsl:with-param name="replace" select="'SourceDir'" />
<xsl:with-param name="by" select="concat('!(bindpath.',$projectName,')')" />
</xsl:call-template>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
The Heat output should then use the ComponentGroup name as the bind path variable.
<Component Id="..." Guid="*">
<File Id="..." KeyPath="yes" Source="!(bindpath.MyProject1)\Default.aspx" />
</Component>
Once built I verified the MSI in Orca. Before the fix Orca listed two versions of my files both with the same byte size. Now it lists both versions with their proper sizes.