diff --git a/src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd b/src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd
index 186e3c18a53..83b48d2558c 100644
--- a/src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd
+++ b/src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd
@@ -1869,6 +1869,7 @@ elementFormDefault="qualified">
+
diff --git a/src/Tasks.UnitTests/AddToWin32Manifest_Tests.cs b/src/Tasks.UnitTests/AddToWin32Manifest_Tests.cs
new file mode 100644
index 00000000000..2de044bbddd
--- /dev/null
+++ b/src/Tasks.UnitTests/AddToWin32Manifest_Tests.cs
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Xml;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.UnitTests;
+using Microsoft.Build.Utilities;
+using Shouldly;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Build.Tasks.UnitTests
+{
+ public class AddToWin32Manifest_Tests
+ {
+ private static string TestAssetsRootPath { get; } = Path.Combine(
+ Path.GetDirectoryName(typeof(AddToWin32Manifest_Tests).Assembly.Location) ?? AppContext.BaseDirectory,
+ "TestResources",
+ "Manifests");
+
+ private readonly ITestOutputHelper _testOutput;
+
+ public AddToWin32Manifest_Tests(ITestOutputHelper testOutput) => _testOutput = testOutput;
+
+ [Theory]
+ [InlineData("testManifestWithInvalidSupportedArchs.manifest", false)]
+ [InlineData("testManifestWithApplicationDefined.manifest", true)]
+ [InlineData("testManifestSavesTheCurrentNodesPositions.manifest", true)]
+ [InlineData("testManifestNoPrefixes.manifest", true)]
+ [InlineData(null, true)]
+ public void ManifestPopulationCheck(string manifestName, bool expectedResult)
+ {
+ AddToWin32Manifest task = new AddToWin32Manifest()
+ {
+ BuildEngine = new MockEngine(_testOutput)
+ };
+
+ using (TestEnvironment env = TestEnvironment.Create())
+ {
+ var tempOutput = env.CreateFolder().Path;
+ task.OutputDirectory = tempOutput;
+ task.SupportedArchitectures = "amd64 arm64";
+ if (!string.IsNullOrEmpty(manifestName))
+ {
+ task.ApplicationManifest = new TaskItem(Path.Combine(TestAssetsRootPath, manifestName));
+ }
+
+ var result = task.Execute();
+
+ result.ShouldBe(expectedResult);
+
+ if (result)
+ {
+ string generatedManifest = task.ManifestPath;
+ string expectedManifest = Path.Combine(TestAssetsRootPath, $"{manifestName ?? "default.win32manifest"}_expected");
+
+ XmlDocument expectedDoc = new XmlDocument();
+ XmlDocument actualDoc = new XmlDocument();
+
+ expectedDoc.Load(expectedManifest);
+ actualDoc.Load(generatedManifest);
+
+ expectedDoc.OuterXml.ShouldBe(actualDoc.OuterXml);
+ expectedDoc.InnerXml.ShouldBe(actualDoc.InnerXml);
+ }
+ }
+ }
+
+ [SupportedOSPlatform("windows")]
+ [WindowsOnlyTheory]
+ [InlineData(null, true)]
+ [InlineData("buildIn.manifest", true)]
+ [InlineData("testManifestWithValidSupportedArchs.manifest", true)]
+ public void E2EScenarioTests(string manifestName, bool expectedResult)
+ {
+ using (TestEnvironment env = TestEnvironment.Create())
+ {
+ var outputPath = env.CreateFolder().Path;
+ string projectContent = @$"
+
+
+
+
+ AnyCPU
+ v4.7.2
+ Library
+ true
+ false
+ {(!string.IsNullOrEmpty(manifestName) ? $"{manifestName}" : "")}
+ {outputPath}
+
+
+
+
+
+
+ ";
+
+ var projectFolder = env.CreateFolder();
+ var projectFile = env.CreateFile(projectFolder, "test.csproj", projectContent).Path;
+
+ // copy application manifest
+ if (!string.IsNullOrEmpty(manifestName))
+ {
+ File.Copy(Path.Combine(TestAssetsRootPath, manifestName), Path.Combine(projectFolder.Path, manifestName));
+ }
+
+ Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFile, touchProject: false);
+
+ bool result = project.Build(new MockLogger(_testOutput));
+ result.ShouldBe(expectedResult);
+
+ // #2 - represents the name for native resource (Win 32 resource), #24 - the type (Manifest)
+ byte[]? actualManifestBytes = AssemblyNativeResourceManager.GetResourceFromExecutable(Path.Combine(outputPath, "test.dll"), "#2", "#24");
+
+ // check manifest content
+ if (actualManifestBytes != null)
+ {
+ string expectedManifest = Path.Combine(TestAssetsRootPath, $"{manifestName ?? "default.win32manifest"}_expected");
+
+ XmlDocument expectedDoc = new XmlDocument();
+ XmlDocument actualDoc = new XmlDocument();
+
+ expectedDoc.Load(expectedManifest);
+ using (MemoryStream stream = new MemoryStream(actualManifestBytes))
+ {
+ actualDoc.Load(stream);
+ }
+
+ NormalizeLineEndings(expectedDoc.OuterXml).ShouldBe(NormalizeLineEndings(actualDoc.OuterXml));
+ NormalizeLineEndings(expectedDoc.InnerText).ShouldBe(NormalizeLineEndings(actualDoc.InnerText));
+ }
+ }
+
+ static string NormalizeLineEndings(string input) => input.Replace("\r\n", "\n").Replace("\r", "\n");
+ }
+
+ [SupportedOSPlatform("windows")]
+ internal sealed class AssemblyNativeResourceManager
+ {
+ public enum LoadLibraryFlags : uint { LOAD_LIBRARY_AS_DATAFILE = 2 };
+
+ [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public static extern IntPtr LoadLibrary(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr LockResource(IntPtr hResData);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
+
+ public static byte[]? GetResourceFromExecutable(string assembly, string lpName, string lpType)
+ {
+ IntPtr hModule = LoadLibrary(assembly, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);
+ try
+ {
+ if (hModule != IntPtr.Zero)
+ {
+ IntPtr hResource = FindResource(hModule, lpName, lpType);
+ if (hResource != IntPtr.Zero)
+ {
+ uint resSize = SizeofResource(hModule, hResource);
+ IntPtr resData = LoadResource(hModule, hResource);
+ if (resData != IntPtr.Zero)
+ {
+ byte[] uiBytes = new byte[resSize];
+ IntPtr ipMemorySource = LockResource(resData);
+ Marshal.Copy(ipMemorySource, uiBytes, 0, (int)resSize);
+
+ return uiBytes;
+ }
+ }
+ }
+ }
+ finally
+ {
+ NativeMethodsShared.FreeLibrary(hModule);
+ }
+
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Tasks.UnitTests/MSBuildInternalMessage_Tests.cs b/src/Tasks.UnitTests/MSBuildInternalMessage_Tests.cs
new file mode 100644
index 00000000000..c03a9e2cde1
--- /dev/null
+++ b/src/Tasks.UnitTests/MSBuildInternalMessage_Tests.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Shared;
+using Microsoft.Build.UnitTests;
+using Shouldly;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Build.Tasks.UnitTests
+{
+ public class MSBuildInternalMessage_Tests
+ {
+ private readonly ITestOutputHelper _testOutput;
+
+ public MSBuildInternalMessage_Tests(ITestOutputHelper testOutput) => _testOutput = testOutput;
+
+ [Theory]
+ [InlineData(true, true, "CommonTarget.Prefer32BitAndPreferNativeArm64Enabled", false)]
+ [InlineData(false, false, "CommonTarget.PlatformIsAnyCPUAndPreferNativeArm64Enabled", true, new[] { "Release" })]
+ public void E2EScenarioTests(bool prefer32, bool isPlatformAnyCpu, string expectedResourceName, bool isNetWarningExpected, string[]? formatArgs = null)
+ {
+ using (TestEnvironment env = TestEnvironment.Create())
+ {
+ var outputPath = env.CreateFolder().Path;
+ string projectContent = @$"
+
+
+
+
+ {(isPlatformAnyCpu ? "AnyCPU" : "Release")}
+ Library
+ true
+ {(prefer32 ? "true" : "false")}
+
+
+
+
+
+
+ ";
+
+ var projectFile = env.CreateFile(env.CreateFolder(), "test.csproj", projectContent).Path;
+ Project project = ObjectModelHelpers.LoadProjectFileInTempProjectDirectory(projectFile, touchProject: false);
+
+ string expectedBuildMessage = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(expectedResourceName, formatArgs);
+ MockLogger logger = new MockLogger(_testOutput);
+
+ project.Build(logger);
+
+ if (isNetWarningExpected)
+ {
+ logger.Warnings[0].RawMessage.ShouldBe(expectedBuildMessage);
+ }
+ else
+ {
+ logger.Errors[0].RawMessage.ShouldBe(expectedBuildMessage);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj
index 2c1fca47574..81b5048f0f7 100644
--- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj
+++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj
@@ -152,6 +152,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest b/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest
new file mode 100644
index 00000000000..c8f1c0d76c7
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest_expected
new file mode 100644
index 00000000000..08194699912
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/buildIn.manifest_expected
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ amd64 arm64
+
+
+
+
+
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/default.win32manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/default.win32manifest_expected
new file mode 100644
index 00000000000..d48062a4e72
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/default.win32manifest_expected
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ amd64 arm64
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest b/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest
new file mode 100644
index 00000000000..70349317bc2
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest
@@ -0,0 +1,9 @@
+
+
+
+
+ true
+ true
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest_expected
new file mode 100644
index 00000000000..3c8e87cc046
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestNoPrefixes.manifest_expected
@@ -0,0 +1,10 @@
+
+
+
+
+ true
+ true
+ amd64 arm64
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest b/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest
new file mode 100644
index 00000000000..4c722f2df60
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+ test
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest_expected
new file mode 100644
index 00000000000..5213064fcba
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestSavesTheCurrentNodesPositions.manifest_expected
@@ -0,0 +1,11 @@
+
+
+
+
+ amd64 arm64
+
+
+
+ test
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest
new file mode 100644
index 00000000000..bc8eb93d98a
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest_expected
new file mode 100644
index 00000000000..7d2897b86ab
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithApplicationDefined.manifest_expected
@@ -0,0 +1,8 @@
+
+
+
+
+ amd64 arm64
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithInvalidSupportedArchs.manifest b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithInvalidSupportedArchs.manifest
new file mode 100644
index 00000000000..951ab4c1734
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithInvalidSupportedArchs.manifest
@@ -0,0 +1,8 @@
+
+
+
+
+ dummy
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest
new file mode 100644
index 00000000000..2662a0c776d
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest
@@ -0,0 +1,9 @@
+
+
+
+
+ amd64 arm64
+
+
+
+
diff --git a/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest_expected b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest_expected
new file mode 100644
index 00000000000..2662a0c776d
--- /dev/null
+++ b/src/Tasks.UnitTests/TestResources/Manifests/testManifestWithValidSupportedArchs.manifest_expected
@@ -0,0 +1,9 @@
+
+
+
+
+ amd64 arm64
+
+
+
+
diff --git a/src/Tasks/AddToWin32Manifest.cs b/src/Tasks/AddToWin32Manifest.cs
new file mode 100644
index 00000000000..50aeaba4434
--- /dev/null
+++ b/src/Tasks/AddToWin32Manifest.cs
@@ -0,0 +1,252 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Xml;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Shared;
+using Microsoft.Build.Tasks.Deployment.ManifestUtilities;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.Build.Tasks
+{
+ ///
+ /// Generates an application manifest or adds an entry to the existing one when PreferNativeArm64 property is true.
+ ///
+ public sealed class AddToWin32Manifest : TaskExtension
+ {
+ private const string supportedArchitectures = "supportedArchitectures";
+ private const string windowsSettings = "windowsSettings";
+ private const string application = "application";
+ private const string asmv3Prefix = "asmv3";
+ private const string DefaultManifestName = "default.win32manifest";
+ private const string WindowsSettingsNamespace = "http://schemas.microsoft.com/SMI/2024/WindowsSettings";
+
+ private string _outputDirectory = string.Empty;
+ private string _supportedArchitectures = string.Empty;
+ private string _generatedManifestFullPath = string.Empty;
+
+ ///
+ /// Represents the result of validating an application manifest.
+ ///
+ private enum ManifestValidationResult
+ {
+ ///
+ /// The manifest validation was successful.
+ ///
+ Success = 1,
+
+ ///
+ /// The manifest validation failed.
+ ///
+ Failure,
+
+ ///
+ /// The supported architectures exist in the manifest with the expected value.
+ ///
+ SupportedArchitecturesExists,
+ }
+
+ ///
+ /// Existing application manifest.
+ ///
+ public ITaskItem? ApplicationManifest { get; set; }
+
+ ///
+ /// Intermediate output directory.
+ ///
+ [Required]
+ public string OutputDirectory
+ {
+ get => _outputDirectory;
+ set => _outputDirectory = value ?? throw new ArgumentNullException(nameof(OutputDirectory));
+ }
+
+ ///
+ /// Value for supportedArchitectures node.
+ ///
+ [Required]
+ public string SupportedArchitectures
+ {
+ get => _supportedArchitectures;
+ set => _supportedArchitectures = value ?? throw new ArgumentNullException(nameof(SupportedArchitectures));
+ }
+
+ ///
+ /// Returns path to the generated manifest.
+ ///
+ [Output]
+ public string ManifestPath
+ {
+ get => _generatedManifestFullPath;
+ private set => _generatedManifestFullPath = value;
+ }
+
+ private string? GetManifestPath()
+ {
+ if (ApplicationManifest != null)
+ {
+ if (string.IsNullOrEmpty(ApplicationManifest.ItemSpec) || !File.Exists(ApplicationManifest?.ItemSpec))
+ {
+ Log.LogErrorWithCodeFromResources(null, ApplicationManifest?.ItemSpec, 0, 0, 0, 0, "AddToWin32Manifest.SpecifiedApplicationManifestCanNotBeFound");
+ return null;
+ }
+
+ return ApplicationManifest!.ItemSpec;
+ }
+
+ string? defaultManifestPath = ToolLocationHelper.GetPathToDotNetFrameworkFile(DefaultManifestName, TargetDotNetFrameworkVersion.Version46);
+
+ return defaultManifestPath;
+ }
+
+ private Stream? GetManifestStream(string? path)
+ {
+ // The logic for getting default manifest is similar to the one from Roslyn:
+ // If Roslyn logic returns null, we fall back to reading embedded manifest.
+ return path is null
+ ? typeof(AddToWin32Manifest).Assembly.GetManifestResourceStream($"Microsoft.Build.Tasks.Resources.{DefaultManifestName}")
+ : File.OpenRead(path);
+ }
+
+ public override bool Execute()
+ {
+ string? manifestPath = GetManifestPath();
+ try
+ {
+ using Stream? stream = GetManifestStream(manifestPath);
+
+ if (stream is null)
+ {
+ Log.LogErrorWithCodeFromResources(null, manifestPath, 0, 0, 0, 0, "AddToWin32Manifest.ManifestCanNotBeOpened");
+
+ return !Log.HasLoggedErrors;
+ }
+
+ XmlDocument document = LoadManifest(stream);
+ XmlNamespaceManager xmlNamespaceManager = XmlNamespaces.GetNamespaceManager(document.NameTable);
+
+ ManifestValidationResult validationResult = ValidateManifest(manifestPath, document, xmlNamespaceManager);
+
+ switch (validationResult)
+ {
+ case ManifestValidationResult.Success:
+ AddSupportedArchitecturesElement(document, xmlNamespaceManager);
+ SaveManifest(document, Path.GetFileName(ApplicationManifest?.ItemSpec) ?? DefaultManifestName);
+ return !Log.HasLoggedErrors;
+ case ManifestValidationResult.SupportedArchitecturesExists:
+ return !Log.HasLoggedErrors;
+ case ManifestValidationResult.Failure:
+ return !Log.HasLoggedErrors;
+ default:
+ return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.LogErrorWithCodeFromResources(null, manifestPath, 0, 0, 0, 0, "AddToWin32Manifest.ManifestCanNotBeOpenedWithException", ex.Message);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+
+ private XmlDocument LoadManifest(Stream stream)
+ {
+ XmlDocument document = new XmlDocument();
+
+ using (XmlReader xr = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true }))
+ {
+ document.Load(xr);
+ }
+
+ return document;
+ }
+
+ private void SaveManifest(XmlDocument document, string manifestName)
+ {
+ ManifestPath = Path.Combine(OutputDirectory, manifestName);
+ using (var xmlWriter = new XmlTextWriter(ManifestPath, Encoding.UTF8))
+ {
+ xmlWriter.Formatting = Formatting.Indented;
+ xmlWriter.Indentation = 4;
+ document.Save(xmlWriter);
+ }
+ }
+
+ private ManifestValidationResult ValidateManifest(string? manifestPath, XmlDocument document, XmlNamespaceManager xmlNamespaceManager)
+ {
+ if (ApplicationManifest == null)
+ {
+ return ManifestValidationResult.Success;
+ }
+
+ XmlNode? assemblyNode = document.SelectSingleNode(XPaths.assemblyElement, xmlNamespaceManager);
+
+ if (assemblyNode is null)
+ {
+ Log.LogErrorWithCodeFromResources(null, manifestPath, 0, 0, 0, 0, "AddToWin32Manifest.AssemblyNodeIsMissed");
+ return ManifestValidationResult.Failure;
+ }
+
+ XmlNode? supportedArchitecturesNode = GetNode(assemblyNode, supportedArchitectures, xmlNamespaceManager);
+ if (supportedArchitecturesNode != null)
+ {
+ if (!string.Equals(supportedArchitecturesNode.InnerText.Trim(), SupportedArchitectures, StringComparison.OrdinalIgnoreCase))
+ {
+ Log.LogErrorWithCodeFromResources(null, manifestPath, 0, 0, 0, 0, "AddToWin32Manifest.InvalidValueInSupportedArchitectures", supportedArchitecturesNode.InnerText);
+
+ return ManifestValidationResult.Failure;
+ }
+
+ return ManifestValidationResult.SupportedArchitecturesExists;
+ }
+
+ return ManifestValidationResult.Success;
+ }
+
+ private void AddSupportedArchitecturesElement(XmlDocument document, XmlNamespaceManager xmlNamespaceManager)
+ {
+ XmlNode? assemblyNode = document.SelectSingleNode(XPaths.assemblyElement, xmlNamespaceManager);
+ XmlElement appNode = GetOrCreateXmlElement(document, xmlNamespaceManager, application, asmv3Prefix, XmlNamespaces.asmv3);
+ XmlElement winSettingsNode = GetOrCreateXmlElement(document, xmlNamespaceManager, windowsSettings, asmv3Prefix, XmlNamespaces.asmv3);
+ if (string.IsNullOrEmpty(winSettingsNode.GetAttribute(XMakeAttributes.xmlns)))
+ {
+ winSettingsNode.SetAttribute(XMakeAttributes.xmlns, WindowsSettingsNamespace);
+ }
+
+ XmlElement supportedArchitecturesNode = GetOrCreateXmlElement(document, xmlNamespaceManager, supportedArchitectures, namespaceURI: WindowsSettingsNamespace);
+ supportedArchitecturesNode.InnerText = SupportedArchitectures;
+ winSettingsNode.AppendChild(supportedArchitecturesNode);
+
+ // If ParentNode is null, this indicates that winSettingsNode was not a part of the manifest.
+ if (winSettingsNode.ParentNode == null)
+ {
+ appNode.AppendChild(winSettingsNode);
+ }
+
+ if (appNode.ParentNode == null)
+ {
+ assemblyNode!.AppendChild(appNode);
+ }
+ }
+
+ private XmlElement GetOrCreateXmlElement(XmlDocument document, XmlNamespaceManager xmlNamespaceManager, string localName, string prefix = "", string namespaceURI = "")
+ {
+ XmlNode? existingNode = GetNode(document, localName, xmlNamespaceManager);
+
+ if (existingNode is XmlElement element)
+ {
+ return element;
+ }
+
+ return !string.IsNullOrEmpty(prefix)
+ ? document.CreateElement(prefix, localName, namespaceURI)
+ : document.CreateElement(localName, namespaceURI);
+ }
+
+ private XmlNode? GetNode(XmlNode node, string localName, XmlNamespaceManager xmlNamespaceManager) => node.SelectSingleNode($"//*[local-name()='{localName}']", xmlNamespaceManager);
+ }
+}
diff --git a/src/Tasks/MSBuildInternalMessage.cs b/src/Tasks/MSBuildInternalMessage.cs
new file mode 100644
index 00000000000..0595e286179
--- /dev/null
+++ b/src/Tasks/MSBuildInternalMessage.cs
@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Build.Framework;
+
+namespace Microsoft.Build.Tasks
+{
+ ///
+ /// Represents a task that produces localized messages based on the specified resource name.
+ /// This task is intended to be called from internal targets only.
+ ///
+ public sealed class MSBuildInternalMessage : TaskExtension
+ {
+ private enum BuildMessageSeverity
+ {
+ ///
+ /// Indicates that the message corresponds to build information.
+ ///
+ Message,
+
+ ///
+ /// Indicates that the message corresponds to a build warning.
+ ///
+ Warning,
+
+ ///
+ /// Indicates that the message corresponds to a build error.
+ ///
+ Error,
+ }
+
+ ///
+ /// The name of the resource in Strings.resx that contains the desired error message.
+ ///
+ [Required]
+ public string ResourceName { get; set; } = string.Empty;
+
+ ///
+ /// Resource arguments to be used in the format string.
+ ///
+ public string[] FormatArguments { get; set; } = [];
+
+ ///
+ /// .
+ ///
+ [Required]
+ public string Severity { set; get; } = string.Empty;
+
+ ///
+ /// Configurable message importance.
+ ///
+ public string MessageImportance { get; set; } = "Normal";
+
+ public override bool Execute()
+ {
+ if (Enum.TryParse(Severity, ignoreCase: true, out BuildMessageSeverity severity))
+ {
+ switch (severity)
+ {
+ case BuildMessageSeverity.Error:
+ Log.LogErrorWithCodeFromResources(ResourceName, FormatArguments);
+ return !Log.HasLoggedErrors;
+
+ case BuildMessageSeverity.Warning:
+ Log.LogWarningWithCodeFromResources(ResourceName, FormatArguments);
+ return !Log.HasLoggedErrors;
+
+ case BuildMessageSeverity.Message:
+ MessageImportance importance = (MessageImportance)Enum.Parse(typeof(MessageImportance), MessageImportance, true);
+ Log.LogMessageFromResources(importance, ResourceName, FormatArguments);
+ return !Log.HasLoggedErrors;
+
+ default:
+ return !Log.HasLoggedErrors;
+ }
+ }
+
+ Log.LogErrorFromResources("CommonTarget.SpecifiedSeverityDoesNotExist", Severity);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Tasks/ManifestUtil/XmlNamespaces.cs b/src/Tasks/ManifestUtil/XmlNamespaces.cs
index 033c1f4006c..bccdf44abf6 100644
--- a/src/Tasks/ManifestUtil/XmlNamespaces.cs
+++ b/src/Tasks/ManifestUtil/XmlNamespaces.cs
@@ -25,6 +25,7 @@ public static XmlNamespaceManager GetNamespaceManager(XmlNameTable nameTable)
nsmgr.AddNamespace("dsig", dsig);
nsmgr.AddNamespace("xrml", xrml);
nsmgr.AddNamespace("xsi", xsi);
+
return nsmgr;
}
}
diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj
index 3012b0ef44e..5bdaff55739 100644
--- a/src/Tasks/Microsoft.Build.Tasks.csproj
+++ b/src/Tasks/Microsoft.Build.Tasks.csproj
@@ -258,7 +258,9 @@
+
+
@@ -484,6 +486,8 @@
$(AssemblyName).Strings.ManifestUtilities.resources
Designer
+
+
Microsoft.Build.Tasks.Deployment.ManifestUtilities.manifest.xml
@@ -654,6 +658,9 @@
+
+
+
diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets
index 5e81c64c525..4080585a710 100644
--- a/src/Tasks/Microsoft.Common.CurrentVersion.targets
+++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets
@@ -427,6 +427,11 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<_WindowsMetadataOutputPath>$(OutDir)$(WinMDExpOutputWindowsMetadataFilename)
+
+
+ <_SupportedArchitectures>amd64 arm64
+
+
<_DeploymentManifestEntryPoint Include="@(IntermediateAssembly)">
@@ -854,6 +859,12 @@ Copyright (C) Microsoft Corporation. All rights reserved.
x86
+
+
+
+
+
+
+
- $(_FrameworkVersion40Path)\default.win32manifest
+ $(_FrameworkVersion40Path)\default.win32manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(_Win32Manifest)
@@ -4294,6 +4334,13 @@ Copyright (C) Microsoft Corporation. All rights reserved.
+
+
+
<_DeploymentCopyApplicationManifest>true
diff --git a/src/Tasks/Microsoft.Common.tasks b/src/Tasks/Microsoft.Common.tasks
index cf67254502d..35018eb1918 100644
--- a/src/Tasks/Microsoft.Common.tasks
+++ b/src/Tasks/Microsoft.Common.tasks
@@ -8,6 +8,7 @@
+
@@ -62,6 +63,7 @@
+
diff --git a/src/Tasks/Microsoft.NETFramework.CurrentVersion.props b/src/Tasks/Microsoft.NETFramework.CurrentVersion.props
index 0f03f04dfef..8fe39f32479 100644
--- a/src/Tasks/Microsoft.NETFramework.CurrentVersion.props
+++ b/src/Tasks/Microsoft.NETFramework.CurrentVersion.props
@@ -83,7 +83,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
v$(MSBuildRuntimeVersion)
-
+
true
diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx
index a969d1dc8de..7761f84804a 100644
--- a/src/Tasks/Resources/Strings.resx
+++ b/src/Tasks/Resources/Strings.resx
@@ -2621,7 +2621,7 @@
MSB3781: The SDK "{0}" depends on the following SDK(s) {1}, which have not been added to the project or were not found. Please ensure that you add these dependencies to your project or you may experience runtime issues. You can add dependencies to your project through the Reference Manager.
{StrBegin="MSB3781: "}
-
+
MSB3782: The "{0}" SDK does not support targeting a neutral architecture with "Prefer 32-Bit" enabled for the project. Please go to the project properties (Build tab for C# and Compile tab for VB) and disable the "Prefer 32-bit" option, or change your project to target a non-neutral architecture.
{StrBegin="MSB3782: "} Also, please localize "Prefer 32-Bit" in the same way that it is localized in wizard\vbdesigner\designer\proppages\buildproppage.resx
@@ -3013,6 +3013,47 @@
MSB3992: '{0}' is not set. When {1} is true, make sure to set a value for '{0}'.
{StrBegin="MSB3992: "}
+
+
+
+ MSB4300: The specified value '{0}' for the supportedArchitectures element is invalid. Either remove it from the manifest or set it to 'amd64 arm64'.
+ {StrBegin="MSB4300: "}
+
+
+ The assembly element is missing from the application manifest.
+
+
+ The application manifest file cannot be found. Please make sure it exists.
+
+
+ The manifest file either does not exist or can not be read. Please make sure it exists and has relevant content.
+
+
+ The manifest file opening has failed with exception: '{0}'. Please make sure it exists and has relevant content.
+
+
+
+
+ MSB9901: The specified severity is not relevant: '{0}' for the message: '{1}'.
+ {StrBegin="MSB9901: "}
+
+
+ MSB9902: Prefer32Bit and PreferNativeArm64 options are mutually exclusive. Please enable only one.
+ {StrBegin="MSB9902: "}
+
+
+ MSB9903: PreferNativeArm64 requires a Win32 application manifest and is mutually exclusive with NoWin32Manifest.
+ {StrBegin="MSB9903: "}
+
+
+ MSB9904: When PreferNativeArm64 is enabled, ensure that the Platform is set to AnyCPU. Current Platform: {0}.
+ {StrBegin="MSB9904: "}
+
+