diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln
index efaad84182ac..e38dc7fe1092 100644
--- a/Microsoft.Maui-dev.sln
+++ b/Microsoft.Maui-dev.sln
@@ -271,6 +271,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{50442BCE-D01
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maui25871Library", "src\Controls\tests\Maui25871Library\Maui25871Library.csproj", "{8820D95F-EB6D-4B70-4985-759AAC83CF68}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.ApplicationArtifacts.Windows", "src\Workload\Microsoft.Maui.ApplicationArtifacts.Windows\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "{3BC53DE7-90E7-47FB-9A70-D913782E07D6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -684,6 +686,10 @@ Global
{8820D95F-EB6D-4B70-4985-759AAC83CF68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8820D95F-EB6D-4B70-4985-759AAC83CF68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8820D95F-EB6D-4B70-4985-759AAC83CF68}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -808,6 +814,7 @@ Global
{5E0D0ACD-EA38-448D-B643-30B966A41922} = {1A031B27-6EE7-35A8-5C38-EB7370E6029E}
{50442BCE-D01E-36E8-5392-D3166C7E5B47} = {6A5A18F7-8A4B-639B-2D50-68074424D750}
{8820D95F-EB6D-4B70-4985-759AAC83CF68} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6} = {EC63FD88-5E12-46D7-B440-68F70241D987}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/Microsoft.Maui-mac.slnf b/Microsoft.Maui-mac.slnf
index 623e1571661a..124c189cc49c 100644
--- a/Microsoft.Maui-mac.slnf
+++ b/Microsoft.Maui-mac.slnf
@@ -67,6 +67,7 @@
"src\\TestUtils\\src\\VisualTestUtils\\VisualTestUtils.csproj",
"src\\TestUtils\\src\\VisualTestUtils.MagickNet\\VisualTestUtils.MagickNet.csproj",
"src\\TestUtils\\src\\Microsoft.Maui.IntegrationTests\\Microsoft.Maui.IntegrationTests.csproj",
+ "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj",
"src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj",
"src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj"
]
diff --git a/Microsoft.Maui-windows.slnf b/Microsoft.Maui-windows.slnf
index 4ed4c36e2225..d69d5d54efdb 100644
--- a/Microsoft.Maui-windows.slnf
+++ b/Microsoft.Maui-windows.slnf
@@ -84,6 +84,7 @@
"src\\TestUtils\\src\\UITest.NUnit\\UITest.NUnit.csproj",
"src\\TestUtils\\src\\VisualTestUtils\\VisualTestUtils.csproj",
"src\\TestUtils\\src\\VisualTestUtils.MagickNet\\VisualTestUtils.MagickNet.csproj",
+ "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj",
"src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj",
"src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj"
]
diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln
index 37c133515923..e438216cb910 100644
--- a/Microsoft.Maui.sln
+++ b/Microsoft.Maui.sln
@@ -269,6 +269,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.DeviceTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.UnitTests", "src\AI\tests\Essentials.AI.UnitTests\Essentials.AI.UnitTests.csproj", "{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.ApplicationArtifacts.Windows", "src\Workload\Microsoft.Maui.ApplicationArtifacts.Windows\Microsoft.Maui.ApplicationArtifacts.Windows.csproj", "{3BC53DE7-90E7-47FB-9A70-D913782E07D6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -686,6 +688,10 @@ Global
{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -810,6 +816,7 @@ Global
{B578E852-193C-4F37-ACB1-2B9B1B32F6B6} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
{90B0A751-82EA-4575-B22C-256EF3A4FEF1} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
{BD5D52FA-CA8E-4236-979A-95B4CE4158D8} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {3BC53DE7-90E7-47FB-9A70-D913782E07D6} = {EC63FD88-5E12-46D7-B440-68F70241D987}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/docs/design/ApplicationArtifact.md b/docs/design/ApplicationArtifact.md
new file mode 100644
index 000000000000..62dfdcb5570b
--- /dev/null
+++ b/docs/design/ApplicationArtifact.md
@@ -0,0 +1,42 @@
+# ApplicationArtifact metadata in .NET MAUI
+
+`@(ApplicationArtifact)` is the shared public item group for final application artifacts. Platform SDKs own creating these items and their artifact identity, path, format, and platform-specific metadata:
+
+- .NET for Android creates APK and AAB items.
+- .NET for iOS, Mac Catalyst, tvOS, and macOS creates `.app`, `.ipa`, `.pkg`, and `.xcarchive` items.
+- `Microsoft.Maui.ApplicationArtifacts.Windows` creates items for modern SDK-style Windows App SDK applications.
+- Other platforms should populate the same item group from their own build or publish pipeline.
+
+MAUI does not rediscover platform package files and does not create a parallel MAUI-specific artifact item group. Each producer attaches common metadata from its final manifest or resolved build output:
+
+- `ApplicationId`
+- `ApplicationName`
+- `ApplicationTitle`
+- `ApplicationDisplayVersion`
+- `ApplicationVersion`
+
+Platform-specific metadata remains available alongside the common values, such as Android `PackageId`, Apple `BundleIdentifier`, and Windows `PackageVersion`. `ApplicationIdGuid` is Windows-specific and is present only when the effective package identity is a GUID. Resource-backed names remain resource references when the build cannot resolve a single locale.
+
+Each platform owns `GetApplicationArtifacts`, retains `$(GetApplicationArtifactsDependsOn)` as the downstream extension point, and makes the collected items available during Publish.
+
+## Windows App SDK
+
+`Microsoft.Maui.ApplicationArtifacts.Windows` is a targets-only `buildTransitive` package with no MAUI runtime, Resizetizer, or MAUI build-task dependency. `Microsoft.Maui.Core` depends on it only for Windows target frameworks, and non-MAUI Windows App SDK applications can reference it directly. It activates for real, non-design-time Windows App SDK application builds with `Exe` or `WinExe` output. Set `EnableWindowsApplicationArtifacts` to `false` to disable it. Classic `.wapproj` packaging is not supported.
+
+The package defines `GetApplicationArtifacts`, retains `$(GetApplicationArtifactsDependsOn)` as the downstream extension point, and makes publish-path items available to `AfterTargets="Publish"` consumers in the same invocation. It reads final Windows App SDK output properties and item lists rather than searching output directories.
+
+Each Windows item has an `ArtifactRole`:
+
+- `Primary`
+- `PayloadDirectory`
+- `Bundle`
+- `StoreUpload`
+- `DeploymentManifest`
+- `Symbols`
+- `Certificate`
+- `InstallScript`
+- `DependencyPackage`
+- `LandingPage`
+- `Support`
+
+Windows items also provide `IsPrimary`, `PrimaryArtifact`, `PackageFormat`, `PackageType`, `PackageVersion`, `Architecture`, `RuntimeIdentifier`, `Signed`, `BundlePlatforms`, `EntryPoint`, and `DeploymentDirectory` when available. Packaged identity, display name, and version metadata comes from the final Appx manifest, including unchanged `ms-resource:` references.
diff --git a/docs/design/NuGets.md b/docs/design/NuGets.md
index 7db4653f76d5..1ceb40aef5cb 100644
--- a/docs/design/NuGets.md
+++ b/docs/design/NuGets.md
@@ -12,6 +12,7 @@ There are several artifacts in the .NET MAUI "universe":
* `Microsoft.Maui.Controls.Xaml` - The XAML parser
* `Microsoft.Maui.Controls.Build.Tasks` - The real set of XAML[C|G] targets and globs
* `Microsoft.Maui.Resizetizer` - The framework-and-platform-agnostic asset generator and integrator
+ * `Microsoft.Maui.ApplicationArtifacts.Windows` - Targets-only discovery of final Windows App SDK application outputs
* `Microsoft.Maui.Core` - The base interfaces and handlers for all .NET MAUI things
* `Microsoft.Maui.Essentials` - The core set of framework-agnostic, cross-platform APIs
* `Microsoft.Maui.Controls.Compatibility` - The set of compatibility APIs for Xamarin.Forms
@@ -42,6 +43,7 @@ Most nugets do not contain or need targets and/or props files, but some do. Thes
- Manifest - `Microsoft.NET.Sdk.Maui.Manifest`
- SDK - `Microsoft.Maui.Sdk`
- NuGet Packages
+ - Windows application artifacts - `Microsoft.Maui.ApplicationArtifacts.Windows`
- Controls - `Microsoft.Maui.Controls.Build.Tasks`
- Core - `Microsoft.Maui.Core`
- Resizetizer - `Microsoft.Maui.Resizetizer`
@@ -104,12 +106,17 @@ The targets file can then import all the SDK:
### Targets in the Packs
-There are 3 packs today that need targets:
+There are 4 packs today that need targets:
+ - Windows application artifacts - `Microsoft.Maui.ApplicationArtifacts.Windows`
- Controls - `Microsoft.Maui.Controls.Build.Tasks`
- Core - `Microsoft.Maui.Core`
- Resizetizer - `Microsoft.Maui.Resizetizer`
+#### Targets in Windows application artifacts
+
+`Microsoft.Maui.ApplicationArtifacts.Windows` is a neutral, targets-only package. Its `buildTransitive` target discovers final outputs from modern SDK-style Windows App SDK application projects and exposes them as `@(ApplicationArtifact)`. It has no dependency on MAUI runtime assemblies, Resizetizer, or MAUI build tasks, so non-MAUI Windows App SDK applications can reference it directly. Classic `.wapproj` packaging is not supported.
+
#### Targets in Controls
The Controls section of MAUI is the main set of libraries that a MAUI app uses. The targets and props needed but MAUI mostly live in the `Microsoft.Maui.Controls.Build.Tasks` nuget. This is mainly because of the dependency structure of MAUI.
diff --git a/eng/Microsoft.Maui.Packages-mac.slnf b/eng/Microsoft.Maui.Packages-mac.slnf
index 5529edb22fdb..265328468a12 100644
--- a/eng/Microsoft.Maui.Packages-mac.slnf
+++ b/eng/Microsoft.Maui.Packages-mac.slnf
@@ -22,6 +22,7 @@
"src\\Graphics\\src\\Text.Markdig\\Graphics.Text.Markdig.csproj",
"src\\SingleProject\\Resizetizer\\src\\Resizetizer.csproj",
"src\\Templates\\src\\Microsoft.Maui.Templates.csproj",
+ "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj",
"src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj",
"src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj"
]
diff --git a/eng/Microsoft.Maui.Packages.slnf b/eng/Microsoft.Maui.Packages.slnf
index 7c21491d97e3..fb74bed2074b 100644
--- a/eng/Microsoft.Maui.Packages.slnf
+++ b/eng/Microsoft.Maui.Packages.slnf
@@ -25,6 +25,7 @@
"src\\Essentials\\src\\Essentials.csproj",
"src\\SingleProject\\Resizetizer\\src\\Resizetizer.csproj",
"src\\Templates\\src\\Microsoft.Maui.Templates.csproj",
+ "src\\Workload\\Microsoft.Maui.ApplicationArtifacts.Windows\\Microsoft.Maui.ApplicationArtifacts.Windows.csproj",
"src\\Workload\\Microsoft.Maui.Sdk\\Microsoft.Maui.Sdk.csproj",
"src\\Workload\\Microsoft.NET.Sdk.Maui.Manifest\\Microsoft.NET.Sdk.Maui.Manifest.csproj"
]
diff --git a/eng/cake/dotnet.cake b/eng/cake/dotnet.cake
index 084a098460c9..27c006da3c55 100644
--- a/eng/cake/dotnet.cake
+++ b/eng/cake/dotnet.cake
@@ -34,6 +34,7 @@ var NuGetOnlyPackages = new string[] {
"Microsoft.Maui.Core.*.{nupkg,snupkg}",
"Microsoft.Maui.Essentials.*.{nupkg,snupkg}",
"Microsoft.Maui.Graphics.*.{nupkg,snupkg}",
+ "Microsoft.Maui.ApplicationArtifacts.Windows.*.{nupkg,snupkg}",
"Microsoft.Maui.Maps.*.{nupkg,snupkg}",
"Microsoft.Maui.Resizetizer.*.{nupkg,snupkg}",
"Microsoft.AspNetCore.Components.WebView.*.{nupkg,snupkg}",
diff --git a/src/Core/src/Core.csproj b/src/Core/src/Core.csproj
index 41e5ba010d08..005cf2f6c41a 100644
--- a/src/Core/src/Core.csproj
+++ b/src/Core/src/Core.csproj
@@ -51,6 +51,9 @@
+
diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs
index 1ed09d2b7e4a..ef2000e4c5fd 100644
--- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs
+++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/BaseBuildTest.cs
@@ -44,6 +44,7 @@ private void SetUpNuGetPackages()
"Microsoft.Maui.Core.*.nupkg",
"Microsoft.Maui.Essentials.*.nupkg",
"Microsoft.Maui.Graphics.*.nupkg",
+ "Microsoft.Maui.ApplicationArtifacts.Windows.*.nupkg",
"Microsoft.Maui.Maps.*.nupkg",
"Microsoft.Maui.Resizetizer.*.nupkg",
"Microsoft.AspNetCore.Components.WebView.*.nupkg",
diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs
new file mode 100644
index 000000000000..a1e5a6f6918e
--- /dev/null
+++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsApplicationArtifactsPackageTest.cs
@@ -0,0 +1,505 @@
+using System.IO.Compression;
+using System.Security;
+using System.Xml.Linq;
+
+namespace Microsoft.Maui.IntegrationTests;
+
+[Trait("Category", "Build")]
+public class WindowsApplicationArtifactsPackageTest : BaseBuildTest
+{
+ const string PackageId = "Microsoft.Maui.ApplicationArtifacts.Windows";
+
+ public WindowsApplicationArtifactsPackageTest(IntegrationTestFixture fixture, ITestOutputHelper output)
+ : base(fixture, output)
+ {
+ }
+
+ [Fact]
+ public void PackageContainsOnlyNeutralBuildAssets()
+ {
+ SetTestIdentifier(nameof(PackageContainsOnlyNeutralBuildAssets));
+ var packagePath = FindPackage(PackageId);
+
+ using var archive = ZipFile.OpenRead(packagePath);
+ var entries = archive.Entries.Select(entry => entry.FullName).ToArray();
+
+ Assert.Contains("buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets", entries);
+ Assert.DoesNotContain(entries, entry =>
+ entry.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
+ entry.StartsWith("lib/", StringComparison.OrdinalIgnoreCase) ||
+ entry.StartsWith("runtimes/", StringComparison.OrdinalIgnoreCase) ||
+ entry.StartsWith("tools/", StringComparison.OrdinalIgnoreCase) ||
+ entry.StartsWith("analyzers/", StringComparison.OrdinalIgnoreCase));
+ Assert.Single(entries.Where(entry => entry.StartsWith("buildTransitive/", StringComparison.OrdinalIgnoreCase)));
+
+ var nuspecEntry = Assert.Single(archive.Entries.Where(entry => entry.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase)));
+ using var nuspecStream = nuspecEntry.Open();
+ var nuspec = XDocument.Load(nuspecStream);
+ var ns = nuspec.Root!.Name.Namespace;
+ var packageType = Assert.Single(nuspec.Descendants(ns + "packageType"));
+ Assert.Equal("Dependency", (string?)packageType.Attribute("name"));
+ }
+
+ [Fact]
+ public void CorePackageDependsOnApplicationArtifactsOnlyForWindows()
+ {
+ SetTestIdentifier(nameof(CorePackageDependsOnApplicationArtifactsOnlyForWindows));
+ if (!TestEnvironment.IsWindows)
+ {
+ if (true)
+ return; // Skip: "Packing the Windows Core target framework is only supported on Windows CI."
+ }
+
+ var coreProject = Path.Combine(TestEnvironment.GetMauiDirectory(), "src", "Core", "src", "Core.csproj");
+ Assert.True(
+ DotnetInternal.Run(
+ "pack",
+ $"\"{coreProject}\" -c Debug --no-build --no-restore -p:Packing=true -p:EnableWindowsTargeting=true -p:IncludeBuildOutput=false -p:NoWarn=NU5127%3BNU5128 -p:PackageOutputPath=\"{TestDirectory}\" -v:minimal",
+ output: _output),
+ "Unable to generate the Core package dependency graph.");
+ var packagePath = Path.Combine(TestDirectory, $"Microsoft.Maui.Core.{MauiPackageVersion}.nupkg");
+ Assert.True(File.Exists(packagePath), $"The expected Core package was not created at '{packagePath}'.");
+
+ using var archive = ZipFile.OpenRead(packagePath);
+ var nuspecEntry = Assert.Single(archive.Entries.Where(entry => entry.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase)));
+ using var nuspecStream = nuspecEntry.Open();
+ var nuspec = XDocument.Load(nuspecStream);
+ var ns = nuspec.Root!.Name.Namespace;
+ var dependencyGroups = nuspec
+ .Descendants(ns + "group")
+ .Where(group => group.Elements(ns + "dependency").Any(dependency =>
+ string.Equals((string?)dependency.Attribute("id"), PackageId, StringComparison.Ordinal)))
+ .ToArray();
+
+ Assert.NotEmpty(dependencyGroups);
+ Assert.All(dependencyGroups, group =>
+ {
+ Assert.Contains("windows", (string?)group.Attribute("targetFramework") ?? string.Empty, StringComparison.OrdinalIgnoreCase);
+ var dependency = Assert.Single(group.Elements(ns + "dependency").Where(candidate =>
+ string.Equals((string?)candidate.Attribute("id"), PackageId, StringComparison.Ordinal)));
+ Assert.Equal("All", (string?)dependency.Attribute("include"), ignoreCase: true);
+ Assert.Null(dependency.Attribute("exclude"));
+ });
+ }
+
+ [Fact]
+ public void TargetsClassifyAndDeduplicateAuthoritativeOutputs()
+ {
+ SetTestIdentifier(nameof(TargetsClassifyAndDeduplicateAuthoritativeOutputs));
+ var packageDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "AppPackages")).FullName;
+ var packageTestDir = Directory.CreateDirectory(Path.Combine(packageDir, "ArtifactApp_2.3.4.5_x64_Test")).FullName;
+ var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin", "Debug", "net11.0-windows")).FullName;
+ var uploadDir = Directory.CreateDirectory(Path.Combine(targetDir, "Upload")).FullName;
+ var intermediateDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "obj", "Debug")).FullName;
+
+ var primary = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.msix");
+ var bundle = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.msixbundle");
+ var uploadPackage = CreateFile(uploadDir, "ArtifactApp_2.3.4.5_x64.msix");
+ var storeUpload = CreateFile(packageDir, "ArtifactApp_2.3.4.5_x64.msixupload");
+ var appInstaller = CreateFile(packageDir, "ArtifactApp.appinstaller");
+ var symbols = CreateFile(packageTestDir, "ArtifactApp_2.3.4.5_x64.appxsym");
+ var certificate = CreateFile(packageTestDir, "ArtifactApp.cer");
+ var installScript = CreateFile(packageTestDir, "Install.ps1");
+ var dependency = CreateFile(Path.Combine(packageTestDir, "Dependencies", "x64"), "Framework.msix");
+ var landingPage = CreateFile(packageDir, "index.html");
+ var support = CreateFile(packageDir, "install.json");
+ var appInstallerSupport = CreateFile(packageDir, "appinstaller-data.xml");
+ var intermediate = CreateFile(intermediateDir, "intermediate.msix");
+ _ = CreateFile(targetDir, "loose-payload.dll");
+
+ var manifest = Path.Combine(targetDir, "AppxManifest.xml");
+ File.WriteAllText(manifest, """
+
+
+
+
+ ms-resource:ArtifactApp
+
+
+
+
+
+ """);
+
+ var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt");
+ var projectFile = Path.Combine(TestDirectory, "ArtifactClassification.proj");
+ var targetsFile = Path.Combine(
+ TestEnvironment.GetMauiDirectory(),
+ "src",
+ "Workload",
+ PackageId,
+ "buildTransitive",
+ $"{PackageId}.targets");
+
+ File.WriteAllText(projectFile, $"""
+
+
+ net11.0-windows10.0.19041.0
+ WinExe
+ false
+ {Escape(packageDir)}
+ MSIX
+ {Escape(WithDirectorySeparator(targetDir))}
+ {Escape(WithDirectorySeparator(targetDir))}
+ {Escape(WithDirectorySeparator(intermediateDir))}
+ {Escape(WithDirectorySeparator(packageDir))}
+ {Escape(WithDirectorySeparator(packageTestDir))}
+ {Escape(WithDirectorySeparator(uploadDir))}
+ {Escape(primary)}
+ {Escape(bundle)}
+ {Escape(uploadPackage)}
+ {Escape(storeUpload)}
+ {Escape(symbols)}
+ {Escape(certificate)}
+ {Escape(landingPage)}
+ {Escape(manifest)}
+ x64
+ win-x64
+ true
+ x64_arm64
+ ArtifactApp
+ StampCollectedArtifacts
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ extended-%(ApplicationArtifact.BundlePlatforms)
+
+
+
+
+
+
+
+ """);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output),
+ "Unable to execute the Windows ApplicationArtifact targets contract fixture.");
+
+ var artifacts = File.ReadAllLines(artifactsFile)
+ .Select(ArtifactRecord.Parse)
+ .ToArray();
+
+ Assert.Equal(13, artifacts.Length);
+ Assert.Equal(artifacts.Length, artifacts.Select(artifact => artifact.Path).Distinct(StringComparer.OrdinalIgnoreCase).Count());
+ Assert.DoesNotContain(artifacts, artifact => artifact.Path.StartsWith(intermediateDir, StringComparison.OrdinalIgnoreCase));
+ Assert.DoesNotContain(artifacts, artifact => artifact.Path.EndsWith("loose-payload.dll", StringComparison.OrdinalIgnoreCase));
+
+ AssertRole(primary, "Primary", isPrimary: true);
+ AssertRole(packageTestDir, "PayloadDirectory");
+ AssertRole(bundle, "Bundle");
+ AssertRole(uploadPackage, "StoreUpload");
+ AssertRole(storeUpload, "StoreUpload");
+ AssertRole(appInstaller, "DeploymentManifest");
+ AssertRole(symbols, "Symbols");
+ AssertRole(certificate, "Certificate");
+ AssertRole(installScript, "InstallScript");
+ AssertRole(dependency, "DependencyPackage");
+ AssertRole(landingPage, "LandingPage");
+ AssertRole(support, "Support");
+ AssertRole(appInstallerSupport, "Support");
+
+ Assert.All(artifacts, artifact =>
+ {
+ Assert.Equal("MSIX", artifact.PackageType);
+ Assert.Equal("2.3.4.5", artifact.PackageVersion);
+ Assert.Equal("x64", artifact.Architecture);
+ Assert.Equal("win-x64", artifact.RuntimeIdentifier);
+ Assert.Equal("true", artifact.Signed, ignoreCase: true);
+ Assert.Equal("extended-x64_arm64", artifact.BundlePlatforms);
+ Assert.Equal("ArtifactApp.exe", artifact.EntryPoint);
+ Assert.Equal(
+ Path.TrimEndingDirectorySeparator(Path.GetFullPath(packageTestDir)),
+ Path.TrimEndingDirectorySeparator(Path.GetFullPath(artifact.DeploymentDirectory)));
+ Assert.Equal("11111111-2222-3333-4444-555555555555", artifact.ApplicationId);
+ Assert.Equal(artifact.ApplicationId, artifact.ApplicationIdGuid);
+ Assert.Equal("ms-resource:ArtifactApp", artifact.ApplicationTitle);
+ Assert.Equal(artifact.ApplicationTitle, artifact.ApplicationName);
+ Assert.Equal("2.3.4", artifact.ApplicationDisplayVersion);
+ Assert.Equal("5", artifact.ApplicationVersion);
+ });
+
+ void AssertRole(string path, string role, bool isPrimary = false)
+ {
+ var artifact = Assert.Single(artifacts.Where(candidate =>
+ string.Equals(
+ Path.TrimEndingDirectorySeparator(Path.GetFullPath(candidate.Path)),
+ Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)),
+ StringComparison.OrdinalIgnoreCase)));
+ Assert.Equal(role, artifact.Role);
+ Assert.Equal(isPrimary ? "true" : "false", artifact.IsPrimary, ignoreCase: true);
+ Assert.Equal(
+ Path.GetFullPath(isPrimary ? path : primary),
+ Path.GetFullPath(artifact.PrimaryArtifact),
+ ignoreCase: true);
+ }
+ }
+
+ [Theory]
+ [InlineData("net11.0", "WinExe", true, true, false, true)]
+ [InlineData("net11.0-windows10.0.19041.0", "Library", true, true, false, true)]
+ [InlineData("net11.0-windows10.0.19041.0", "WinExe", false, true, false, true)]
+ [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, false, false, true)]
+ [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, true, true, true)]
+ [InlineData("net11.0-windows10.0.19041.0", "WinExe", true, true, false, false)]
+ public void TargetsAreInertOutsideRealWindowsAppSdkApplications(
+ string targetFramework,
+ string outputType,
+ bool hasWindowsAppSdk,
+ bool enabled,
+ bool designTimeBuild,
+ bool buildingProject)
+ {
+ SetTestIdentifier(targetFramework, outputType, hasWindowsAppSdk, enabled, designTimeBuild, buildingProject);
+ var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt");
+ var projectFile = Path.Combine(TestDirectory, "Inert.proj");
+ var targetsFile = Path.Combine(
+ TestEnvironment.GetMauiDirectory(),
+ "src",
+ "Workload",
+ PackageId,
+ "buildTransitive",
+ $"{PackageId}.targets");
+
+ File.WriteAllText(projectFile, $"""
+
+
+ {targetFramework}
+ {outputType}
+ {buildingProject}
+ {designTimeBuild}
+ {enabled}
+ {(hasWindowsAppSdk ? Escape(TestDirectory) : string.Empty)}
+ None
+
+
+
+
+
+
+
+ """);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output),
+ "Unable to execute the inert Windows ApplicationArtifact fixture.");
+ Assert.True(!File.Exists(artifactsFile) || new FileInfo(artifactsFile).Length == 0);
+ }
+
+ [Fact]
+ public void SparsePackageUsesTheFinalMsixAsPrimaryArtifact()
+ {
+ SetTestIdentifier(nameof(SparsePackageUsesTheFinalMsixAsPrimaryArtifact));
+ var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin")).FullName;
+ var sparsePackage = CreateFile(targetDir, "SparseApp.msix");
+ var artifactsFile = Path.Combine(TestDirectory, "application-artifacts.txt");
+ var projectFile = Path.Combine(TestDirectory, "Sparse.proj");
+ var targetsFile = Path.Combine(
+ TestEnvironment.GetMauiDirectory(),
+ "src",
+ "Workload",
+ PackageId,
+ "buildTransitive",
+ $"{PackageId}.targets");
+
+ File.WriteAllText(projectFile, $"""
+
+
+ net11.0-windows10.0.19041.0
+ WinExe
+ false
+ {Escape(TestDirectory)}
+ Sparse
+ {Escape(WithDirectorySeparator(targetDir))}
+ {Escape(sparsePackage)}
+ SparseApp
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+ """);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteApplicationArtifacts -v:minimal", output: _output),
+ "Unable to execute the sparse Windows ApplicationArtifact fixture.");
+
+ var fields = Assert.Single(File.ReadAllLines(artifactsFile)).Split('|');
+ Assert.Equal(Path.GetFullPath(sparsePackage), Path.GetFullPath(fields[0]), ignoreCase: true);
+ Assert.Equal("Primary", fields[1]);
+ Assert.Equal("Sparse", fields[2]);
+ Assert.Equal("msix", fields[3]);
+ }
+
+ [Fact]
+ public void UnpackagedBuildAndPublishUseTheirOwnOutputPaths()
+ {
+ SetTestIdentifier(nameof(UnpackagedBuildAndPublishUseTheirOwnOutputPaths));
+ var targetDir = Directory.CreateDirectory(Path.Combine(TestDirectory, "bin")).FullName;
+ var publishDir = Directory.CreateDirectory(Path.Combine(targetDir, "publish")).FullName;
+ var buildExecutable = CreateFile(targetDir, "UnpackagedApp.exe");
+ var publishExecutable = CreateFile(publishDir, "UnpackagedApp.exe");
+ var buildArtifactsFile = Path.Combine(TestDirectory, "build-application-artifacts.txt");
+ var publishArtifactsFile = Path.Combine(TestDirectory, "publish-application-artifacts.txt");
+ var projectFile = Path.Combine(TestDirectory, "Unpackaged.proj");
+ var targetsFile = Path.Combine(
+ TestEnvironment.GetMauiDirectory(),
+ "src",
+ "Workload",
+ PackageId,
+ "buildTransitive",
+ $"{PackageId}.targets");
+
+ File.WriteAllText(projectFile, $"""
+
+
+ net11.0-windows10.0.19041.0
+ WinExe
+ true
+ {Escape(TestDirectory)}
+ None
+ {Escape(WithDirectorySeparator(targetDir))}
+ {Escape(WithDirectorySeparator(publishDir))}
+ UnpackagedApp
+ UnpackagedApp
+ com.example.unpackaged
+ Unpackaged App
+ 1.2.3
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+ """);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteBuildApplicationArtifacts;Publish -v:minimal", output: _output),
+ "Unable to collect unpackaged build and publish artifacts in one MSBuild invocation.");
+ var sameInvocationPublishPrimary = Assert.Single(File.ReadAllLines(publishArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal)));
+ Assert.Equal(Path.GetFullPath(publishExecutable), Path.GetFullPath(sameInvocationPublishPrimary.Split('|')[0]), ignoreCase: true);
+
+ File.Delete(buildArtifactsFile);
+ File.Delete(publishArtifactsFile);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:WriteBuildApplicationArtifacts -v:minimal", output: _output),
+ "Unable to collect unpackaged build artifacts.");
+ var buildPrimary = Assert.Single(File.ReadAllLines(buildArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal)));
+ Assert.Equal(Path.GetFullPath(buildExecutable), Path.GetFullPath(buildPrimary.Split('|')[0]), ignoreCase: true);
+
+ Assert.True(
+ DotnetInternal.Run("msbuild", $"\"{projectFile}\" -t:Publish -v:minimal", output: _output),
+ "Unable to collect unpackaged publish artifacts.");
+ var publishPrimary = Assert.Single(File.ReadAllLines(publishArtifactsFile).Where(line => line.EndsWith("|Primary", StringComparison.Ordinal)));
+ Assert.Equal(Path.GetFullPath(publishExecutable), Path.GetFullPath(publishPrimary.Split('|')[0]), ignoreCase: true);
+ }
+
+ string FindPackage(string packageId)
+ {
+ var artifactDir = Path.Combine(TestEnvironment.GetMauiDirectory(), "artifacts");
+ var matchingPackages = Directory
+ .GetFiles(artifactDir, $"{packageId}.{MauiPackageVersion}.nupkg", SearchOption.AllDirectories)
+ .Where(path => !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
+ .ToArray();
+
+ return Assert.Single(matchingPackages);
+ }
+
+ static string CreateFile(string directory, string fileName)
+ {
+ Directory.CreateDirectory(directory);
+ var path = Path.Combine(directory, fileName);
+ File.WriteAllText(path, fileName);
+ return path;
+ }
+
+ static string WithDirectorySeparator(string path) =>
+ path.EndsWith(Path.DirectorySeparatorChar, StringComparison.Ordinal) ? path : path + Path.DirectorySeparatorChar;
+
+ static string Escape(string value) => SecurityElement.Escape(value) ?? string.Empty;
+
+ sealed record ArtifactRecord(
+ string Path,
+ string Role,
+ string IsPrimary,
+ string PrimaryArtifact,
+ string PackageFormat,
+ string PackageType,
+ string PackageVersion,
+ string Architecture,
+ string RuntimeIdentifier,
+ string Signed,
+ string BundlePlatforms,
+ string EntryPoint,
+ string DeploymentDirectory,
+ string ApplicationId,
+ string ApplicationIdGuid,
+ string ApplicationTitle,
+ string ApplicationName,
+ string ApplicationDisplayVersion,
+ string ApplicationVersion)
+ {
+ public static ArtifactRecord Parse(string line)
+ {
+ var fields = line.Split('|');
+ Assert.Equal(19, fields.Length);
+ return new(
+ fields[0],
+ fields[1],
+ fields[2],
+ fields[3],
+ fields[4],
+ fields[5],
+ fields[6],
+ fields[7],
+ fields[8],
+ fields[9],
+ fields[10],
+ fields[11],
+ fields[12],
+ fields[13],
+ fields[14],
+ fields[15],
+ fields[16],
+ fields[17],
+ fields[18]);
+ }
+ }
+}
diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs
index 3842969306c3..eb7b70e79de9 100644
--- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs
+++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/WindowsTemplateTest.cs
@@ -1,4 +1,9 @@
-namespace Microsoft.Maui.IntegrationTests;
+using System.Security;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Xml.Linq;
+
+namespace Microsoft.Maui.IntegrationTests;
[Trait("Category", "WindowsTemplates")]
public class WindowsTemplateTest : BaseTemplateTests
@@ -46,7 +51,8 @@ public void BuildWindowsAppSDKSelfContained(string id, bool wasdkself, bool nets
SetTestIdentifier(id, wasdkself, netself, packageType);
if (TestEnvironment.IsMacOS)
{
- if (true) return; // Skip: "This test is designed for testing a windows build."
+ if (true)
+ return; // Skip: "This test is designed for testing a windows build."
}
var projectDir = TestDirectory;
@@ -80,7 +86,8 @@ public void BuildWindowsRidGraph(string id, bool useRidGraph, string packageType
SetTestIdentifier(id, useRidGraph, packageType);
if (TestEnvironment.IsMacOS)
{
- if (true) return; // Skip: "This test is designed for testing a windows build."
+ if (true)
+ return; // Skip: "This test is designed for testing a windows build."
}
var projectDir = TestDirectory;
@@ -113,7 +120,8 @@ public void PublishUnpackaged(string id, string framework, string config, bool u
SetTestIdentifier(id, framework, config, usesRidGraph);
if (!TestEnvironment.IsWindows)
{
- if (true) return; // Skip: "Running Windows templates is only supported on Windows."
+ if (true)
+ return; // Skip: "Running Windows templates is only supported on Windows."
}
var projectDir = TestDirectory;
@@ -156,7 +164,8 @@ public void PublishPackaged(string id, string framework, string config, bool use
SetTestIdentifier(id, framework, config, usesRidGraph);
if (!TestEnvironment.IsWindows)
{
- if (true) return; // Skip: "Running Windows templates is only supported on Windows."
+ if (true)
+ return; // Skip: "Running Windows templates is only supported on Windows."
}
var projectDir = TestDirectory;
@@ -213,4 +222,281 @@ public void BuildWithIdentityClient()
Assert.True(DotnetInternal.Build(projectFile, "Debug", properties: BuildProps, msbuildWarningsAsErrors: true, output: _output),
$"Project {Path.GetFileName(projectFile)} failed to build. Check test output/attachments for errors.");
}
+
+ [Theory]
+ [InlineData("None")]
+ [InlineData("MSIX")]
+ public void ApplicationArtifactsAreAvailableForMauiBuildAndPublish(string packageType)
+ {
+ SetTestIdentifier(packageType);
+ if (!TestEnvironment.IsWindows)
+ {
+ if (true)
+ return; // Skip: "Running Windows templates is only supported on Windows."
+ }
+
+ var projectDir = TestDirectory;
+ var projectFile = Path.Combine(projectDir, $"{Path.GetFileName(projectDir)}.csproj");
+ var buildArtifactsFile = Path.Combine(projectDir, "build-application-artifacts.txt");
+ var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt");
+
+ Assert.True(DotnetInternal.New("maui", projectDir, DotNetCurrent, output: _output),
+ "Unable to create the MAUI template. Check test output for errors.");
+
+ var signingProperties = string.Empty;
+ if (packageType == "MSIX")
+ {
+ const string certificatePassword = "application-artifacts-test";
+ var certificatePath = Path.Combine(projectDir, "ApplicationArtifactsTest.pfx");
+ CreateSigningCertificate(certificatePath, certificatePassword);
+ signingProperties = $"""
+ true
+ true
+ true
+ {Escape(certificatePath)}
+ {certificatePassword}
+ """;
+ }
+
+ FileUtilities.ReplaceInFile(
+ projectFile,
+ "None",
+ $"""
+ {packageType}
+ {signingProperties}
+ """);
+ AddApplicationArtifactWriters(projectFile, buildArtifactsFile, publishArtifactsFile);
+
+ var framework = $"{DotNetCurrent}-windows10.0.19041.0";
+ Assert.True(
+ DotnetInternal.Build(projectFile, "Debug", target: "WriteBuildApplicationArtifacts", framework: framework, properties: BuildProps, output: _output),
+ "The MAUI project failed to collect build ApplicationArtifact items.");
+ AssertApplicationArtifacts(File.ReadAllLines(buildArtifactsFile), packageType, published: false);
+
+ Assert.True(
+ DotnetInternal.Publish(projectFile, "Debug", framework: framework, properties: BuildProps, output: _output),
+ "The MAUI project failed to publish.");
+ AssertApplicationArtifacts(File.ReadAllLines(publishArtifactsFile), packageType, published: true);
+ }
+
+ [Fact]
+ public void ApplicationArtifactsIncludeBundleStoreUploadAndAppInstaller()
+ {
+ SetTestIdentifier(nameof(ApplicationArtifactsIncludeBundleStoreUploadAndAppInstaller));
+ if (!TestEnvironment.IsWindows)
+ {
+ if (true)
+ return; // Skip: "Running Windows templates is only supported on Windows."
+ }
+
+ const string certificatePassword = "application-artifacts-test";
+ var projectDir = TestDirectory;
+ var projectFile = Path.Combine(projectDir, $"{Path.GetFileName(projectDir)}.csproj");
+ var certificatePath = Path.Combine(projectDir, "ApplicationArtifactsTest.pfx");
+ var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt");
+
+ Assert.True(DotnetInternal.New("maui", projectDir, DotNetCurrent, output: _output),
+ "Unable to create the MAUI template. Check test output for errors.");
+
+ CreateSigningCertificate(certificatePath, certificatePassword);
+ FileUtilities.ReplaceInFile(
+ projectFile,
+ "None",
+ $"""
+ MSIX
+ true
+ true
+ true
+ {Escape(certificatePath)}
+ {certificatePassword}
+ StoreAndSideload
+ Always
+ x64
+ true
+ https://example.invalid/application-artifacts/
+ 0
+ """);
+ AddApplicationArtifactWriters(projectFile, Path.Combine(projectDir, "build-application-artifacts.txt"), publishArtifactsFile);
+
+ var framework = $"{DotNetCurrent}-windows10.0.19041.0";
+ Assert.True(
+ DotnetInternal.Publish(projectFile, "Release", framework: framework, properties: BuildProps, output: _output),
+ "The MAUI project failed to produce the Windows packaging output matrix.");
+
+ var artifactLines = File.ReadAllLines(publishArtifactsFile);
+ var artifacts = artifactLines.Select(WindowsArtifact.Parse).ToArray();
+ AssertApplicationArtifacts(artifactLines, "MSIX", published: true);
+ Assert.Contains(artifacts, artifact => artifact.Role == "Bundle" && artifact.Path.EndsWith(".msixbundle", StringComparison.OrdinalIgnoreCase));
+ Assert.Contains(artifacts, artifact => artifact.Role == "StoreUpload" && artifact.Path.EndsWith(".msixupload", StringComparison.OrdinalIgnoreCase));
+ Assert.Contains(artifacts, artifact => artifact.Role == "DeploymentManifest" && artifact.Path.EndsWith(".appinstaller", StringComparison.OrdinalIgnoreCase));
+ }
+
+ [Fact]
+ public void StandaloneWindowsAppSdkApplicationCanReferenceApplicationArtifactsPackage()
+ {
+ SetTestIdentifier(nameof(StandaloneWindowsAppSdkApplicationCanReferenceApplicationArtifactsPackage));
+ if (!TestEnvironment.IsWindows)
+ {
+ if (true)
+ return; // Skip: "Running Windows templates is only supported on Windows."
+ }
+
+ var projectDir = TestDirectory;
+ var projectFile = Path.Combine(projectDir, "StandaloneWindowsApp.csproj");
+ var buildArtifactsFile = Path.Combine(projectDir, "build-application-artifacts.txt");
+ var publishArtifactsFile = Path.Combine(projectDir, "publish-application-artifacts.txt");
+ var windowsAppSdkVersion = GetWindowsAppSdkPackageVersion();
+
+ File.WriteAllText(Path.Combine(projectDir, "Program.cs"), """
+ namespace StandaloneWindowsApp;
+
+ internal static class Program
+ {
+ [STAThread]
+ static void Main()
+ {
+ }
+ }
+ """);
+ File.WriteAllText(projectFile, $"""
+
+
+ {DotNetCurrent}-windows10.0.19041.0
+ WinExe
+ win-x64
+ None
+ com.example.standalonewindowsapp
+ Standalone Windows App
+ 7.8.9
+ 10
+
+
+
+
+
+ {GetApplicationArtifactWriters(buildArtifactsFile, publishArtifactsFile)}
+
+ """);
+
+ Assert.True(
+ DotnetInternal.Build(projectFile, "Debug", target: "WriteBuildApplicationArtifacts", properties: BuildProps, output: _output),
+ "The standalone Windows App SDK project failed to collect build ApplicationArtifact items.");
+ AssertApplicationArtifacts(File.ReadAllLines(buildArtifactsFile), "None", published: false);
+
+ Assert.True(
+ DotnetInternal.Publish(projectFile, "Debug", properties: BuildProps, output: _output),
+ "The standalone Windows App SDK project failed to publish.");
+ AssertApplicationArtifacts(File.ReadAllLines(publishArtifactsFile), "None", published: true);
+
+ var assetsFile = File.ReadAllText(Path.Combine(projectDir, "obj", "project.assets.json"));
+ Assert.DoesNotContain("Microsoft.Maui.Core/", assetsFile, StringComparison.OrdinalIgnoreCase);
+ Assert.DoesNotContain("Microsoft.Maui.Resizetizer/", assetsFile, StringComparison.OrdinalIgnoreCase);
+ }
+
+ static void AddApplicationArtifactWriters(string projectFile, string buildArtifactsFile, string publishArtifactsFile)
+ {
+ FileUtilities.ReplaceInFile(
+ projectFile,
+ "",
+ $"""
+ {GetApplicationArtifactWriters(buildArtifactsFile, publishArtifactsFile)}
+
+ """);
+ }
+
+ static string GetApplicationArtifactWriters(string buildArtifactsFile, string publishArtifactsFile) => $"""
+
+
+
+
+
+
+ """;
+
+ static void AssertApplicationArtifacts(string[] artifactLines, string packageType, bool published)
+ {
+ var artifacts = artifactLines.Select(WindowsArtifact.Parse).ToArray();
+ Assert.NotEmpty(artifacts);
+ Assert.Equal(artifacts.Length, artifacts.Select(artifact => artifact.Path).Distinct(StringComparer.OrdinalIgnoreCase).Count());
+ Assert.DoesNotContain(artifacts, artifact =>
+ artifact.Path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase));
+ Assert.All(artifacts, artifact =>
+ {
+ Assert.False(string.IsNullOrWhiteSpace(artifact.Role));
+ Assert.False(string.IsNullOrWhiteSpace(artifact.Format));
+ Assert.Equal(packageType, artifact.PackageType, ignoreCase: true);
+ Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationId));
+ Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationTitle));
+ Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationDisplayVersion));
+ Assert.False(string.IsNullOrWhiteSpace(artifact.ApplicationVersion));
+ });
+
+ var primary = Assert.Single(artifacts.Where(artifact => artifact.Role == "Primary"));
+ Assert.Equal(packageType == "None" ? "exe" : "msix", primary.Format, ignoreCase: true);
+ if (published && packageType == "None")
+ Assert.Contains($"{Path.DirectorySeparatorChar}publish{Path.DirectorySeparatorChar}", primary.Path, StringComparison.OrdinalIgnoreCase);
+
+ Assert.Contains(artifacts, artifact => artifact.Role == "PayloadDirectory");
+ if (packageType == "MSIX")
+ {
+ Assert.Equal("true", primary.Signed, ignoreCase: true);
+ Assert.False(string.IsNullOrWhiteSpace(primary.PackageVersion));
+ Assert.Contains(artifacts, artifact => artifact.Role == "Certificate");
+ Assert.Contains(artifacts, artifact => artifact.Role == "InstallScript");
+ }
+ else
+ {
+ Assert.Equal("false", primary.Signed, ignoreCase: true);
+ }
+ }
+
+ static void CreateSigningCertificate(string path, string password)
+ {
+ using var rsa = RSA.Create(2048);
+ var request = new CertificateRequest("CN=User Name", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
+ request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
+ var enhancedKeyUsages = new OidCollection
+ {
+ new Oid("1.3.6.1.5.5.7.3.3")
+ };
+ request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsages, false));
+ using var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
+ File.WriteAllBytes(path, certificate.Export(X509ContentType.Pfx, password));
+ }
+
+ static string GetWindowsAppSdkPackageVersion()
+ {
+ var versionsFile = Path.Combine(TestEnvironment.GetMauiDirectory(), "eng", "Versions.props");
+ var versions = XDocument.Load(versionsFile);
+ return versions.Descendants("MicrosoftWindowsAppSDKPackageVersion").Single().Value;
+ }
+
+ static string Escape(string value) => SecurityElement.Escape(value) ?? string.Empty;
+
+ sealed record WindowsArtifact(
+ string Path,
+ string Role,
+ string Format,
+ string PackageType,
+ string PackageVersion,
+ string Signed,
+ string ApplicationId,
+ string ApplicationTitle,
+ string ApplicationDisplayVersion,
+ string ApplicationVersion)
+ {
+ public static WindowsArtifact Parse(string line)
+ {
+ var fields = line.Split('|');
+ Assert.Equal(10, fields.Length);
+ return new(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fields[8], fields[9]);
+ }
+ }
}
diff --git a/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj
new file mode 100644
index 000000000000..b9d882dd3869
--- /dev/null
+++ b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/Microsoft.Maui.ApplicationArtifacts.Windows.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ true
+ false
+ Dependency
+ Microsoft.Maui.ApplicationArtifacts.Windows
+ Windows Application Artifacts
+ MSBuild targets that expose final Windows App SDK application outputs through the ApplicationArtifact item.
+
+
+
+
+
+
+
diff --git a/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets
new file mode 100644
index 000000000000..ded8fdfd38ff
--- /dev/null
+++ b/src/Workload/Microsoft.Maui.ApplicationArtifacts.Windows/buildTransitive/Microsoft.Maui.ApplicationArtifacts.Windows.targets
@@ -0,0 +1,490 @@
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactsEnabled
+ Condition="'$(EnableWindowsApplicationArtifacts)' != 'false'
+ and '$(MicrosoftWindowsAppSDKPackageDir)' != ''
+ and '$(MSBuildProjectExtension)' != '.wapproj'
+ and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'
+ and ('$(OutputType)' == 'Exe' or '$(OutputType)' == 'WinExe')">true
+
+
+ _CollectMicrosoftMauiWindowsApplicationArtifacts;
+ $(GetApplicationArtifactsDependsOn)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_MicrosoftMauiWindowsCollectingForPublish>true
+
+
+
+
+
+
+
+
+
+
+ <_MicrosoftMauiWindowsPrimaryArtifact>$([System.IO.Path]::GetFullPath('$(PublishDir)$(TargetName).exe'))
+ <_MicrosoftMauiWindowsDeploymentDirectory>$([System.IO.Path]::GetFullPath('$(PublishDir)'))
+
+
+
+
+
+
+ Primary
+ true
+ $(_MicrosoftMauiWindowsPrimaryArtifact)
+ exe
+ $(WindowsPackageType)
+ $(_MicrosoftMauiWindowsPackageVersion)
+ $(_MicrosoftMauiWindowsArchitecture)
+ $(RuntimeIdentifier)
+ $(_MicrosoftMauiWindowsSigned)
+ $(AppxBundlePlatforms)
+ $(_MicrosoftMauiWindowsEntryPoint)
+ $(_MicrosoftMauiWindowsDeploymentDirectory)
+ $(_MicrosoftMauiWindowsApplicationId)
+ $(_MicrosoftMauiWindowsApplicationIdGuid)
+ $(_MicrosoftMauiWindowsApplicationTitle)
+ $(_MicrosoftMauiWindowsApplicationName)
+ $(_MicrosoftMauiWindowsApplicationDisplayVersion)
+ $(_MicrosoftMauiWindowsApplicationVersion)
+ <_MicrosoftMauiWindowsApplicationArtifact>true
+
+
+
+ PayloadDirectory
+ false
+ $(_MicrosoftMauiWindowsPrimaryArtifact)
+ directory
+ $(WindowsPackageType)
+ $(_MicrosoftMauiWindowsPackageVersion)
+ $(_MicrosoftMauiWindowsArchitecture)
+ $(RuntimeIdentifier)
+ $(_MicrosoftMauiWindowsSigned)
+ $(AppxBundlePlatforms)
+ $(_MicrosoftMauiWindowsEntryPoint)
+ $(_MicrosoftMauiWindowsDeploymentDirectory)
+ $(_MicrosoftMauiWindowsApplicationId)
+ $(_MicrosoftMauiWindowsApplicationIdGuid)
+ $(_MicrosoftMauiWindowsApplicationTitle)
+ $(_MicrosoftMauiWindowsApplicationName)
+ $(_MicrosoftMauiWindowsApplicationDisplayVersion)
+ $(_MicrosoftMauiWindowsApplicationVersion)
+ <_MicrosoftMauiWindowsApplicationArtifact>true
+
+
+
+
+
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Remove="@(_MicrosoftMauiWindowsApplicationArtifactRaw)" />
+ <_MicrosoftMauiWindowsReportedOutput Remove="@(_MicrosoftMauiWindowsReportedOutput)" />
+ <_MicrosoftMauiWindowsApplicationArtifactNormalized Remove="@(_MicrosoftMauiWindowsApplicationArtifactNormalized)" />
+ <_MicrosoftMauiWindowsApplicationArtifactDistinct Remove="@(_MicrosoftMauiWindowsApplicationArtifactDistinct)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_MicrosoftMauiWindowsPackageVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestVersion)' != ''">$(_MicrosoftMauiWindowsManifestVersion)
+ <_MicrosoftMauiWindowsPackageVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsPackageVersion)' == ''">$(AppxManifestIdentityVersion)
+
+ <_MicrosoftMauiWindowsApplicationId Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestIdentity)' != ''">$(_MicrosoftMauiWindowsManifestIdentity)
+ <_MicrosoftMauiWindowsApplicationId Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationId)' == '' and '$(AppxManifestIdentityName)' != ''">$(AppxManifestIdentityName)
+ <_MicrosoftMauiWindowsApplicationId Condition="'$(_MicrosoftMauiWindowsApplicationId)' == '' and '$(ApplicationId)' != ''">$(ApplicationId)
+ <_MicrosoftMauiWindowsApplicationId Condition="'$(_MicrosoftMauiWindowsApplicationId)' == ''">$(AssemblyName)
+
+ <_MicrosoftMauiWindowsApplicationTitle Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestDisplayName)' != ''">$(_MicrosoftMauiWindowsManifestDisplayName)
+ <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(ApplicationTitle)' != ''">$(ApplicationTitle)
+ <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(ApplicationName)' != ''">$(ApplicationName)
+ <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == '' and '$(AssemblyTitle)' != ''">$(AssemblyTitle)
+ <_MicrosoftMauiWindowsApplicationTitle Condition="'$(_MicrosoftMauiWindowsApplicationTitle)' == ''">$(AssemblyName)
+
+ <_MicrosoftMauiWindowsApplicationName Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestDisplayName)' != ''">$(_MicrosoftMauiWindowsManifestDisplayName)
+ <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == '' and '$(ApplicationName)' != ''">$(ApplicationName)
+ <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == '' and '$(ApplicationTitle)' != ''">$(ApplicationTitle)
+ <_MicrosoftMauiWindowsApplicationName Condition="'$(_MicrosoftMauiWindowsApplicationName)' == ''">$(AssemblyName)
+
+ <_MicrosoftMauiWindowsApplicationDisplayVersion
+ Condition="'$(WindowsPackageType)' != 'None'
+ and $([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsPackageVersion)', '^\d+\.\d+\.\d+\.\d+$'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_MicrosoftMauiWindowsPackageVersion)', '^(\d+\.\d+\.\d+)\.(\d+)$', '$1'))
+ <_MicrosoftMauiWindowsApplicationVersion
+ Condition="'$(WindowsPackageType)' != 'None'
+ and $([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsPackageVersion)', '^\d+\.\d+\.\d+\.\d+$'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_MicrosoftMauiWindowsPackageVersion)', '^(\d+\.\d+\.\d+)\.(\d+)$', '$2'))
+ <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == ''">$(_MicrosoftMauiWindowsPackageVersion)
+ <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == ''">$(ApplicationVersion)
+
+ <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(ApplicationDisplayVersion)' != ''">$(ApplicationDisplayVersion)
+ <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == '' and '$(InformationalVersion)' != ''">$(InformationalVersion)
+ <_MicrosoftMauiWindowsApplicationDisplayVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationDisplayVersion)' == ''">$(Version)
+ <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(ApplicationVersion)' != ''">$(ApplicationVersion)
+ <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == '' and '$(FileVersion)' != ''">$(FileVersion)
+ <_MicrosoftMauiWindowsApplicationVersion Condition="'$(WindowsPackageType)' == 'None' and '$(_MicrosoftMauiWindowsApplicationVersion)' == ''">$(AssemblyVersion)
+
+ <_MicrosoftMauiWindowsApplicationIdGuid
+ Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(_MicrosoftMauiWindowsApplicationId)', '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|[0-9a-fA-F]{32})$'))">$(_MicrosoftMauiWindowsApplicationId)
+
+ <_MicrosoftMauiWindowsArchitecture Condition="'$(PackageArchitecture)' != ''">$(PackageArchitecture)
+ <_MicrosoftMauiWindowsArchitecture Condition="'$(_MicrosoftMauiWindowsArchitecture)' == '' and '$(PlatformTarget)' != ''">$(PlatformTarget)
+ <_MicrosoftMauiWindowsArchitecture Condition="'$(_MicrosoftMauiWindowsArchitecture)' == ''">$(Platform)
+ <_MicrosoftMauiWindowsSigned Condition="'$(WindowsPackageType)' == 'None'">false
+ <_MicrosoftMauiWindowsSigned Condition="'$(_MicrosoftMauiWindowsSigned)' == '' and '$(AppxPackageSigningEnabled)' != ''">$(AppxPackageSigningEnabled)
+ <_MicrosoftMauiWindowsSigned Condition="'$(_MicrosoftMauiWindowsSigned)' == ''">false
+
+ <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsManifestExecutable)' != ''">$(_MicrosoftMauiWindowsManifestExecutable)
+ <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' != 'None' and '$(_MicrosoftMauiWindowsEntryPoint)' == ''">$(_MicrosoftMauiWindowsManifestEntryPoint)
+ <_MicrosoftMauiWindowsEntryPoint Condition="'$(WindowsPackageType)' == 'None'">$(TargetName).exe
+
+
+
+ <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(AppxPackageOutput)' != '' and Exists('$(AppxPackageOutput)')">$([System.IO.Path]::GetFullPath('$(AppxPackageOutput)'))
+ <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' == '' and '$(AppxMainPackageOutput)' != '' and Exists('$(AppxMainPackageOutput)')">$([System.IO.Path]::GetFullPath('$(AppxMainPackageOutput)'))
+ <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(AppxPackageTestDir)' != '' and Exists('$(AppxPackageTestDir)')">$([System.IO.Path]::GetFullPath('$(AppxPackageTestDir)'))
+
+
+
+ <_MicrosoftMauiWindowsPublishExecutable>$(PublishDir)$(TargetName).exe
+ <_MicrosoftMauiWindowsPublishExecutable Condition="!Exists('$(_MicrosoftMauiWindowsPublishExecutable)')">$(PublishDir)$(AssemblyName).exe
+ <_MicrosoftMauiWindowsBuildExecutable>$(TargetDir)$(TargetName).exe
+ <_MicrosoftMauiWindowsTargetPathExtension Condition="'$(TargetPath)' != ''">$([System.IO.Path]::GetExtension('$(TargetPath)'))
+ <_MicrosoftMauiWindowsBuildExecutable Condition="!Exists('$(_MicrosoftMauiWindowsBuildExecutable)') and '$(TargetPath)' != '' and '$(_MicrosoftMauiWindowsTargetPathExtension)' == '.exe'">$(TargetPath)
+ <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsCollectingForPublish)' == 'true' and '$(PublishDir)' != '' and Exists('$(_MicrosoftMauiWindowsPublishExecutable)')">$([System.IO.Path]::GetFullPath('$(_MicrosoftMauiWindowsPublishExecutable)'))
+ <_MicrosoftMauiWindowsPrimaryArtifact Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' == '' and Exists('$(_MicrosoftMauiWindowsBuildExecutable)')">$([System.IO.Path]::GetFullPath('$(_MicrosoftMauiWindowsBuildExecutable)'))
+ <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(_MicrosoftMauiWindowsCollectingForPublish)' == 'true' and '$(PublishDir)' != '' and Exists('$(PublishDir)')">$([System.IO.Path]::GetFullPath('$(PublishDir)'))
+ <_MicrosoftMauiWindowsDeploymentDirectory Condition="'$(_MicrosoftMauiWindowsDeploymentDirectory)' == '' and '$(TargetDir)' != '' and Exists('$(TargetDir)')">$([System.IO.Path]::GetFullPath('$(TargetDir)'))
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackageOutput)"
+ Condition="'$(AppxPackageOutput)' != '' and Exists('$(AppxPackageOutput)')"
+ ArtifactRole="Primary" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxMainPackageOutput)"
+ Condition="'$(AppxMainPackageOutput)' != '' and Exists('$(AppxMainPackageOutput)')"
+ ArtifactRole="Primary" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxPackageItem)"
+ Condition="Exists('%(FinalAppxPackageItem.Identity)')"
+ ArtifactRole="Primary" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackageTestDir)"
+ Condition="'$(AppxPackageTestDir)' != '' and Exists('$(AppxPackageTestDir)')"
+ ArtifactRole="PayloadDirectory"
+ PackageFormat="directory" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxBundleOutput)"
+ Condition="'$(AppxBundleOutput)' != '' and Exists('$(AppxBundleOutput)')"
+ ArtifactRole="Bundle" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadBundleOutput)"
+ Condition="'$(AppxUploadBundleOutput)' != '' and Exists('$(AppxUploadBundleOutput)')"
+ ArtifactRole="Bundle" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxStoreContainer)"
+ Condition="'$(AppxStoreContainer)' != '' and Exists('$(AppxStoreContainer)')"
+ ArtifactRole="StoreUpload" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadPackageOutput)"
+ Condition="'$(AppxUploadPackageOutput)' != '' and Exists('$(AppxUploadPackageOutput)')"
+ ArtifactRole="StoreUpload" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadMainPackageOutput)"
+ Condition="'$(AppxUploadMainPackageOutput)' != '' and Exists('$(AppxUploadMainPackageOutput)')"
+ ArtifactRole="StoreUpload" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxUploadPackageItem)"
+ Condition="Exists('%(FinalAppxUploadPackageItem.Identity)')"
+ ArtifactRole="StoreUpload" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(AppInstallerFilePath)"
+ Condition="Exists('%(AppInstallerFilePath.Identity)')"
+ ArtifactRole="DeploymentManifest" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxSymbolPackageOutput)"
+ Condition="'$(AppxSymbolPackageOutput)' != '' and Exists('$(AppxSymbolPackageOutput)')"
+ ArtifactRole="Symbols" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxUploadSymbolPackageOutput)"
+ Condition="'$(AppxUploadSymbolPackageOutput)' != '' and Exists('$(AppxUploadSymbolPackageOutput)')"
+ ArtifactRole="Symbols" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxSymbolPackageItem)"
+ Condition="Exists('%(FinalAppxSymbolPackageItem.Identity)')"
+ ArtifactRole="Symbols" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(FinalAppxUploadSymbolPackageItem)"
+ Condition="Exists('%(FinalAppxUploadSymbolPackageItem.Identity)')"
+ ArtifactRole="Symbols" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(AppxPackagePublicKeyFile)"
+ Condition="'$(AppxPackagePublicKeyFile)' != '' and Exists('$(AppxPackagePublicKeyFile)')"
+ ArtifactRole="Certificate" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(LandingPagePath)"
+ Condition="'$(LandingPagePath)' != '' and Exists('$(LandingPagePath)')"
+ ArtifactRole="LandingPage" />
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(_MicrosoftMauiWindowsPrimaryArtifact)"
+ Condition="'$(_MicrosoftMauiWindowsPrimaryArtifact)' != ''"
+ ArtifactRole="Primary"
+ PackageFormat="exe" />
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="$(_MicrosoftMauiWindowsDeploymentDirectory)"
+ Condition="'$(_MicrosoftMauiWindowsDeploymentDirectory)' != ''"
+ ArtifactRole="PayloadDirectory"
+ PackageFormat="directory" />
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactTargetDir Condition="'$(TargetDir)' != ''">$([System.IO.Path]::GetFullPath('$(TargetDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactTargetDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactTargetDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactTargetDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactTargetDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactOutDir Condition="'$(OutDir)' != ''">$([System.IO.Path]::GetFullPath('$(OutDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactOutDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactOutDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactOutDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactOutDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactOutDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactPublishDir Condition="'$(PublishDir)' != ''">$([System.IO.Path]::GetFullPath('$(PublishDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPublishDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPublishDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPublishDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPublishDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactPackageDir Condition="'$(AppxPackageDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxPackageDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPackageDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPackageDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPackageDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactPackageTestDir Condition="'$(AppxPackageTestDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxPackageTestDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPackageTestDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactUploadDir Condition="'$(AppxUploadPackageDir)' != ''">$([System.IO.Path]::GetFullPath('$(AppxUploadPackageDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactUploadDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactUploadDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactUploadDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactUploadDir.ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactIntermediateDir Condition="'$(IntermediateOutputPath)' != ''">$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))
+ <_MicrosoftMauiWindowsApplicationArtifactIntermediateDir Condition="'$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir)' != ''">$([MSBuild]::EnsureTrailingSlash('$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir)'))
+ <_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable>$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDir.ToLowerInvariant())
+
+
+
+ <_MicrosoftMauiWindowsReportedOutput Include="@(InstallerFileWrites)">
+ <_Source>InstallerFileWrites
+
+ <_MicrosoftMauiWindowsReportedOutput Include="@(PackagingFileWrites)">
+ <_Source>PackagingFileWrites
+
+ <_MicrosoftMauiWindowsReportedOutput Include="@(AllBuiltSideloadPackages)">
+ <_Source>AllBuiltSideloadPackages
+
+ <_MicrosoftMauiWindowsReportedOutput Include="@(AllBuiltUploadPackages)">
+ <_Source>AllBuiltUploadPackages
+
+ <_MicrosoftMauiWindowsReportedOutput Include="@(AppInstallerFileWrites)">
+ <_Source>AppInstallerFileWrites
+
+
+ <_MicrosoftMauiWindowsReportedOutput Update="@(_MicrosoftMauiWindowsReportedOutput)">
+ <_ComparablePath>$([System.String]::Copy('%(FullPath)').ToLowerInvariant())
+ <_ExtensionLower>$([System.String]::Copy('%(Extension)').ToLowerInvariant())
+ <_IsFinalOutput>false
+ <_IsIntermediateOutput>false
+
+ <_MicrosoftMauiWindowsReportedOutput Update="@(_MicrosoftMauiWindowsReportedOutput)">
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactTargetDirComparable)'))">true
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactOutDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactOutDirComparable)'))">true
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPublishDirComparable)'))">true
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPackageDirComparable)'))">true
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactPackageTestDirComparable)'))">true
+ <_IsFinalOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactUploadDirComparable)'))">true
+ <_IsIntermediateOutput Condition="'$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable)' != ''
+ and $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').StartsWith('$(_MicrosoftMauiWindowsApplicationArtifactIntermediateDirComparable)'))">true
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.msixbundle'
+ or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxbundle')"
+ ArtifactRole="Bundle" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.msixupload'
+ or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxupload'
+ or ('%(_MicrosoftMauiWindowsReportedOutput._Source)' == 'AllBuiltUploadPackages'
+ and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' != '.msixbundle'
+ and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' != '.appxbundle'))"
+ ArtifactRole="StoreUpload" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appinstaller'"
+ ArtifactRole="DeploymentManifest" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.appxsym'"
+ ArtifactRole="Symbols" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.cer'"
+ ArtifactRole="Certificate" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and ('%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.ps1'
+ or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.psd1'
+ or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.cmd'
+ or '%(_MicrosoftMauiWindowsReportedOutput._ExtensionLower)' == '.bat')"
+ ArtifactRole="InstallScript" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')
+ and ($([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').Contains('\dependencies\'))
+ or $([System.String]::Copy('%(_MicrosoftMauiWindowsReportedOutput._ComparablePath)').Contains('/dependencies/')))"
+ ArtifactRole="DependencyPackage" />
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Include="@(_MicrosoftMauiWindowsReportedOutput->'%(FullPath)')"
+ Condition="'%(_MicrosoftMauiWindowsReportedOutput._IsFinalOutput)' == 'true'
+ and '%(_MicrosoftMauiWindowsReportedOutput._IsIntermediateOutput)' != 'true'
+ and Exists('%(_MicrosoftMauiWindowsReportedOutput.Identity)')"
+ ArtifactRole="Support" />
+
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Update="@(_MicrosoftMauiWindowsApplicationArtifactRaw)"
+ Condition="'%(_MicrosoftMauiWindowsApplicationArtifactRaw.PackageFormat)' == ''
+ and '%(_MicrosoftMauiWindowsApplicationArtifactRaw.Extension)' != ''">
+ $([System.String]::Copy('%(_MicrosoftMauiWindowsApplicationArtifactRaw.Extension)').TrimStart('.').ToLowerInvariant())
+
+ <_MicrosoftMauiWindowsApplicationArtifactRaw Update="@(_MicrosoftMauiWindowsApplicationArtifactRaw)"
+ Condition="'%(_MicrosoftMauiWindowsApplicationArtifactRaw.PackageFormat)' == ''">
+ file
+
+
+ <_MicrosoftMauiWindowsApplicationArtifactNormalized
+ Include="@(_MicrosoftMauiWindowsApplicationArtifactRaw->'%(FullPath)')"
+ KeepMetadata="ArtifactRole;PackageFormat" />
+
+
+
+
+
+
+
+
+ %(_MicrosoftMauiWindowsApplicationArtifactDistinct.ArtifactRole)
+ true
+ false
+ %(_MicrosoftMauiWindowsApplicationArtifactDistinct.Identity)
+ $(_MicrosoftMauiWindowsPrimaryArtifact)
+ %(_MicrosoftMauiWindowsApplicationArtifactDistinct.PackageFormat)
+ $(WindowsPackageType)
+ $(_MicrosoftMauiWindowsPackageVersion)
+ $(_MicrosoftMauiWindowsArchitecture)
+ $(RuntimeIdentifier)
+ $(_MicrosoftMauiWindowsSigned)
+ $(AppxBundlePlatforms)
+ $(_MicrosoftMauiWindowsEntryPoint)
+ $(_MicrosoftMauiWindowsDeploymentDirectory)
+ $(_MicrosoftMauiWindowsApplicationId)
+ $(_MicrosoftMauiWindowsApplicationIdGuid)
+ $(_MicrosoftMauiWindowsApplicationTitle)
+ $(_MicrosoftMauiWindowsApplicationName)
+ $(_MicrosoftMauiWindowsApplicationDisplayVersion)
+ $(_MicrosoftMauiWindowsApplicationVersion)
+ <_MicrosoftMauiWindowsApplicationArtifact>true
+
+
+
+
+
diff --git a/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json b/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json
index 9a742a5d6bf1..e8803e6f4c6b 100644
--- a/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json
+++ b/src/Workload/Microsoft.NET.Sdk.Maui.Manifest/WorkloadManifest.in.json
@@ -74,6 +74,7 @@
"description": ".NET MAUI SDK for Windows",
"extends": [ "maui-blazor" ],
"packs": [
+ "Microsoft.Maui.ApplicationArtifacts.Windows",
"Microsoft.Maui.Graphics.Windows"
]
},
@@ -130,6 +131,10 @@
"win-arm64": "Microsoft.Maui.Graphics.Win2D.WinUI.Desktop"
}
},
+ "Microsoft.Maui.ApplicationArtifacts.Windows": {
+ "kind": "library",
+ "version": "@VERSION@"
+ },
"Microsoft.Maui.Resizetizer": {
"kind": "library",
"version": "@VERSION@"
diff --git a/src/Workload/README.md b/src/Workload/README.md
index 82f4738f5df6..66090677a3aa 100644
--- a/src/Workload/README.md
+++ b/src/Workload/README.md
@@ -89,6 +89,8 @@ installed:
* `maui-windows`
* `maui-tizen`
+The `maui-windows` workload also installs `Microsoft.Maui.ApplicationArtifacts.Windows`, a targets-only package that exposes final modern Windows App SDK build and publish outputs as `@(ApplicationArtifact)`. The package can be referenced directly by non-MAUI Windows App SDK applications; classic `.wapproj` projects are outside its scope.
+
`maui-android` simply extends the `android` workload, adding the
Android-specific platform implementation for MAUI.