diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Windows.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Windows.targets
index 19f9cdcf9a65..86121139d05b 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Windows.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Windows.targets
@@ -373,4 +373,36 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+ <_NormalizedIntermediateOutputPath>$([MSBuild]::EnsureTrailingSlash($([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))))
+
+
+ <_GeneratedXbfFiles Update="@(_GeneratedXbfFiles)"
+ Condition="'%(Link)' == '' and !$([MSBuild]::ValueOrDefault('%(FullPath)', '').StartsWith($([MSBuild]::EnsureTrailingSlash($(MSBuildProjectDirectory)))))">
+ $([MSBuild]::MakeRelative($(_NormalizedIntermediateOutputPath), %(FullPath)))
+
+
+
diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAWindowsDesktopProject.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAWindowsDesktopProject.cs
index c255a124a38e..2d44c23c97fd 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAWindowsDesktopProject.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildAWindowsDesktopProject.cs
@@ -864,5 +864,82 @@ private string GetPropertyValue(TestAsset testAsset, string propertyName)
return getValueCommand.GetValues().Single();
}
+
+ // Regression test for https://github.com/dotnet/sdk/issues/53556
+ // When UseArtifactsOutput=true, IntermediateOutputPath is moved outside the project directory.
+ // The _FixGeneratedXbfFilesLink target should set Link metadata on _GeneratedXbfFiles items
+ // to preserve subdirectory paths in resources.pri (e.g., "Resources\Styles.xbf" not "Styles.xbf").
+ [WindowsOnlyFact]
+ public void ItSetsCorrectLinkMetadataOnGeneratedXbfFilesWhenUsingArtifactsOutput()
+ {
+ const string targetFramework = "net10.0-windows10.0.26100.0";
+
+ var testProject = new TestProject()
+ {
+ Name = "UwpXbfLinkTest",
+ ProjectSdk = "Microsoft.NET.Sdk",
+ TargetFrameworks = targetFramework
+ };
+ testProject.AdditionalProperties["UseUwp"] = "true";
+ testProject.AdditionalProperties["UseUwpTools"] = "false";
+
+ var testAsset = TestAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project =>
+ {
+ // Inject a target that populates _GeneratedXbfFiles with simulated XBF files
+ // in subdirectories of IntermediateOutputPath, as would happen with UseArtifactsOutput.
+ // This simulates what the XAML compiler (MarkupCompilePass2) normally produces.
+ var injectTarget = XElement.Parse("""
+
+
+ <_GeneratedXbfFiles Include="$(IntermediateOutputPath)Resources\Styles.xbf" />
+ <_GeneratedXbfFiles Include="$(IntermediateOutputPath)Views\Home.xbf" />
+ <_GeneratedXbfFiles Include="$(IntermediateOutputPath)App.xbf" />
+
+
+ """);
+ project.Root.Add(injectTarget);
+ });
+
+ // Write a Directory.Build.props that enables artifacts output, which moves
+ // IntermediateOutputPath outside the project directory.
+ File.WriteAllText(Path.Combine(testAsset.Path, "Directory.Build.props"),
+ """
+
+
+ true
+
+
+ """);
+
+ var getValuesCommand = new GetValuesCommand(
+ Log,
+ Path.Combine(testAsset.Path, testProject.Name),
+ targetFramework,
+ "_GeneratedXbfFiles",
+ GetValuesCommand.ValueType.Item)
+ {
+ ShouldRestore = false,
+ DependsOnTargets = "_FixGeneratedXbfFilesLink"
+ };
+ getValuesCommand.MetadataNames.Add("Link");
+
+ getValuesCommand.Execute().Should().Pass();
+
+ var items = getValuesCommand.GetValuesWithMetadata();
+
+ // Items in subdirectories should have Link metadata that preserves the subdirectory path.
+ // Without the fix, these would be empty or just the filename (e.g., "Styles.xbf").
+ var stylesItem = items.Single(i => Path.GetFileName(i.value).Equals("Styles.xbf", StringComparison.OrdinalIgnoreCase));
+ stylesItem.metadata["Link"].Should().Be(@"Resources\Styles.xbf");
+
+ var homeItem = items.Single(i => Path.GetFileName(i.value).Equals("Home.xbf", StringComparison.OrdinalIgnoreCase));
+ homeItem.metadata["Link"].Should().Be(@"Views\Home.xbf");
+
+ // Root-level XBF item should have Link metadata with just the filename.
+ var appItem = items.Single(i => Path.GetFileName(i.value).Equals("App.xbf", StringComparison.OrdinalIgnoreCase));
+ appItem.metadata["Link"].Should().Be("App.xbf");
+ }
}
}