Skip to content

Add public-API packaged-app identity/AUMID groundwork and Windows gating to PackagedApp extension#9914

Open
Evangelink wants to merge 1 commit into
mainfrom
dev/amauryleve/improve-packagedapp-extension
Open

Add public-API packaged-app identity/AUMID groundwork and Windows gating to PackagedApp extension#9914
Evangelink wants to merge 1 commit into
mainfrom
dev/amauryleve/improve-packagedapp-extension

Conversation

@Evangelink

Copy link
Copy Markdown
Member

Summary

Follow-up to the discussion under #9908 about Microsoft.Testing.Extensions.PackagedApp. Today the launcher's only real launch path is CopyDirectory + Process.Start, which cannot launch a packaged (MSIX) WinUI/UWP app. VSTest gets loose-layout registration and AUMID activation from Visual-Studio-internal deployment components (IVsAppContainerProjectDeploy2, IDeploymentServicesProvider.GetAppLauncher(), MEF-composed from the VS install) that are not redistributable, so a standalone MTP extension cannot depend on them.

This PR lands the parts of that flow that need no VS-internal API, plus makes the extension honest about what it can do today.

Changes

  • AppxManifestInfo (new): parses AppxManifest.xml and computes the PackageFamilyName and Application User Model ID ({PackageFamilyName}!{AppId}) using the public Windows publisher-hash algorithm — SHA-256 of the UTF-16LE Publisher, first 8 bytes, Crockford base32. Pure managed and cross-platform; this is the public replacement for VS's internal deploymentFile.AppUserModelId. Unit-tested against the well-known 8wekyb3d8bbwe vector.
  • Windows gating: IsEnabledAsync() now returns OperatingSystem.IsWindows(). On non-Windows the launcher is never registered, so the platform keeps its default in-process path instead of being forced onto the controller/deploy path by a no-op launcher.
  • Fail fast on packaged layouts: LaunchTestHostAsync detects a packaged layout (presence of AppxManifest.xml) and throws an actionable error — including the AUMID that activation would use and a pointer to Add support for testing unpackaged WinUI applications #2784 — instead of silently Process.Starting an executable that can't host the run. Non-packaged (loose-layout) hosts keep the existing behavior unchanged.

