WLapsAdmin or Administrator? Auditing Windows LAPS at fleet scale.

Windows LAPS (Local Administrator Password Solution) is the feature that auto-rotates the local admin password on each PC and backs it up to Entra ID. An IT lead asked me last week why some of his PCs showed Administrator in the LAPS recovery portal while the rest showed WLapsAdmin. Same policy, same tenant, identical assignments. Good question. The answer comes down to one Microsoft design decision in Windows 11 24H2 that quietly creates a split fleet, and the only practical way to see the full picture is via Microsoft Graph. Here is the audit I run, end to end.

why this matters

The Intune recovery portal shows you the LAPS account name for one device at a time. There is no fleet-wide view. The Graph v1.0 list endpoint will give you device names and timestamps, but it will not give you the account name in bulk. Microsoft deliberately blocks bulk account name retrieval with an HTTP 400 error, treating password account names as sensitive metadata that should only be queried per device. On any tenant of meaningful size, the only way to see the full picture is to write the audit yourself.

On top of that, an Intune-enrolled, compliant device is not necessarily escrowing LAPS. The portal happily shows the policy as applied while the device silently fails to back up its password to Entra. The shape of a real LAPS audit on a hybrid tenant is always the same three questions:

  • How many devices are actually escrowing, versus how many should be?
  • For the ones that are escrowing, what account name is being managed?
  • For the ones that are not, why?

before you touch anything

  • Microsoft.Graph PowerShell SDK installed. Latest version, not the v1 module that Microsoft has retired. Install with Install-Module Microsoft.Graph -Scope CurrentUser.
  • An admin account with the right Graph scopes. You need read access to local credentials, devices, and managed devices: DeviceLocalCredential.ReadBasic.All, DeviceLocalCredential.Read.All, Device.Read.All, and DeviceManagementManagedDevices.Read.All. Connect once at the start of the session with all of them, so you do not get re-prompted halfway through a 90-second loop.
  • WAM disabled if you are on PowerShell 5.1. WAM (Web Account Manager) is the Windows broker that handles login. On PowerShell 5.1 it sometimes triggers a double prompt when Connect-MgGraph runs. Fix: run Set-MgGraphOption -DisableLoginByWAM $true before connecting.
// run this
Set-MgGraphOption -DisableLoginByWAM $true
Connect-MgGraph -TenantId "00000000-0000-0000-0000-000000000000" `
    -Scopes "DeviceLocalCredential.ReadBasic.All", `
            "DeviceLocalCredential.Read.All", `
            "Device.Read.All", `
            "DeviceManagementManagedDevices.Read.All" `
    -NoWelcome

step 1 · pull the escrow records

The list endpoint returns one record per escrowed device with the device name and last backup timestamp. It does not return the account name. That comes later.

// run this
$response = Invoke-MgGraphRequest -Method GET `
    -Uri "https://graph.microsoft.com/v1.0/directory/deviceLocalCredentials"
$response.value.Count
// output
  81

Hold onto that count. The next step compares it against the live Intune inventory and almost never matches.

step 2 · pull the Intune inventory

The escrow list is one number. The Intune inventory of currently active Windows devices is another. Stale escrow records from retired devices stick around in the v1.0 list. Live devices that should be escrowing but are not get hidden by the noise. You need both.

// run this
$intune = Invoke-MgGraphRequest -Method GET `
    -Uri "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices"
$winDevices = $intune.value | Where-Object { $_['operatingSystem'] -eq 'Windows' }
$winDevices.Count
// output
  84

step 3 · gap analysis

Cross-reference both lists by device name. Two outputs that you should care about: devices in Intune but not escrowing, and escrow records with no matching Intune device.

// run this
$intuneNames   = $winDevices | ForEach-Object { $_['deviceName'] }
$escrowedNames = $response.value | ForEach-Object { $_['deviceName'] }

$intuneNames | Where-Object { $_ -notin $escrowedNames }
$escrowedNames | Where-Object { $_ -notin $intuneNames }
// output (in Intune but not escrowing, then escrowing but not in Intune)
  CORP-PC-014
  CORP-PC-039
  CORP-PC-072

  (no results)

Three devices in Intune, not escrowing. No stale records on the escrow side this time. Those three are the actual investigation targets, not the headline number.

step 4 · OS eligibility, the boundary nobody talks about

Now the bit that explains the WLapsAdmin versus Administrator split. Microsoft's Automatic Account Management feature, the one that creates and manages WLapsAdmin instead of touching the built-in Administrator, only applies on Windows 11 24H2 (build 10.0.26100) or later. On Windows 11 23H2 and below, the AAM CSP settings are silently ignored and LAPS manages the built-in Administrator under its default name. Both are correct. The password works. The portal just shows a different account name.

// run this
$threshold = [version]"10.0.26100"
$winDevices | ForEach-Object {
    [pscustomobject]@{
        DeviceName = $_['deviceName']
        OsVersion  = $_['osVersion']
        Eligible   = ([version]$_['osVersion'] -ge $threshold)
    }
} | Group-Object Eligible | Format-Table Name, Count
// output
  Name   Count
  ----   -----
  True    72
  False   12

Twelve devices on 23H2 or below. Those will show Administrator in the portal as long as they are on that build. They will flip to WLapsAdmin the next time they upgrade to 24H2. No policy change required.

Reference table. Build 10.0.22621.x = 22H2. Build 10.0.22631.x = 23H2. Build 10.0.26100.x and above = 24H2. Build 10.0.21996.x is a Windows 10 Insider Preview from October 2021, not a production release. If a device is on that build, do not patch it. Rebuild or replace.

step 5 · per-device account name confirmation

Now the part Microsoft will not let you shortcut. The v1.0 list endpoint does not include accountName in its response. The natural thing to try is adding $select=credentials to the list call to ask for it. That returns:

// the response
  HTTP/1.1 400 Bad Request
  {"error":{"code":"invalid_request",
    "message":"Querying local account passwords over multiple devices is unsupported."}}

Why this fails: Microsoft treats local credential account names as sensitive metadata. The list endpoint will not return them in bulk by design.

Fix: a per-device foreach loop against the beta endpoint. Around 60 to 90 seconds for 80 devices, with one Graph call per device. The beta endpoint allows the credentials select on a single device because it returns one record at a time.

// run this
$accountReport = foreach ($d in $response.value) {
    $detail = Invoke-MgGraphRequest -Method GET `
        -Uri "https://graph.microsoft.com/beta/directory/deviceLocalCredentials/$($d['id'])?`$select=credentials"
    [pscustomobject]@{
        DeviceName  = $d['deviceName']
        AccountName = $detail.credentials[0].accountName
    }
}
$accountReport | Group-Object AccountName | Format-Table Name, Count
// output
  Name           Count
  ----           -----
  WLapsAdmin       70
  Administrator    11

