microsoft / microsoft/WindowsAppSDK

dotnet publish omits the app's .pri for unpackaged WinUI apps: publish wiring exists only on the MSIX path, not the MrtCore path

Open
#6,720 2 comments 0 reactions 0 assignees View on GitHub
area-MRTCore area-Packaging needs-triage
Dominant language
C++
Stars
4.7k
Forks
471
Avg merge
3d 13h
Merged PRs (30d)
28

Description

## Summary

`dotnet publish` of an **unpackaged** WinUI app (`UseWinUI=true`, `WindowsPackageType=None`) silently omits the app's own `.pri`, while correctly publishing the Windows App SDK runtime PRIs (`Microsoft.UI.pri`, `Microsoft.UI.Xaml.Controls.pri`, …).

The PRI **is** generated into the build output. It is simply never wired into the publish item list.

> **Note:** an earlier revision of this issue proposed an `AfterTargets` → `BeforeTargets` change on `AddProjectPriToResolvedFileToPublish`. **That was wrong** — I tested it by patching the targets file directly and it does not fix anything, because that target is never imported in an unpackaged build. Corrected root cause below.

## Repro

Minimal blank WinUI 3 csproj — **no third-party framework involved**:

```xml
WinExe
true
None
```

```powershell
dotnet publish BlankWinUI3.csproj -c Release -p:Platform=x64 -p:PublishAot=false -o out
```

| Location | `BlankWinUI3.pri` |
|---|---|
| build output (`bin\x64\Release\net10.0-windows10.0.22621.0`) | **present** (672 bytes) |
| publish output (`out`) | **absent** |

## Root cause

The publish wiring exists, but only on the **MSIX packaging** code path, which an unpackaged project never imports.

`Microsoft.Windows.SDK.BuildTools.MSIX.targets` gates the packaging chain:

```xml
$(MsixPackageSupport)
...

```

For the repro project, evaluated:

```
MsixPackageSupport = '' (empty)
ShouldImportMsixCommonTargets = '' (empty -> import condition '== true' is false)
WindowsPackageType = None
```

So the MSIX chain — `Microsoft.Windows.SDK.BuildTools.MSIX.Packaging.targets`, and the `Microsoft.Windows.SDK.BuildTools.MSIX.Pri.targets` it imports at line 4813 — is **never imported**. `Pri.targets` is where `AddProjectPriToResolvedFileToPublish` lives, so that target simply does not exist in the project:

```
> dotnet build BlankWinUI3.csproj -t:AddProjectPriToResolvedFileToPublish
error MSB4057: The target "AddProjectPriToResolvedFileToPublish" does not exist in the project.
```

Meanwhile the PRI itself is produced by the **MrtCore** path, which *is* imported unconditionally (and, per the comment above, is the path that does the work precisely when MSIX tooling is off). That path has no publish wiring at all:

| Targets file | `ResolvedFileToPublish` / `CopyToPublishDirectory` hits |
|---|---|
| `...MSIX.MrtCore.targets` | 0 |
| `...MSIX.MrtCore.PriGen.targets` | 0 |
| `...MSIX.MrtCore.PriExpansion.targets` | 0 |
| `...MSIX.MrtCore.Tasks.targets` | 0 |
| `...MSIX.Packaging.targets` *(MSIX-only)* | 10 |
| `...MSIX.Pri.targets` *(MSIX-only)* | 3 |

**In short: the code that publishes the PRI lives only on the MSIX path, while the code that generates the PRI for unpackaged apps lives on the MrtCore path — which has none.**

The relevant properties are all correctly populated on the unpackaged path, so nothing else is missing — only the item wiring:

```
ProjectPriFullPath = ...\bin\x64\Release\net10.0-windows10.0.22621.0\BlankWinUI3.pri
ProjectPriFileName = BlankWinUI3.pri
CopyBuildOutputToPublishDirectory = true
```

## Suggested fix

Wire the app PRI into `ResolvedFileToPublish` on the **MrtCore / unpackaged** path (or move/duplicate `AddProjectPriToResolvedFileToPublish` somewhere imported regardless of `ShouldImportMsixCommonTargets`). Verified working when injected into an unpackaged build:

```xml



$(ProjectPriFileName)
PreserveNewest

```

With this present, `BlankWinUI3.pri` lands in the publish output.

## Impact

**Silent at publish time, fatal at runtime for apps that need their PRI.** Severity depends on whether the app resolves resources from its own PRI:

- A trivial code-only WinUI app (a `TextBlock`, no resource dictionaries) runs fine without it, so this can go unnoticed.
- An app that loads XAML resource dictionaries dies **~10 seconds after launch** with `0xC000027B` (stowed XAML exception), faulting in `Microsoft.UI.Xaml.dll`. There is no build or publish diagnostic pointing at the missing file.

Verified bidirectionally on such an app: with the `.pri` present it launches and renders correctly; delete that one file from the same publish output and it exits `0xC000027B`. Nothing else changed.

This also affects .NET 10 **file-based apps** (`dotnet publish app.cs`) identically — same missing file, same crash — for both AOT and non-AOT publish.

## Workaround for consumers

The target above can be dropped into a `Directory.Build.targets` (or injected via `CustomAfterMicrosoftCommonTargets`). It is additive and `Exists()`-guarded, so it goes inert once this is fixed.

## Environment

- .NET SDK **10.0.400**
- `Microsoft.Windows.SDK.BuildTools.MSIX` **1.7.251221100**, `Microsoft.WindowsAppSDK.WinUI` **2.1.0**
- Windows 11, x64, unpackaged (`WindowsPackageType=None`)

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with Microsoft.Windows.SDK.BuildTools.MSIX.targets and the imported MSIX.MrtCore.targets, then compare the publish wiring in MSIX.Packaging.targets and MSIX.Pri.targets. Run the provided unpackaged WinUI publish command and inspect the resolved publish items. Done means the generated BlankWinUI3.pri appears in publish output without affecting the MSIX path.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
build-system, desktop
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.