Pre-install XFA with Microsoft Configuration Manager (SCCM)
Create an Application with a Windows Installer deployment type and pass your enrollment token as an MSI property. Pre-installation is an Enterprise feature.
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.
1. Download the installer
Download XFA.msi and place it on your content share.
One installer covers x64 and ARM64. On ARM64 it runs under emulation and XFA replaces itself with the native build automatically.
2. Create the application
In the Configuration Manager console, go to Software Library > Application Management > Applications and create an application from XFA.msi. Configuration Manager reads the product code and creates the detection method for you.
The rule you set reports whether XFA is installed, not whether this user is enrolled in your organization. To confirm affiliation, for instance when a device may already run XFA for another organization, use a compliance rule that runs in the user's context. A detection method runs as the system account, which cannot reach the user's per-user install, so %LOCALAPPDATA% below only resolves to the right profile when the rule runs as the user.
xfa is not on the PATH and is a windowed application, so a rule that runs it directly neither waits for it nor receives its exit code. Start the backend, wait for it, and report affiliated only on exit code 0 (10 means not affiliated):
$run = Start-Process "$env:LOCALAPPDATA\XFA\xfa-backend.exe" -Wait -PassThru `
-ArgumentList 'enrollment-status', '--organization-id', '<id>'
if ($run.ExitCode -eq 0) { Write-Output 'Affiliated' }
This is the one setting to get right. The rule Configuration Manager generates checks the MSI product code. XFA regenerates its product code every time it updates itself, so after the first self-update the recorded code no longer matches. Configuration Manager then reports XFA as not installed and reinstalls the packaged build over the newer one, on every evaluation cycle. Left in place, it pushes an older build across your fleet.
Replace it with a PowerShell detection script. Configuration Manager treats XFA as installed when the script writes to standard output, and as missing when it writes nothing.
The script has to account for two things. Configuration Manager runs detection as the system account, even when the app installs for the user, so a rule under HKEY_CURRENT_USER or %LOCALAPPDATA% reads the system account's own profile and never sees the user's install. And XFA is 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.
So detect the signed-in user's install. 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 lands 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 check both views and accept the marker in either:
$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 { }
}
This detects on presence, not version, so XFA's self-updates never trip it. It is scoped to the signed-in user, so on a shared machine Configuration Manager installs XFA for each person as they sign in, rather than skipping everyone after the first. The marker is written at install and stays put across self-updates, so the rule holds for any version. The try/catch matters: if the account cannot be resolved to a SID (for example a domain controller is briefly unreachable), the script writes nothing and Configuration Manager reads "not installed", which is the safe direction, never a false match.
This assumes one interactive user at a time, which is the normal case for a laptop or desktop. On a multi-session host (Remote Desktop Session Host) Win32_ComputerSystem.UserName only reports the physical console user, so detection there covers that one user rather than every session.
3. Set the installation program
Take the command from Integrations > Pre-install XFA through MDM > Microsoft Configuration Manager (SCCM) in the XFA dashboard, which already contains your token:
msiexec /i "XFA.msi" /qn ENROLLMENT_TOKEN=<token-from-xfa-dashboard>
EMAIL sets one address. An Application has one installation program for the whole collection, so putting an address there enrols every device in the collection as that person.
On a domain-joined fleet XFA reads the address from Active Directory's mail attribute, per device, which is what you want. It falls back to the user principal name when mail is empty or no domain controller is reachable.
If devices fail with an address error, the fix is in Active Directory (populate mail for those accounts), not in the install command. EMAIL is for installing on a single device, where you know whose it is.
mail is not the identity you enroll underBy default XFA reads Active Directory's mail attribute first, then the user principal name. On an Entra or Microsoft 365 fleet the UPN is usually the person's real address, and mail may be stale or unset. Add PREFER_UPN=1 to the install command to try the user principal name first:
msiexec /i "XFA.msi" /qn ENROLLMENT_TOKEN=<token> PREFER_UPN=1
Leave it off on a classic domain-joined fleet: a directory suffix like corp.local is a valid-looking UPN that is not an address, which is why mail comes first by default.
Uninstall program:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File uninstall-xfa.ps1
The product code changes with every self-update, so the uninstall cannot name the original XFA.msi: after an update its product code no longer matches what is installed. Ship uninstall-xfa.ps1 alongside XFA.msi in the content source. It resolves the current product through Windows Installer by XFA's stable UpgradeCode, which is the same for every version and independent of 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
}
The uninstall program removes the package 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 xfa unenroll --organization-id <id> as the signed-in user first, to remove only this organization and keep XFA installed. 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 uninstall.
4. Set the deployment type options
XFA is a per-user application, so it must install while the user is signed in.
| Setting | Value |
|---|---|
| Installation behavior | Install for user |
| Logon requirement | Only when a user is logged on |
| Installation program visibility | Hidden |
| Deployment target | User collection |
The Configuration Manager client runs as SYSTEM. Left at Install for system, a per-user application installs into the SYSTEM profile: Configuration Manager reports success, and the signed-in user has no XFA and never appears on your Devices page.
XFA refuses to enrol when it is running as the system account, so this reports a failure naming the setting rather than completing into a profile nobody uses. The setting is still what prevents it.
5. Deploy and verify
Deploy the application to a user collection, so it installs for the signed-in user.
XFA registers the device with the service during the install, so it appears on your Devices page straight away. It then stores the device's credentials in the signed-in user's profile.
A managed install can run in a session that cannot yet write to that profile. Configuration Manager installs under a service-side logon that has no credential store of its own. When that happens XFA records the enrollment and finishes storing the credentials the next time the user signs in, from a session that can, then reports posture from there. So a freshly deployed device can appear before it is fully reporting, and settles once the user has signed in. It is automatic, with nothing to configure.
Then 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.
A rejected token fails the install, so Configuration Manager shows the deployment as failed instead of passing a device that installed without joining your organization. It reports a generic installer failure rather than XFA's own reason code, so read the reason from %TEMP%\xfa\xfa-enrollment.log on the device: a rejected token reads there as code 2, an unreachable service as code 3 (worth retrying). XFA stays installed either way.