Still a follow-up (not in this PR — #2784)

Actual PackageManager.RegisterPackageByUriAsync registration, IApplicationActivationManager AUMID activation, and the AppContainer connect-back transport (the platform passes the controller IPC pipe name via an environment variable, which an activated packaged process does not inherit) remain to be done.

Verification

  • Microsoft.Testing.Extensions.PackagedApp builds with 0 warnings / 0 errors (incl. -p:TreatWarningsAsErrors=true); internal API and xlf updated.
  • New AppxManifestInfoTests (12 tests) discovered and passing; full Microsoft.Testing.Extensions.UnitTests suite green (640 passed, 4 skipped, 0 failed).

The PackagedApp launcher's only real launch path was CopyDirectory + Process.Start, which cannot launch a packaged (MSIX) WinUI/UWP app; VSTest gets registration + AUMID activation from VS-internal deployment components (IVsAppContainerProjectDeploy2, IDeploymentServicesProvider) that are not redistributable.

This adds the parts that need no VS-internal API:
- AppxManifestInfo: parses AppxManifest.xml and computes PackageFamilyName + Application User Model ID using the public Windows publisher-hash algorithm (SHA-256 of the UTF-16LE publisher, first 8 bytes, Crockford base32). Pure managed, cross-platform, unit-tested against the well-known 8wekyb3d8bbwe vector.
- IsEnabledAsync now gates on OperatingSystem.IsWindows(), so on non-Windows the launcher is never registered and the platform keeps its default in-process path (instead of being forced onto the controller/deploy path).
- LaunchTestHostAsync now detects a packaged layout (AppxManifest.xml) and fails fast with an actionable message including the AUMID activation would use, referencing #2784, instead of silently starting an executable that cannot host the run. Non-packaged (loose-layout) hosts keep the existing Process.Start path.

Registration (PackageManager.RegisterPackageByUriAsync) + AUMID activation (IApplicationActivationManager) and the sandbox connect-back transport remain a follow-up (#2784).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c5c7d634-e822-4475-9312-a9dde92753f6
Copilot AI review requested due to automatic review settings July 13, 2026 20:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds groundwork in the Microsoft.Testing.Extensions.PackagedApp extension to recognize packaged (MSIX) layouts by parsing AppxManifest.xml to compute PFN/AUMID, gates the extension to Windows-only registration, and fails fast with an actionable error when a packaged layout is detected (since AUMID activation isn’t implemented yet).

Changes:

  • Add AppxManifestInfo to parse AppxManifest.xml and compute PackageFamilyName and AppUserModelId (AUMID), plus unit tests for the publisher-hash algorithm and manifest parsing.
  • Gate PackagedAppTestHostLauncher.IsEnabledAsync() to OperatingSystem.IsWindows() and reject packaged layouts (manifest present) with a localized, actionable error.
  • Update resources (.resx + .xlf) and internal API tracking; expose internals to the unit test assembly.
Show a summary per file
File Description
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj Conditionally reference the PackagedApp extension only for .NETCoreApp TFMs.
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/AppxManifestInfoTests.cs New unit tests covering publisher-id hashing and manifest parsing edge cases.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/AppxManifestInfo.cs New manifest parser + PFN/AUMID computation.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/PackagedAppTestHostLauncher.cs Windows gating + fail-fast rejection for packaged layouts; updated doc remarks.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/ExtensionResources.resx Add new localized strings for invalid manifest and unsupported packaged launch.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.cs.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.de.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.es.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.fr.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.it.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.ja.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.ko.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.pl.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.pt-BR.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.ru.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.tr.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.zh-Hans.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Resources/xlf/ExtensionResources.zh-Hant.xlf Add new trans-units for the new resource keys.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/Microsoft.Testing.Extensions.PackagedApp.csproj Add InternalsVisibleTo for the unit test assembly.
src/Platform/Microsoft.Testing.Extensions.PackagedApp/InternalAPI/InternalAPI.Unshipped.txt Track newly introduced internal API surface for AppxManifestInfo.

Review details

  • Files reviewed: 20/20 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines +83 to +84
info = ReadFromManifest(manifestPath);
return true;
Comment on lines +50 to +53
// Packaged Windows apps (UWP/WinUI) are a Windows-only concept. On other operating systems the
// launcher stays disabled so it is never registered, which keeps the platform on its default
// in-process/Process.Start path instead of forcing the controller (deploy-and-launch) host.
public Task<bool> IsEnabledAsync() => Task.FromResult(OperatingSystem.IsWindows());
Comment on lines +67 to +71
if (AppxManifestInfo.TryReadFromLayout(sourceDirectory, out AppxManifestInfo? manifestInfo))
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test quality grade — PR #9914

GradeTestNotes
B (80–89) new AppxManifestInfoTests.
ComputePublisherId_
IsThirteenCharacters
Verifies only the output length; consider also asserting the output uses only lowercase base-32 characters ([a-z2-7]) to pin the encoding, not just the count.
B (80–89) new AppxManifestInfoTests.
ComputePublisherId_
IsStableForSameInput
Tests determinism correctly, but calling a pure function twice with identical input is narrow — pairing it with a known-output assertion (as in the sibling test) would give stronger coverage; consider using Directory.CreateTempSubdirectory() in the layout tests to remove the manual try/finally cleanup pattern.
A (90–100) new AppxManifestInfoTests.
ComputePublisherId_
ForMicrosoftStorePublisher_
MatchesWellKnownHash
Anchors the hash algorithm to a real-world externally verifiable constant — an excellent regression guard. No issues found.
A (90–100) new AppxManifestInfoTests.
ComputePublisherId_
IsCaseSensitive
Clearly names and verifies a security-relevant property of the hash function. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
ComputesPackageFamilyNameAndAppUserModelId
Five assertions cover every derived property — thorough and well-named. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
IgnoresSchemaNamespaceVersion
Important resilience test for namespace-version variance; inline XML keeps the scenario self-contained. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
UsesFirstApplicationId
Concise and precise; verifies tie-breaking behavior with a minimal multi-application manifest. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
WithoutApplication_
LeavesAppUserModelIdNull
Three assertions under the no-application scenario including PackageFamilyName — appropriately thorough. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
WithoutIdentity_
Throws
Uses ThrowsExactly to pin the precise exception type — stronger than ThrowsException. No issues found.
A (90–100) new AppxManifestInfoTests.
ReadFromManifest_
WithoutPublisher_
Throws
Same precise exception handling as the identity-missing sibling. No issues found.
A (90–100) new AppxManifestInfoTests.
TryReadFromLayout_
WithManifest_
ReturnsParsedInfo
Three assertions cover the happy path; consider also asserting PackageName and Publisher for full round-trip fidelity — Directory.CreateTempSubdirectory() would replace the try/finally boilerplate.
A (90–100) new AppxManifestInfoTests.
TryReadFromLayout_
WithoutManifest_
ReturnsFalse
Correct assertions on both return value and out-param; Directory.CreateTempSubdirectory() (.NET 7+) would simplify cleanup and eliminate the risk of leaking the temp directory on test failure.

This advisory comment was generated automatically. Grades are heuristic
and informational — they do not block merging. Re-run with
/grade-tests.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🤖 Automated content by GitHub Copilot. Generated by the Grade Tests on PR (on open / sync) workflow. · 51.3 AIC · ⌖ 7.74 AIC · ⊞ 8.9K · [◷]( · )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants