diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/RunCsWinRTGenerator.cs b/src/Tasks/Microsoft.NET.Build.Tasks/RunCsWinRTGenerator.cs
new file mode 100644
index 000000000000..3c28ee32aa74
--- /dev/null
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/RunCsWinRTGenerator.cs
@@ -0,0 +1,262 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.NET.Build.Tasks;
+
+///
+/// The custom MSBuild task that invokes the 'cswinrtgen' tool.
+///
+public sealed class RunCsWinRTGenerator : ToolTask
+{
+ ///
+ /// Gets or sets the paths to assembly files that are reference assemblies, representing
+ /// the entire surface area for compilation. These assemblies are the full set of assemblies
+ /// that will contribute to the interop .dll being generated.
+ ///
+ [Required]
+ public ITaskItem[]? ReferenceAssemblyPaths { get; set; }
+
+ ///
+ /// Gets or sets the path to the output assembly that was produced by the build (for the current project).
+ ///
+ ///
+ /// This property is an array, but it should only ever receive a single item.
+ ///
+ [Required]
+ public ITaskItem[]? OutputAssemblyPath { get; set; }
+
+ ///
+ /// Gets or sets the directory where the generated interop assembly will be placed.
+ ///
+ [Required]
+ public string? InteropAssemblyDirectory { get; set; }
+
+ ///
+ /// Gets or sets the directory where the debug repro will be produced.
+ ///
+ /// If not set, no debug repro will be produced.
+ public string? DebugReproDirectory { get; set; }
+
+ ///
+ /// Gets or sets the tools directory where the 'cswinrtgen' tool is located.
+ ///
+ [Required]
+ public string? CsWinRTToolsDirectory { get; set; }
+
+ ///
+ /// Gets or sets the architecture of 'cswinrtgen' to use.
+ ///
+ ///
+ /// If not set, the architecture will be determined based on the current process architecture.
+ ///
+ public string? CsWinRTToolsArchitecture { get; set; }
+
+ ///
+ /// Gets or sets whether to use Windows.UI.Xaml projections.
+ ///
+ /// If not set, it will default to (i.e. using Microsoft.UI.Xaml projections).
+ public bool UseWindowsUIXamlProjections { get; set; } = false;
+
+ ///
+ /// Gets whether to validate the assembly version of WinRT.Runtime.dll, to ensure it matches the generator.
+ ///
+ public bool ValidateWinRTRuntimeAssemblyVersion { get; set; } = true;
+
+ ///
+ /// Gets whether to validate that any references to WinRT.Runtime.dll version 2 are present across any assemblies.
+ ///
+ public bool ValidateWinRTRuntimeDllVersion2References { get; set; } = true;
+
+ ///
+ /// Gets whether to enable incremental generation (i.e. with a cache file on disk saving the full set of types to generate).
+ ///
+ public bool EnableIncrementalGeneration { get; set; } = true;
+
+ ///
+ /// Gets whether to treat warnings coming from 'cswinrtgen' as errors (regardless of the global 'TreatWarningsAsErrors' setting).
+ ///
+ public bool TreatWarningsAsErrors { get; set; } = false;
+
+ ///
+ /// Gets or sets the maximum number of parallel tasks to use for execution.
+ ///
+ /// If not set, the default will match the number of available processor cores.
+ public int MaxDegreesOfParallelism { get; set; } = -1;
+
+ ///
+ /// Gets or sets additional arguments to pass to the tool.
+ ///
+ public ITaskItem[]? AdditionalArguments { get; set; }
+
+ ///
+ protected override string ToolName => "cswinrtgen.exe";
+
+ ///
+ /// Gets the effective item spec for the output assembly.
+ ///
+ private string EffectiveOutputAssemblyItemSpec => OutputAssemblyPath![0].ItemSpec;
+
+ ///
+#if NET10_0_OR_GREATER
+ [MemberNotNullWhen(true, nameof(ReferenceAssemblyPaths))]
+ [MemberNotNullWhen(true, nameof(OutputAssemblyPath))]
+ [MemberNotNullWhen(true, nameof(InteropAssemblyDirectory))]
+ [MemberNotNullWhen(true, nameof(CsWinRTToolsDirectory))]
+#endif
+ protected override bool ValidateParameters()
+ {
+ if (!base.ValidateParameters())
+ {
+ return false;
+ }
+
+ if (ReferenceAssemblyPaths is not { Length: > 0 })
+ {
+ Log.LogWarning("Invalid 'ReferenceAssemblyPaths' input(s).");
+
+ return false;
+ }
+
+ if (OutputAssemblyPath is not { Length: 1 })
+ {
+ Log.LogWarning("Invalid 'OutputAssemblyPath' input.");
+
+ return false;
+ }
+
+ if (InteropAssemblyDirectory is null || !Directory.Exists(InteropAssemblyDirectory))
+ {
+ Log.LogWarning("Generated assembly directory '{0}' is invalid or does not exist.", InteropAssemblyDirectory);
+
+ return false;
+ }
+
+ if (DebugReproDirectory is not null && !Directory.Exists(DebugReproDirectory))
+ {
+ Log.LogWarning("Debug repro directory '{0}' is invalid or does not exist.", DebugReproDirectory);
+
+ return false;
+ }
+
+ if (CsWinRTToolsDirectory is null || !Directory.Exists(CsWinRTToolsDirectory))
+ {
+ Log.LogWarning("Tools directory '{0}' is invalid or does not exist.", CsWinRTToolsDirectory);
+
+ return false;
+ }
+
+ if (CsWinRTToolsArchitecture is not null &&
+ !CsWinRTToolsArchitecture.Equals("x86", StringComparison.OrdinalIgnoreCase) &&
+ !CsWinRTToolsArchitecture.Equals("x64", StringComparison.OrdinalIgnoreCase) &&
+ !CsWinRTToolsArchitecture.Equals("arm64", StringComparison.OrdinalIgnoreCase) &&
+ !CsWinRTToolsArchitecture.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase))
+ {
+ Log.LogWarning("Tools architecture '{0}' is invalid (it must be 'x86', 'x64', 'arm64', or 'AnyCPU').", CsWinRTToolsArchitecture);
+
+ return false;
+ }
+
+ // The degrees of parallelism matches the semantics of the 'MaxDegreesOfParallelism' property of 'Parallel.For'. That is, it must either be exactly '-1', which is a special
+ // value meaning "use as many parallel threads as the runtime deems appropriate", or it must be set to a positive integer, to explicitly control the number of threads.
+ // See: https://learn.microsoft.com/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism#system-threading-tasks-paralleloptions-maxdegreeofparallelism.
+ if (MaxDegreesOfParallelism is not (-1 or > 0))
+ {
+ Log.LogWarning("Invalid 'MaxDegreesOfParallelism' value. It must be '-1' or greater than '0' (but was '{0}').", MaxDegreesOfParallelism);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ [SuppressMessage("Style", "IDE0072", Justification = "We always use 'x86' as a fallback for all other CPU architectures.")]
+ protected override string GenerateFullPathToTool()
+ {
+ string? effectiveArchitecture = CsWinRTToolsArchitecture;
+
+ // Special case for when 'AnyCPU' is specified (mostly for testing scenarios).
+ // We just reuse the exact input directory and assume the architecture matches.
+ // This makes it easy to run the task against a local build of 'cswinrtgen'.
+ if (effectiveArchitecture?.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) is true)
+ {
+ return Path.Combine(CsWinRTToolsDirectory!, ToolName);
+ }
+
+ // If the architecture is not specified, determine it based on the current process architecture
+ effectiveArchitecture ??= RuntimeInformation.ProcessArchitecture switch
+ {
+ Architecture.X64 => "x64",
+ Architecture.Arm64 => "arm64",
+ _ => "x86"
+ };
+
+ // The tool is inside an architecture-specific subfolder, as it's a native binary
+ string architectureDirectory = $"win-{effectiveArchitecture}";
+
+ return Path.Combine(CsWinRTToolsDirectory!, architectureDirectory, ToolName);
+ }
+
+ ///
+ protected override string GenerateResponseFileCommands()
+ {
+ StringBuilder args = new();
+
+ IEnumerable referenceAssemblyPaths = ReferenceAssemblyPaths!.Select(static path => path.ItemSpec);
+ string referenceAssemblyPathsArg = string.Join(",", referenceAssemblyPaths);
+
+ AppendResponseFileCommand(args, "--reference-assembly-paths", referenceAssemblyPathsArg);
+ AppendResponseFileCommand(args, "--output-assembly-path", EffectiveOutputAssemblyItemSpec);
+ AppendResponseFileCommand(args, "--generated-assembly-directory", InteropAssemblyDirectory!);
+ AppendResponseFileOptionalCommand(args, "--debug-repro-directory", DebugReproDirectory);
+ AppendResponseFileCommand(args, "--use-windows-ui-xaml-projections", UseWindowsUIXamlProjections.ToString());
+ AppendResponseFileCommand(args, "--validate-winrt-runtime-assembly-version", ValidateWinRTRuntimeAssemblyVersion.ToString());
+ AppendResponseFileCommand(args, "--validate-winrt-runtime-dll-version-2-references", ValidateWinRTRuntimeDllVersion2References.ToString());
+ AppendResponseFileCommand(args, "--enable-incremental-generation", EnableIncrementalGeneration.ToString());
+ AppendResponseFileCommand(args, "--treat-warnings-as-errors", TreatWarningsAsErrors.ToString());
+ AppendResponseFileCommand(args, "--max-degrees-of-parallelism", MaxDegreesOfParallelism.ToString());
+
+ // Add any additional arguments that are not statically known
+ foreach (ITaskItem additionalArgument in AdditionalArguments ?? [])
+ {
+ _ = args.AppendLine(additionalArgument.ItemSpec);
+ }
+
+ return args.ToString();
+ }
+
+ ///
+ /// Appends a command line argument to the response file arguments, with the right format.
+ ///
+ /// The command line arguments being built.
+ /// The command name to append.
+ /// The command value to append.
+ private static void AppendResponseFileCommand(StringBuilder args, string commandName, string commandValue)
+ {
+ _ = args.Append($"{commandName} ").AppendLine(commandValue);
+ }
+
+ ///
+ /// Appends an optional command line argument to the response file arguments, with the right format.
+ ///
+ /// The command line arguments being built.
+ /// The command name to append.
+ /// The optional command value to append.
+ /// This method will not append the command if is .
+ private static void AppendResponseFileOptionalCommand(StringBuilder args, string commandName, string? commandValue)
+ {
+ if (commandValue is not null)
+ {
+ AppendResponseFileCommand(args, commandName, commandValue);
+ }
+ }
+}
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 644d0faa4658..85c07b37e009 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
@@ -167,4 +167,178 @@ Copyright (c) .NET Foundation. All rights reserved.
and '$(UseUwp)' == 'true' ">
+
+
+
+
+
+ true
+ false
+
+
+ $(IntermediateOutputPath)
+ true
+ true
+ true
+ false
+ -1
+ High
+ High
+ true
+
+
+ <_CsWinRTGeneratorInteropAssemblyName>WinRT.Interop
+ <_CsWinRTGeneratorInteropAssemblyFileName>$(_CsWinRTGeneratorInteropAssemblyName).dll
+ <_CsWinRTGeneratorInteropAssemblyPath>$([MSBuild]::NormalizePath('$(CsWinRTGeneratorInteropAssemblyDirectory)', '$(_CsWinRTGeneratorInteropAssemblyFileName)'))
+
+
+ <_RunCsWinRTGeneratorPropertyInputsCachePath Condition="'$(_RunCsWinRTGeneratorPropertyInputsCachePath)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).cswinrtgen.cache
+ <_RunCsWinRTGeneratorPropertyInputsCachePath>$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '$(_RunCsWinRTGeneratorPropertyInputsCachePath)'))
+
+
+
+
+
+
+
+ <_WinRTRuntimeDllReferencePath
+ Include="@(ReferencePath)"
+ Condition="'%(Filename)%(Extension)' == 'WinRT.Runtime.dll'" />
+
+
+
+
+ <_WinRTRuntimeDllDirectory>$([System.IO.Path]::GetDirectoryName('%(_WinRTRuntimeDllReferencePath.FullPath)'))
+ <_CsWinRTOrTargetingPackLibDirectory>$([System.IO.Path]::GetDirectoryName('$(_WinRTRuntimeDllDirectory)'))
+ <_CsWinRTOrTargetingPackRootDirectory>$([System.IO.Path]::GetDirectoryName('$(_CsWinRTOrTargetingPackLibDirectory)'))
+ <_CsWinRTOrTargetingPackToolsDirectory>$([System.IO.Path]::Combine('$(_CsWinRTOrTargetingPackRootDirectory)', 'tools'))
+
+ $(CsWinRTToolsDirectory)
+ $(_CsWinRTOrTargetingPackToolsDirectory)
+
+
+
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTEffectiveToolsDirectory)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTToolsArchitecture)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorInteropAssemblyDirectory)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorDebugReproDirectory)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTUseWindowsUIXamlProjections)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeAssemblyVersion)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeDllVersion2References)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorEnableIncrementalGeneration)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorTreatWarningsAsErrors)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorMaxDegreesOfParallelism)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="@(CsWinRTGeneratorAdditionalArgument)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorStandardOutputImportance)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorStandardErrorImportance)" />
+ <_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorLogStandardErrorAsError)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(_CsWinRTGeneratorInteropAssemblyName)
+ .NETCoreApp
+ true
+ true
+ true
+ _RunCsWinRTGenerator
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+ <_SourceItemsToCopyToOutputDirectory
+ Include="@(CsWinRTGeneratorInteropAssemblyPath)"
+ TargetPath="$(_CsWinRTGeneratorInteropAssemblyFileName)" />
+
+
+ <_SourceItemsToCopyToPublishDirectory
+ Include="@(CsWinRTGeneratorInteropAssemblyPath)"
+ TargetPath="$(_CsWinRTGeneratorInteropAssemblyFileName)" />
+
+
+
+
+
+
+