Pre-install XFA with Microsoft Intune
Add XFA.msi as a Win32 app with a presence based detection rule, and pass your enrollment token as an install argument.
XFA is made to be self-installed by the team you want to secure. They install it themselves after an invitation through Awareness, or at a login protected by Enforcement. For most organizations that is the whole rollout.
Uploading XFA.msi as a line-of-business app is the obvious route, and it is the one that breaks. A line-of-business app detects XFA by its MSI product code, and XFA regenerates that product code every time it updates itself. After the first self-update Intune no longer finds the code it recorded, reports XFA as not installed, and reinstalls the older packaged build over the newer one, on every check. Ignore app version does not save it: that ignores the version, not the product code. Package XFA as a Win32 app instead, where you set the detection rule yourself.
Copy it from Integrations > Pre-install XFA through MDM > Microsoft Intune in the XFA dashboard, which shows the finished command with the token already in it. Pre-installation is an Enterprise feature.
1. Download and package the installer
Put two files in an empty folder:
- XFA.msi.
uninstall-xfa.ps1, the removal script below. XFA's product code changes with every self-update, so the uninstall cannot name the original MSI; after an update its product code no longer matches what is installed. It resolves the current product through Windows Installer by XFA's stable UpgradeCode, which never changes and does not depend on the 32-bit or 64-bit registry view, then removes it:
$ErrorActionPreference = 'Stop'
$upgradeCode = '{FE5FD0A4-8D39-42FD-B0DE-18EB89B7CAC4}'
try {
$installer = New-Object -ComObject WindowsInstaller.Installer
$products = @($installer.RelatedProducts($upgradeCode))
if ($products.Count -ne 1) { exit 1 }
$p = Start-Process 'msiexec.exe' -ArgumentList "/x $($products[0]) /qn" -Wait -PassThru
if (-not $p) { exit 1 }
exit $p.ExitCode
} catch {
exit 1
}
Intune ships Win32 apps as .intunewin files. Wrap the whole folder, which is what puts uninstall-xfa.ps1 inside the app, with Microsoft's Win32 Content Prep Tool:
IntuneWinAppUtil.exe -c <folder with XFA.msi and uninstall-xfa.ps1> -s XFA.msi -o <output folder>
One installer covers x64 and ARM64. On an ARM64 device it runs under emulation and XFA replaces itself with the native ARM64 build automatically.
2. Add the app
In the Intune admin center, go to Apps > All apps > Create. Select the Windows platform, choose Windows app (Win32), and upload the XFA.intunewin you just built.
3. Set the program
| Field | Value |
|---|---|
| Install command | msiexec /i "XFA.msi" /qn ENROLLMENT_TOKEN=<token-from-xfa-dashboard> |
| Uninstall command | powershell.exe -NoProfile -ExecutionPolicy Bypass -File uninstall-xfa.ps1 |
| Install behavior | User |
This is the setting to get right, and Win32 apps default to System. Left there, Intune runs msiexec as the system account, XFA installs into the system profile, and it fails to enroll because there is no signed-in person to install for. Set Install behavior to User so the install runs in the logged-on user's context. It is the same trap as Install for user on Configuration Manager.
The uninstall command runs uninstall-xfa.ps1, the script you packaged in step 1. Because the install behavior is User, it runs as the signed-in user and removes XFA by whatever product code is currently installed.
4. Set the detection rule
This is the step that keeps XFA from reinstalling itself in a loop. Choose Use a custom detection script and upload the script below.
Intune runs detection scripts as the system account, even though the app installs for the user, so a rule that reads HKEY_CURRENT_USER or %LOCALAPPDATA% looks in the system account's own profile and reports XFA missing. XFA is also a per-user install, so "installed" is a question about the signed-in user, not the machine: on a shared PC each user needs their own copy. XFA writes a per-user marker at HKCU\Software\XFA\DesktopApp, which the system account can read in that user's hive under HKEY_USERS. One more wrinkle: XFA.msi is a 32-bit installer, so on a 64-bit machine the marker is in the 32-bit registry view, and after the ARM64 self-update it moves to the 64-bit view. Detection scripts run 64-bit by default, so resolve who is signed in and check both views:
$console = (Get-CimInstance Win32_ComputerSystem).UserName
if ($console) {
try {
$sid = ([System.Security.Principal.NTAccount]$console).Translate(
[System.Security.Principal.SecurityIdentifier]).Value
$installed = $false
foreach ($view in @([Microsoft.Win32.RegistryView]::Registry32,
[Microsoft.Win32.RegistryView]::Registry64)) {
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
[Microsoft.Win32.RegistryHive]::Users, $view)
try {
$key = $base.OpenSubKey("$sid\Software\XFA\DesktopApp")
if ($key) { $installed = $true; $key.Close() }
} finally { $base.Close() }
}
if ($installed) { Write-Output 'Installed' }
} catch { }
}
Intune treats the app as installed when the script writes to standard output, so nothing is written when XFA is absent. This detects on presence, not version, so XFA's self-updates never trip it, and because it is scoped to the signed-in user, Intune installs XFA for each person on a shared machine rather than skipping everyone after the first. The try/catch fails closed: if the account cannot be resolved to a SID, the script writes nothing and Intune reads "not installed", never a false match.
This assumes one interactive user at a time, the normal case for a laptop or desktop. On a multi-session host (Remote Desktop Session Host) Win32_ComputerSystem.UserName reports only the physical console user.
5. Assign the app
Under Assignments, add the user group that should receive XFA under Required.
Assign to a user group, not a device group. XFA installs for whoever is signed in and enrolls that person, so a device assignment installs it for nobody.
6. Verify
After Intune reports the app as installed, confirm that:
- the XFA icon appears in the signed-in user's system tray; and
- the device appears for that user on the XFA Devices page.
If enrollment fails, the install fails with it, so Intune shows the app as failed rather than reporting success on a device that never joined your organization. Intune reports a generic installer failure (it does not surface XFA's own reason code), so read the reason from %TEMP%\xfa\xfa-enrollment.log on the device:
| Code | Meaning |
|---|---|
| 2 | The enrollment token was rejected. Reissue it in the XFA dashboard. |
| 3 | XFA could not be reached. Intune retries. |
| 1, 4, 5 | The command was wrong, or the device could not store its credentials. |
Remove XFA
Unenroll before you uninstall. An Intune Uninstall assignment runs the uninstall command but does not run xfa unenroll, so it leaves the organization affiliation behind on the server, and on a device that belongs to more than one organization it strips XFA from the others too.
Run this as the signed-in user first, to remove only this organization and keep XFA installed:
xfa unenroll --organization-id <organization-id>
Then remove the application only once no affiliation remains. xfa enrollment-status returns exit code 0 while the device is still affiliated with any organization, and 10 when it is safe to remove. When it reports 10, set the Uninstall assignment.