Seventy WLapsAdmin, eleven Administrator. The eleven match the 23H2 list from step 4 exactly. There is no policy fault. Everything is working as designed.

step 6 · the duplicate Entra object trap

For the three devices in Intune but not escrowing, the most common root cause on a hybrid tenant is a duplicate Entra device object. A PC that has been re-imaged, moved between domains, or had a security group reorganisation can end up with two or three Entra objects sharing the same displayName.

Each Entra device object has a TrustType field that says how it was joined: ServerAd (hybrid join from on-prem AD), AzureAd (cloud-only join), or Workplace (BYOD-style registration). On a hybrid tenant, the ServerAd object is the one that should be active. The other two are stale leftovers.

What goes wrong: the correct ServerAd object cannot complete its registration while stale Workplace or AzureAd objects exist for the same name.

Why: registration writes a field called alternativeSecurityIds on the Entra object. With duplicates blocking the way, that field stays empty. LAPS cannot escrow without it because the device cannot prove its identity to Entra.

Fix: identify the duplicates, confirm which is the correct ServerAd object, then delete the stale ones via the Entra portal or Remove-MgDevice. After cleanup, run dsregcmd /leave followed by dsregcmd /join on the device itself to re-register cleanly.

The diagnostic is one Graph beta query per object. Pull all Entra devices with pagination first, then group by name and inspect anything with a count above 1.

// run this
$all = @()
$u = "https://graph.microsoft.com/v1.0/devices"
do {
    $r = Invoke-MgGraphRequest -Method GET -Uri $u
    $all += $r.value
    $u = $r.'@odata.nextLink'
} while ($u)
$all | Group-Object { $_['displayName'] } | Where-Object Count -gt 1
// output
  Count Name
  ----- ----
      3 CORP-PC-039

Three Entra objects for one device. Pull the detail on each, look for the one with TrustType=ServerAd, and check whether its registrationDateTime and alternativeSecurityIds are populated. If one is empty and the other two are old Workplace or AzureAd entries, you have found the blocker.

common gotchas

  • You only see the first 100-ish devices in your "all devices" pull. A tenant with thousands of Entra objects returns one page at a time. Fix: follow @odata.nextLink in a loop until it stops returning. The script in the repo does this; if you copy out a fragment, do not skip the loop. Skipping pagination is the most common silent failure on this whole audit.
  • Escrow count is higher than expected. The v1.0 escrow list keeps records from devices that have been wiped, reprovisioned, or retired. Fix: run the gap analysis (Step 3) and quote the count of escrowed records that match a live Intune device. The raw escrow count is misleading.
  • HTTP 400 on bulk credential queries. You added $select=credentials to the list endpoint. Fix: Microsoft blocks this by design. Do not retry. Use the per-device beta endpoint as shown in Step 5.
  • $matching.Count returns wildly wrong numbers. You used $matches as a variable name. $matches is a reserved PowerShell variable that the -match operator populates with regex capture groups. Fix: rename it to $deviceMatches or anything other than $matches.
  • 23H2 device shows Administrator instead of WLapsAdmin. Not a misconfiguration. This is by design: the WLapsAdmin account is created by a Windows 11 24H2 feature called Automatic Account Management. On 23H2 and below, LAPS just manages the built-in Administrator account and shows that name in the portal. The password works in both cases. The only "fix" is upgrading the device to 24H2.

when to skip this and just look at one device

For a one-off "why isn't this single device escrowing" question, you do not need the fleet audit. Pull the Intune managed device record, pull the matching Entra device object via the beta endpoint, look at registrationDateTime and alternativeSecurityIds. Five minutes. The full audit earns its keep when you are reporting fleet status to a customer, planning a 24H2 rollout, or proving to compliance that 96% of the estate is escrowing as expected.

Need this run across your fleet, or want the script set as a starting point? The full audit toolset is on GitHub, sanitized and ready to drop into a tenant. If you would rather hand the audit over to someone who has done it before, book a 30-minute call and we can scope it.
← all posts // himanshu @ aroramsp