Test and troubleshoot MSIX packages with MSIX app attach
This article will show you how to use MSIX app attach to mount MSIX packages outside of Azure Virtual Desktop for testing and troubleshooting.
To use MSIX app attach with Azure Virtual Desktop, you can use the Azure portal or Azure PowerShell to add and publish applications.
Prerequisites
Before you can use MSIX app attach to follow the directions in this article, you'll need the following things:
A Windows 10 or 11 client.
An application you've expanded from MSIX format into app attach format. To learn how to expand an MSIX application, see Using the MSIXMGR tool.
If you're using a CimFS image, you'll need to install the following module before you can get started:
Install-Module CimDiskImage Import-Module CimDiskImage
These instructions don't require an Azure Virtual Desktop deployment because they describe a process for testing outside of Azure Virtual Desktop.
Note
Microsoft Support doesn't currently support this CimFS disk image module, so if you run into any problems, you'll need to submit a request on the module's GitHub repository.
Phases of MSIX app attach
To use MSIX packages outside of Azure Virtual Desktop, there are four distinct phases that you must perform in the following order, otherwise it won't work:
- Stage
- Register
- Deregister
- Destage
Staging and destaging are machine-level operations, while registering and deregistering are user-level operations. The commands you'll need to use will vary based on which version of PowerShell you're using and whether your disk images are in CimFS or VHD(X) format.
Note
All MSIX application packages include a certificate. You're responsible for making sure the certificates for MSIX applications are trusted in your environment.
Stage the MSIX package
The staging script prepares your machine to receive the MSIX package and mounts the relevant package to your machine. You'll only need to run the following commands once per machine.
However, if you're using an image in CimFS format or a version of PowerShell greater than 5.1, the instructions will look a bit different. Later versions of PowerShell are multi-platform, which means the Windows application parts are split off into their own package called Windows Runtime. You'll need to use a slightly different version of the commands to install a package with a multi-platform version of PowerShell.
You'll need to run PowerShell as an Administrator to run the commands in the following sections.
Next, you'll need to decide which instructions you need to follow to stage your package based on which version of PowerShell you're using.
PowerShell 6 and later
To stage packages at boot using PowerShell 6 or later, you'll need to run the following commands before the staging operations to bring the capabilities of the Windows Runtime package you previously installed into the PowerShell session.
First, run this command to get the Windows Runtime Package:
#Required for PowerShell 6 and later $nuGetPackageName = 'Microsoft.Windows.SDK.NET.Ref' Register-PackageSource -Name MyNuGet -Location https://www.nuget.org/api/v2 -ProviderName NuGet Find-Package $nuGetPackageName | Install-Package
Next, run the following command to make the Windows Runtime components available in your PowerShell session.
#Required for PowerShell 6 and later $nuGetPackageName = 'Microsoft.Windows.SDK.NET.Ref' $winRT = Get-Package $nuGetPackageName $dllWinRT = Get-Childitem (Split-Path -Parent $winRT.Source) -Recurse -File WinRT.Runtime.dll $dllSdkNet = Get-Childitem (Split-Path -Parent $winRT.Source) -Recurse -File Microsoft.Windows.SDK.NET.dll Add-Type -AssemblyName $dllWinRT.FullName Add-Type -AssemblyName $dllSdkNet.FullName
PowerShell 5.1 and earlier
To stage packages at boot with PowerShell version 5.1 or earlier, run this command:
#Required for PowerShell versions less than or equal to 5.1
[Windows.Management.Deployment.PackageManager,Windows.Management.Deployment,ContentType=WindowsRuntime] | Out-Null
Add-Type -AssemblyName System.Runtime.WindowsRuntime
Mount your disk image
Now that you've prepared your machine to stage MSIX app attach packages, you'll need to mount your disk image. This process will vary depending on whether you're using the VHD(X) or CimFs format for your disk image.
Note
Make sure to record the the Device Id for each application in the command output. You'll need this information to follow directions later in this article.
To mount a CimFS disk image:
Run this command:
$diskImage = "<UNC path to the Disk Image>" $mount = Mount-CimDiskimage -ImagePath $diskImage -PassThru -NoMountPath #We can now get the Device Id for the mounted volume, this will be useful for the destage step. $DeviceId = $mount.DeviceId Write-Output $DeviceId
When you're done, proceed to Finish staging your disk image.
Finish staging your disk image
Finally, you'll need to run the following command for all image formats to complete staging the disk image. This command will use the $DeviceId
variable you created when you mounted your disk image in the previous section.
#Once the volume is mounted we can retrieve the application information
$manifest = Get-Childitem -LiteralPath $DeviceId -Recurse -File AppxManifest.xml
$manifestFolder = $manifest.DirectoryName
#We can now get the MSIX package full name, this will be needed for later steps.
$msixPackageFullName = $manifestFolder.Split('\')[-1]
Write-Output $msixPackageFullName
#We need to create an absolute uri for the manifest folder for the Package Manager API
$folderUri = $maniFestFolder.Replace('\\?\','file:\\\')
$folderAbsoluteUri = ([Uri]$folderUri).AbsoluteUri
#Package Manager will now use the absolute uri to stage the application package
$asTask = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { $_.ToString() -eq 'System.Threading.Tasks.Task`1[TResult] AsTask[TResult,TProgress](Windows.Foundation.IAsyncOperationWithProgress`2[TResult,TProgress])' })[0]
$asTaskAsyncOperation = $asTask.MakeGenericMethod([Windows.Management.Deployment.DeploymentResult], [Windows.Management.Deployment.DeploymentProgress])
$packageManager = New-Object -TypeName Windows.Management.Deployment.PackageManager
$asyncOperation = $packageManager.StagePackageAsync($folderAbsoluteUri, $null, "StageInPlace")
$stagingResult = $asTaskAsyncOperation.Invoke($null, @($asyncOperation))
#You can check the $stagingResult variable to monitor the staging progress for the application package
Write-Output $stagingResult
Your MSIX package is now ready to be registered.
Register the MSIX package
To register your MSIX package, run the following PowerShell cmdlets with the placeholder values replaced with values that apply to your environment.
The $msixPackageFullName
parameter should be the full name of the package from the previous section, but the format should be similar to the following example: Publisher.Application_version_Platform__HashCode
.
If you didn't retrieve the parameter after staging your app, you can also find it as the folder name for the app itself in C:\Program Files\WindowsApps.
$msixPackageFullName = "<package full name>"
$manifestPath = Join-Path (Join-Path $Env:ProgramFiles 'WindowsApps') (Join-Path $msixPackageFullName AppxManifest.xml)
Add-AppxPackage -Path $manifestPath -DisableDevelopmentMode -Register
Now that your MSIX package is registered, your application should be available for use in your session. You can now open the application for testing and troubleshooting.
Deregister the MSIX package
If you're finished with your package and are ready to remove it, now it's time to deregister it. In order to deregister, you'll need the $msixPackageFullName
parameter again.
To deregister your package, run the following command after replacing the placeholder text with the relevant values:
$msixPackageFullName = "<package full name>"
Remove-AppxPackage $msixPackageFullName -PreserveRoamableApplicationData
Destage the MSIX package
To destage your MSIX package, make sure you're running an elevated PowerShell prompt. You'll need to run the following PowerShell command to get the disk's DeviceId
parameter. Replace the placeholder for $packageFullName
with the name of the package you're testing. In a production deployment, we recommend only running this command when shutting down your system.
$msixPackageFullName = "<package full name>"
#If you don't know the DeviceId of the mounted disk, you can find it using the following code.
$appPath = Join-Path (Join-Path $Env:ProgramFiles 'WindowsApps') $msixPackageFullName
$folderInfo = Get-Item $appPath
$DeviceId = '\\?\' + $folderInfo.LinkTarget.Split('\')[0] +'\'
Write-Output $DeviceId #Save this for later
Remove-AppxPackage -AllUsers -Package $msixPackageFullName
Remove-AppxPackage -Package $msixPackageFullName
Dismount the disks from the system
To finish the destaging process, you'll need to dismount the disks from the system. The command you'll need to use depends on the format of your disk image.
If your image is in CimFS format, run this cmdlet:
DisMount-CimDiskimage -DeviceId $DeviceId
Once you finish dismounting your disks, you've safely removed your MSIX package.
Set up simulation scripts for the MSIX app attach agent
If you want to add and remove MSIX packages automatically, you can use the PowerShell commands in this article to create scripts that run at startup, logon, logoff, and shutdown. To learn more about these types of scripts, see Using startup, shutdown, logon, and logoff scripts in Group Policy.
Each of these automatic scripts runs one phase of the app attach scripts:
- The startup script runs the stage script.
- The logon script runs the register script.
- The logoff script runs the deregister script.
- The shutdown script runs the destage script.
Note
You can run the task scheduler with the stage script. To run the script, set the task trigger to When the computer starts, then enable Run with highest privileges.
Use packages offline
If you're using packages from the Microsoft Store for Business or the Microsoft Store for Education within your network or on devices that aren't connected to the internet, you need to get the package licenses from the Microsoft Store and install them on your device to successfully run the app. If your device is online and can connect to the Microsoft Store for Business, the required licenses should download automatically, but if you're offline, you'll need to set up the licenses manually.
To install the license files, you'll need to use a PowerShell script that calls the MDM_EnterpriseModernAppManagement_StoreLicenses02_01 class in the WMI Bridge Provider.
Here's how to set up the licenses for offline use:
Download the app package, licenses, and required frameworks from the Microsoft Store for Business. You need both the encoded and unencoded license files. Detailed download instructions can be found here.
Update the following variables in the script for step 3:
$contentID
is the ContentID value from the Unencoded license file (.xml). You can open the license file in a text editor of your choice.$licenseBlob
is the entire string for the license blob in the Encoded license file (.bin). You can open the encoded license file in a text editor of your choice.
Run the following script from PowerShell running as an administrator. A good place to perform license installation is at the end of the staging phase because at that point you also need to run PowerShell as an administrator.
$namespaceName = "root\cimv2\mdm\dmmap" $className = "MDM_EnterpriseModernAppManagement_StoreLicenses02_01" $methodName = "AddLicenseMethod" $parentID = "./Vendor/MSFT/EnterpriseModernAppManagement/AppLicenses/StoreLicenses" #TODO - Update $contentID with the ContentID value from the unencoded license file (.xml) $contentID = "{'ContentID'_in_unencoded_license_file}" #TODO - Update $licenseBlob with the entire String in the encoded license file (.bin) $licenseBlob = "{Entire_String_in_encoded_license_file}" $session = New-CimSession #The final string passed into the AddLicenseMethod should be of the form <License Content="encoded license blob" /> $licenseString = '<License Content='+ '"' + $licenseBlob +'"' + ' />' $params = New-Object Microsoft.Management.Infrastructure.CimMethodParametersCollection $param = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("param",$licenseString ,"String", "In") $params.Add($param) try { $instance = New-CimInstance -Namespace $namespaceName -ClassName $className -Property @{ParentID=$parentID;InstanceID=$contentID} $session.InvokeMethod($namespaceName, $instance, $methodName, $params) } catch [Exception] { write-host $_ | out-string }
Demonstration scripts
You can find demonstration scripts for all four stages of the MSIX App Attach package process and syntax help for how to use them at our template. These scripts will work with any version of PowerShell and any disk image format.
Next steps
If you have any questions, you can ask them at the Azure Virtual Desktop TechCommunity.
You can also leave feedback for Azure Virtual Desktop at the Azure Virtual Desktop feedback hub.
Feedback
Submit and view feedback for