App Attach in Azure Virtual Desktop: Deliver Apps Without Touching the Image

The worst part of running AVD is not building the image. It is rebuilding it. Finance needs their reporting tool bumped a minor version, so you rebuild the image, replicate it across regions, and cycle every session host in the pool for a change that affects eleven people. I have watched teams schedule a maintenance window to update a PDF reader.
App attach is the way out of that loop. Instead of baking applications into the image, you package them once, put them on a file share as a disk image, and the platform mounts them at sign-in for the users who are entitled to them. The image stays a clean OS. Applications become assignments, not builds.
There is an important naming problem to clear up first, because it will affect every search you run. MSIX app attach was retired on 1 June 2025. The feature that replaced it is called simply app attach, and it handles MSIX, App-V and Appx packages rather than MSIX alone. Most of the guidance still floating around online is written against the retired version, and the two configure differently. If a walkthrough tells you to attach the package to the host pool, you are reading the old one.
What Changed, and Why It Matters
The distinction is not cosmetic. The two features have genuinely different models.
| MSIX app attach (retired) | App attach (current) | |
|---|---|---|
| Package formats | MSIX only | MSIX, App-V, Appx |
| Attached to | Host pool | Application group, then assigned to users |
| Assignment granularity | Every host in the pool | Per user or per group |
| Package object | Lived inside the host pool | A standalone Azure resource |
That third row is the one that changes how you design. Under the old model, a package attached to a host pool was present for everyone who landed on it, and entitlement was something you faked with shortcuts and permissions. Under app attach, the package is an object you assign, so two users on the same session host can genuinely have different applications available.
Warning: If you have an existing deployment using MSIX app attach, it is on a retired feature. Packages need to be recreated as app attach package objects. The disk images themselves are reusable, so the migration is mostly re-registering, not repackaging.
Prerequisites
- An AVD host pool with a pooled or personal desktop application group
- A file share the session hosts can read. Azure Files with Entra Kerberos works, and if you already built FSLogix profile containers on Azure Files you can reuse the same storage account with a separate share
- An MSIX package signed with a certificate your session hosts trust
- The MSIX Packaging Tool if you are converting an existing installer
Set your variables:
RG="rg-avd-prod"
POOL="hp-avd-prod"
APPGROUP="ag-avd-desktop"
LOCATION="canadacentral"
SHARE_UNC="\\\\stavdprofiles.file.core.windows.net\\appattach"
Step 1: Package the Application
If your vendor ships an MSIX, skip this. Most do not, so you will be converting an MSI or EXE with the MSIX Packaging Tool.
The conversion itself is a wizard, and the wizard is not the hard part. The hard part is that MSIX runs applications in a container with a virtualised registry and file system, and applications that write to their own install directory or to HKLM at runtime will misbehave in ways that only appear under load.
Before you spend a day packaging something, check the obvious disqualifiers:
- Does it install a driver or a service? Those do not package.
- Does it need to run elevated? MSIX packages run as the user.
- Does it write to
Program Filesafter install? That path is read-only in the container.
Pro tip: Package on a clean VM built from the same image your session hosts use. Packaging on your laptop captures your laptop's state, and you will spend the afternoon wondering why a dependency resolves for you and nobody else.
Step 2: Build the Disk Image
App attach mounts the application from a disk image. You have two formats, and the choice is not a toss-up.
CIM is the one to use. Composite image files mount faster, use less memory per session, and were built for exactly this pattern. VHDX still works and is what the older guidance describes.
Convert the MSIX to CIM with the MSIXMGR tool:
.\msixmgr.exe -Unpack `
-packagePath "C:\packages\ContosoReporting_1.4.0.0_x64.msix" `
-destination "C:\images\ContosoReporting.cim" `
-applyACLs `
-create `
-fileType CIM `
-rootDirectory apps
-applyACLs is not optional. Without it the mounted package has no permissions that let a standard user read it, and the application silently fails to appear. The -rootDirectory value must be consistent across your packages because it becomes part of the mount path.
Copy the resulting image to the share:
Copy-Item "C:\images\ContosoReporting.cim" -Destination "$env:SHARE_UNC\"
Step 3: Create the App Attach Package Object
This is the step that is completely different from the retired feature. The package is now its own Azure resource, created at the subscription level and then linked to application groups.
Pull the package metadata from the image first. The platform needs the package family name, version and dependencies, and it reads them from the image itself:
az desktopvirtualization appattach-package import \
--resource-group $RG \
--location $LOCATION \
--package-path "$SHARE_UNC\ContosoReporting.cim"
Then create the package object with that metadata:
az desktopvirtualization appattach-package create \
--resource-group $RG \
--name "contoso-reporting" \
--location $LOCATION \
--image-path "$SHARE_UNC\ContosoReporting.cim" \
--is-active true \
--fail-health-check-on-staging-failure "NeedsAssistance" \
--host-pool-references "/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.DesktopVirtualization/hostPools/$POOL"
--is-active true is what makes the package available for assignment. You can create a package as inactive, stage it, and flip it live later, which is how you do a controlled rollout of a version bump.
Step 4: Assign the Application to Users
The package existing does not put it on anyone's desktop. Assignment does.
GROUP_ID=$(az ad group show --group "sg-finance-users" --query id -o tsv)
az role assignment create \
--assignee $GROUP_ID \
--role "Desktop Virtualization User" \
--scope "/subscriptions/$SUB_ID/resourceGroups/$RG/providers/Microsoft.DesktopVirtualization/applicationGroups/$APPGROUP"
For a published application rather than a full desktop, you create an application in the app group that points at the package:
az desktopvirtualization application create \
--resource-group $RG \
--application-group-name $APPGROUP \
--name "ContosoReporting" \
--command-line-setting "DoNotAllow" \
--application-type "MsixApplication" \
--msix-package-family-name "ContosoReporting_8wekyb3d8bbwe" \
--msix-package-application-id "ContosoReporting"
The package family name has to match exactly what is inside the package. Copy it from the import output rather than typing it, because a single wrong character produces an application that appears in the feed and fails to launch with no useful error.
Step 5: Trust the Signing Certificate
Session hosts will not run a package they cannot validate. If you signed with an internal CA or a self-signed certificate, that certificate has to be in the trusted store on every host.
Import-Certificate `
-FilePath "\\fileserver\certs\packaging-signing.cer" `
-CertStoreLocation Cert:\LocalMachine\TrustedPeople
TrustedPeople is the correct store for package signing certificates, not Root. This trips people up because most certificate guidance points at Root by habit.
Gotcha: A package signed with an expired certificate keeps working on hosts where it is already staged, and fails on any host that stages it fresh. That produces the worst possible symptom, which is an application that works for most people and not for the person who happened to land on a newly scaled-out host.
Step 6: Verify the Staging
Do not trust the portal showing the package as active. Check on a host.
# Is the package staged on this session host?
Get-AppxPackage -AllUsers | Where-Object { $_.Name -like "*ContosoReporting*" } |
Select-Object Name, Version, InstallLocation, Status
# App attach events
Get-WinEvent -LogName "Microsoft-Windows-HostAppAttach/Operational" -MaxEvents 30 |
Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-List
A healthy sequence is stage, register, then the application appearing in the feed. If you see stage succeed and register fail, that is nearly always the certificate or the package family name.
What Will Bite You
The application appears in the feed and will not launch. Package family name mismatch between the package object and the application definition. Compare them character by character.
It works on some hosts and not others. Either the certificate is missing from the newer hosts, or the share permissions do not cover the computer accounts. App attach mounts the image as the session host, not as the user, so the hosts themselves need read access to the share.
Version updates leave users on the old build. A package object points at a specific image path. Updating means creating a new package object for the new version, activating it, and deactivating the old one. Overwriting the file on the share does not update anything, and it will corrupt the mount for anyone currently attached.
Performance is worse than baking the app into the image. If you converted to VHDX instead of CIM, that is expected. VHDX mounts are heavier per session, and on a pooled host with forty users the difference is measurable.
Pro tip: Keep one application in the image on purpose. Whatever your users open first every morning is worth baking in, because the first app attach mount of a session carries a small startup cost and you do not want to spend it on the thing people notice.
When to Use App Attach and When Not To
App attach is right when the application changes on a different cadence than the OS, when only a subset of users need it, or when licensing means you have to prove who has access.
It is the wrong tool when the application is used by everyone on every host, when it needs a driver or a service, or when it is genuinely tiny. There is real operational overhead in packaging and version management, and for an application that every user opens and that updates twice a year, the image is simply simpler.
I generally end up with a split: the OS, the browser, and the core productivity suite in the image, and everything departmental delivered through app attach.
Conclusions
The feature is good and the documentation landscape around it is a mess, because half of what is published describes a version that was retired in June 2025. Work from the current model: packages are standalone resources, they attach to application groups, and they are assigned to users.
Get the certificate into TrustedPeople, use CIM rather than VHDX, and treat a version bump as a new package object rather than a file overwrite. Those three cover most of what goes wrong.
What's Next
- Azure Monitor for AVD to catch staging failures before a user reports one
- Scaling plans to stop paying for hosts that sit idle overnight
- Image management with Azure Image Builder for the applications that genuinely do belong in the image



