You assign a Windows Hello for Business policy in Intune. The console turns green and says Succeeded. Then someone asks you to prove it is actually on the laptop, so you remote in, open the registry where every other Intune CSP policy writes its values, and find nothing at all. No key. Not an empty key. The key does not exist.
At that point most people conclude the policy failed to apply and start troubleshooting delivery. That is the wrong conclusion, and chasing it wastes hours. The policy is almost certainly applied perfectly. It is simply not written where you are looking, because Windows Hello for Business is one of the few CSPs that does not use the standard PolicyManager location. This post shows where it really goes, how to read it three different ways, and how to diff the endpoint against what Intune actually holds so you can answer "is it applied?" with evidence instead of a screenshot of a green tick.
Windows Hello for Business does not write to PolicyManager\current\device like most CSP policies. It writes to a tenant-scoped path, HKLM\SOFTWARE\Microsoft\Policies\PassportForWork\{TenantId}\Device\Policies, because the CSP node itself contains the tenant GUID. Biometrics settings sit outside that tenant key, and AllowPINLogon is a different CSP again that stores the literal string <disabled/> under a third path and surfaces as AllowDomainPINLogon. Read the device with one PowerShell line, the Intune side across three separate Graph collections, and diff the two. As of the Intune 2607 release you can also collect those registry values natively into Device Inventory with no script at all.
The problem: the key you expect does not exist
Nearly every Intune Settings Catalog policy lands in one of two predictable places on the device. The delivered payload goes under PolicyManager\Providers\{EnrollmentId}, and the value Windows actually enforces appears under PolicyManager\current\device. Learn that pattern once and you can verify almost anything.
So you apply the same pattern to Windows Hello for Business, and this happens.
# The location that works for almost every other CSP policy Test-Path 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\PassportForWork' False # Not a permissions problem - the neighbouring keys are all there Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device' | Select-Object -ExpandProperty PSChildName CredentialProviders DeviceLock Update ... # PassportForWork is simply absent from this list
The user is signed in with Hello. Biometrics work. The PIN honours your minimum length. Everything behaves exactly as configured, and yet the key an experienced admin would check first is not there.
PolicyManager\current\device\PassportForWork key is normal for Windows Hello for Business. It is not evidence of a delivery failure, and a detection script that treats it as one will report every healthy device in your fleet as broken.Why it happens: the CSP node carries the tenant GUID
The registry layout is not arbitrary. It mirrors the CSP node tree almost exactly, and the PassportForWork CSP is unusual in that its policy nodes are scoped per tenant. Microsoft documents the node as:
That {TenantId} segment is the whole explanation. Because the tenant GUID is part of the node path, it becomes part of the registry path too. The values land here instead:
The same documentation shows that the Biometrics nodes are not tenant-scoped — they sit at PassportForWork/Biometrics/... with no GUID. That asymmetry carries straight through to the registry, which is why a real device shows two sibling trees rather than one.
The setting that has three different names
There is a second trap hiding in the same Intune policy. Most WHfB profiles also set AllowPINLogon, which is not a PassportForWork setting at all — it belongs to the CredentialProviders area of the Policy CSP. It behaves differently in three ways at once.
First, it controls the legacy convenience PIN for domain users, not the Hello PIN. Microsoft's wording is explicit: it "allows you to control whether a domain user can sign in using a convenience PIN". Turning it off does not disable Windows Hello.
Second, it is an ADMX-backed policy. Those do not store a DWORD. They store the literal string <enabled/> or <disabled/>, which is why a value that reads <disabled/> is correct rather than corrupt.
Third, its ADMX mapping renames it. The CSP calls it AllowPINLogon; the registry value it ultimately drives is AllowDomainPINLogon under Software\Policies\Microsoft\Windows\System. One setting, three names, three locations.
AllowPINLogon and only look under PassportForWork, you will never find it and may wrongly report the setting as undelivered. Search PolicyManager\Providers for the raw <disabled/> value, and Software\Policies\Microsoft\Windows\System for the resulting AllowDomainPINLogon.How to verify: three ways to read what landed
1. The one-line check
This walks the whole PassportForWork tree, including the sub-keys, and prints every value with its data. It is the fastest way to answer "did anything arrive?" on a single machine.
Get-ChildItem -Recurse 'HKLM:\SOFTWARE\Microsoft\Policies\PassportForWork' | ForEach-Object { $k = $_ $k.GetValueNames() | ForEach-Object { "{0,-42} = {1}" -f $_, $k.GetValue($_) } } DisablePostLogonProvisioning = 0 UseCloudTrustForOnPremAuth = 1 EnablePinRecovery = 1 RequireSecurityDevice = 1 UsePassportForWork = 1 TPM12 = 1 MinimumPINLength = 8 UseBiometrics = 1 FacialFeaturesUseEnhancedAntiSpoofing = 1
If that returns values, Intune delivered the policy, whatever the console says. If the key does not exist at all, it genuinely did not arrive.
The registry values worth knowing
Every value below lives under this parent key, where the GUID is your own tenant ID:
| Value name | Healthy value | If wrong or missing |
|---|---|---|
UsePassportForWork | 1 | 0 disables Hello entirely; absent means no policy arrived |
RequireSecurityDevice | 1 | 0 permits software-backed keys with no TPM protection |
UseCloudTrustForOnPremAuth | 1 | Absent means Cloud Kerberos Trust is off; on-prem SSO will use a different trust model |
EnablePinRecovery | 1 | 0 means a forgotten PIN needs a destructive reset |
DisablePostLogonProvisioning | 0 | 1 suppresses the enrolment prompt after sign-in, so users never get asked |
PINComplexity\MinimumPINLength | your standard, e.g. 8 | Mismatch here is the classic sign of a competing GPO |
ExcludeSecurityDevices\TPM12 | 1 | 0 allows TPM 1.2 devices to provision |
Biometrics\UseBiometrics (no tenant GUID) | 1 | 0 forces PIN only, no face or fingerprint |
Biometrics\FacialFeaturesUseEnhancedAntiSpoofing | 1 | 0 weakens face-spoofing protection |
2. The device report script
Get-WHfBPolicyOnDevice.ps1 runs all of the above plus the enrolment state, the CredentialProviders values, any competing Group Policy keys, the MDM event history and dsregcmd output, then writes a single report file. It is read-only and needs no credentials.
3. Collect it fleet-wide with no script (Intune 2607 and later)
This is the newest option and the one most people have not noticed yet. Intune's properties catalog can now collect selected registry values into Device Inventory, so you can see WHfB policy state across the estate without deploying a script at all.
- Sign in to the Microsoft Intune admin center.
- Go to Devices › Windows.
- Under Manage devices, select Configuration › Create › New Policy.
- Set Platform to Windows 10 and later and Profile type to Properties catalog, then select Create.
- Give the profile a name, for example "Inventory — WHfB policy state", and select Next.
- Select Add properties, choose the Registry category, and add your
PassportForWorkkey path. Add thePINComplexityandExcludeSecurityDevicessub-keys as separate entries. - Select Next, set any scope tags, then assign the profile to your Windows device group.
- Complete Review + create.
- To read the results, go to Devices › Windows, pick a device, and under Tools select Device Inventory.
Three limits matter for this use case. Collection is HKLM only, capped at 100 registry keys per device and 6 KB per value, and it is non-recursive — it reads values directly under a key, which is exactly why the two sub-keys need their own entries. Initial collection can take up to 24 hours because the full sync runs once a day.
The MDM events that tell you whether sync is healthy
Policy that never arrives usually leaves a trail. All of the events below are in one channel:
| Event ID | Level | Meaning |
|---|---|---|
208 | Information | MDM session started — every scheduled or triggered sync begins with one |
404 | Error | A CSP command failed. The message names the exact CSP URI, which is how you tell a WHfB failure from an unrelated one |
813 | Warning | A setting was queued for retry rather than applied on this pass |
819 | Information | Node value reported back to the service |
865 | Error | ADMX ingestion failed, commonly with 0x80070005 access denied |
873 | Information | ADMX ingestion started for an app or policy area |
881 | Information | Routine PolicyManager activity; high counts here are normal, not a fault |
2219 | Information | Configuration source processing, logged heavily during a full sync |
2900 | Information | A CSP node was processed during the sync |
Read that carefully, because it is the single most common misreading of this log. Two errors are present, but neither is a WHfB failure — one is a broken display-scaling CSP and the other is an unrelated ADMX ingestion problem. A device can log dozens of Event 404s and still have perfect Hello policy. Always read the CSP URI in the message before attributing a failure.
The fix: diff the device against Intune
Reading the endpoint only tells you half the story. To prove the endpoint matches intent you need the tenant side too, and that is harder than it sounds because WHfB policy does not live in one place in Intune either.
One policy blade, three Graph collections
Depending on how each policy was created, it sits in a different Graph collection:
| Graph collection | What it holds | Why it gets missed |
|---|---|---|
deviceManagement/configurationPolicies | Settings Catalog policies | Usually the only one people query |
deviceManagement/deviceConfigurations | Legacy templates and custom OMA-URI profiles | A custom OMA-URI setting is invisible to a Settings Catalog query |
deviceManagement/intents | Endpoint security, including Account Protection | Separate API surface again |
In a real tenant this bites. A profile setting DisablePostLogonProvisioning through a custom OMA-URI lives in deviceConfigurations, so a script that reads only configurationPolicies reports the setting as "not configured" while the device happily enforces it.
Settings Catalog values are nested several levels deep
The second obstacle is the shape of the data. Settings Catalog does not return flat name and value pairs. Real values hide inside groupSettingCollectionValue → children, and a naive dump returns parent definition IDs that tell you nothing.
function Expand-SettingInstance { param($Instance, [int]$Depth = 0) $defId = $Instance.settingDefinitionId switch -Wildcard ($Instance.'@odata.type') { '*ChoiceSettingInstance' { # the chosen option is the definition id plus a suffix $val = $Instance.choiceSettingValue.value -replace [regex]::Escape($defId + '_'), '' "{0} = {1}" -f $defId, $val foreach ($c in $Instance.choiceSettingValue.children) { Expand-SettingInstance $c ($Depth+1) } } '*GroupSettingCollectionInstance' { foreach ($g in $Instance.groupSettingCollectionValue) { foreach ($c in $g.children) { Expand-SettingInstance $c ($Depth+1) } } } '*SimpleSettingInstance' { "{0} = {1}" -f $defId, $Instance.simpleSettingValue.value } } }
Run that against a WHfB policy and the nesting resolves into something you can actually compare against a registry value:
[group] passportforwork_{tenantid}
passportforwork_{tenantid}_policies_pincomplexity_minimumpinlength = 8
passportforwork_{tenantid}_policies_enablepinrecovery = true
passportforwork_{tenantid}_policies_requiresecuritydevice = true
passportforwork_{tenantid}_policies_usepassportforwork = true
passportforwork_biometrics_facialfeaturesuseenhancedantispoofing = trueWhich source actually won: MDM or Group Policy
Group Policy and Intune can both set the same WHfB values, and by default Group Policy wins. The device records the outcome, which means you never have to guess. Under PolicyManager\current\device\<Area> you get three companion values for an ADMX-backed setting:
| Value suffix | What it tells you | How to read it |
|---|---|---|
_ProviderSet | How many sources configured this setting | Greater than 1 means more than one source is competing |
_WinningProvider | Which source won | An enrolment GUID means MDM won; compare it to your Intune enrolment ID |
_ADMXInstanceData | Where the raw delivered payload sits | Follow it to the provider key to see the literal value |
To find the competing GPO, check the Group Policy branch directly. It is a different hive path entirely from the MDM one:
If you need to change or remove that GPO, the console path is:
- Open Group Policy Management (
gpmc.msc) on a management workstation. - Find the GPO linked to the OU containing the affected devices, right-click it and select Edit.
- Navigate to Computer Configuration › Policies › Administrative Templates › Windows Components › Windows Hello for Business.
- Set the conflicting setting to Not Configured so the MDM value applies.
- Run
gpupdate /forceon a test device and re-read the registry to confirm the MDM value is now in place.
Deploy the check as an Intune Remediation
To answer the question across the fleet rather than one laptop, deploy the detection script. Microsoft's documentation is explicit that "a script package can contain a detection script only or both a detection script and a remediation script", which matters here because this check should be detection-only.
- Sign in to the Microsoft Intune admin center.
- Go to Devices › Manage devices › Scripts and remediations.
- Select Create script package and name it, for example "WHfB policy delivery — detect".
- On the Settings page, upload
Detect-WHfBPolicyDelivery.ps1as the Detection script file. - Leave the Remediation script file empty.
- Set Run this script using the logged-on credentials to No, and Run script in 64-bit PowerShell to Yes.
- Select Next, apply any scope tags, then assign to your Windows device group.
- Set the schedule to Daily and complete Review + create.
There is deliberately no remediation script. WHfB policy is owned by the MDM channel: anything a script writes gets overwritten at the next sync, and the real fix — assignment, filter, licence or GPO — lives in the service, not on the endpoint. Detection finds the affected devices; you fix the cause centrally.
Two details make the difference between a detection people trust and one they mute. Count only the Event 404s whose CSP URI actually mentions PassportForWork or CredentialProviders, or unrelated ADMX failures will flag healthy machines. And raise an issue only when a GPO value differs from the MDM value, not merely because a GPO exists.
Proof it worked
Here is the comparison script against a real hybrid-joined laptop. Ten settings match across both sides, and the eleventh is a true negative worth understanding.
=== Reading WHfB policy from this device === Found 10 WHfB-related value(s) on this device. === Reading WHfB policy from Intune === Found 11 WHfB setting(s) configured in Intune. === Comparison === SETTING STATUS INTUNE DEVICE AllowPINLogon MATCH 0 0 DisablePostLogonProvisioning MATCH False 0 EnablePinRecovery MATCH true 1 FacialFeaturesUseEnhancedAntiSpoofing MATCH true 1 MinimumPINLength MATCH 8 8 RequireSecurityDevice MATCH true 1 TPM12 MATCH true 1 UseBiometrics MATCH true 1 UseCloudTrustForOnPremAuth MATCH true 1 UsePassportForWork MATCH true 1 enablewebsignin MISSING 1 (absent) MATCH 10 MISMATCH 0 MISSING 1 EXTRA 0
Note how the normalisation works. Intune reports booleans as true and false; the registry stores 1 and 0. A naive string comparison would report ten mismatches on a perfectly healthy device, so both sides are normalised before comparing.
The MISSING row is not a fault either. enablewebsignin belongs to a separate Web Sign-in policy that this device is not assigned, so it is correctly absent. That is why the output distinguishes MISSING (Intune has it, the device does not) from MISMATCH (both have it, they disagree) — only the second is unambiguously a problem.
The detection script reduces the same picture to one line per device, small enough for Intune's 2,048-character output limit:
OK Computer=LAPTOP-01;PolicyDelivered=Yes;UsePassportForWork=1;MinimumPINLength=8;
CloudTrust=1;RequireSecurityDevice=1;GPOPresent=Yes;NgcSet=YES;
WHfBCsp404Last3d=0;AllCsp404Last3d=24That single line is the whole argument of this post. GPOPresent=Yes but no conflict, so no issue is raised. AllCsp404Last3d=24 looks alarming until you see WHfBCsp404Last3d=0 next to it — two dozen CSP failures on this machine, none of them WHfB. And NgcSet=YES confirms the policy did not merely arrive, it produced a working Hello credential.
That last distinction matters more than anything else here. Policy delivery and Hello provisioning are separate outcomes. A device can have every value correct and still show NgcSet : NO because the user has not enrolled yet. Check both, and the green tick in the console stops being the thing you have to trust.
Download these from Imran76Awan/Daily-Tasks — public, no sign-in required. All four are read-only: they read the registry, the event log and Microsoft Graph, and change nothing. The Graph scripts take tenant, client and certificate thumbprint as mandatory parameters and also support device code sign-in. Validate them in your own environment before fleet use.
References
- PassportForWork CSP — documents the
{TenantId}node path and every policy node used above - CredentialProviders Policy CSP —
AllowPINLogon, its convenience-PIN scope, and the ADMX mapping toAllowDomainPINLogon - Collect device properties with the Intune properties catalog — registry inventory, and its HKLM-only, 100-key and 6 KB limits
- Use Remediations to detect and fix support issues — detection-only packages, exit code semantics and the 2,048-character output limit
- Understanding ADMX-backed policies — why some values store
<enabled/>and<disabled/>instead of a DWORD
Related reading on this site
- The complete Windows Hello for Business Event ID catalog — covers the User Device Registration, HelloForBusiness and AAD logs, which sit alongside the MDM channel used here
- dsregcmd /status decoded — the full field reference behind the
NgcSetand PRT checks - WHfB PIN complexity: every setting, CSP and GPO — goes deeper on the PIN settings referenced in the registry table
Microsoft MVP community deep-dives
| Author | Post | What it adds |
|---|---|---|
| Rudy Ooms (MVP) | Intune and troubleshooting Settings Catalog profiles | Works through PolicyManager\Providers to establish which provider won a contested setting, and shows a real case where the Settings Catalog wrote a malformed value |
| Joymalya Basu Roy (MVP) | Track Intune MDM policy information on the endpoint | Independently documents the same two PolicyManager paths and argues the DeviceManagement-Enterprise-Diagnostics-Provider channel should be the first place you look for delivery failures |