Skip to content

Troubleshooting

Rod Christiansen edited this page Sep 5, 2026 · 2 revisions

Troubleshooting

Symptom → cause → fix, grounded in the paths that actually exist in the code. Start every investigation in the same two places.

The operational log for today, which lives in a directory named for the local date the run started:

Get-Content "C:\ProgramData\ManagedUsers\logs\$(Get-Date -Format yyyy-MM-dd)\manageusers.log" -Tail 200

The audit log, which stays at the logs root and is not per-day:

Select-String -Path 'C:\ProgramData\ManagedUsers\logs\manageusers.audit.log' -Pattern '_FAILED|_SKIPPED|_DEFERRED'

Substitute the date to look at an earlier day; day directories are kept for 30 days and then deleted whole. To search the whole retained window at once, glob the day directories:

Get-ChildItem 'C:\ProgramData\ManagedUsers\logs\*\manageusers.log' |
    Select-String -Pattern 'ERROR'

Every entry also exists in machine-readable form in events.jsonl beside the day's log — useful when you want to filter by level or pull one invocation out of a day that ran on a schedule. See Logging and Auditing for the record shape.

There is no verbose flag, no --debug, and no log-level setting; the default output already includes a line per account evaluated with its ages and the keep/delete reason.


Nothing is ever deleted

Check the resolved policy first.

Select-String -Path "C:\ProgramData\ManagedUsers\logs\$(Get-Date -Format yyyy-MM-dd)\manageusers.log" -Pattern 'Deletion policy:|Rule .* matched|No rule matched'
Cause Evidence Fix
Never-delete policy in effect Never-delete policy: <user> — keep, Policy(Duration=-1d, …) Set a non-negative duration_days
No rule matched, default is long No rule matched area='…'/room='…'/usage='…' → default policy Check the inventory values against your regexes; remember room compares to the inventory's location key
Inventory missing Inventory file not found Fix the enrolment system or pass --inventory
Rule has no criteria Rule silently never matches A rule with an empty match: matches nothing; use default_policy for a catch-all
Everything is excluded Skipping excluded user: … for each account Trim exclusions:
Everything is a protected admin Skipping administrator account (protected; …) Use deletable_admins for specific accounts
Accounts are disabled No User: <name> line at all for them Disabled accounts are skipped by pass 1 (but not by the orphan pass)
Ages read as zero Could not determine creation date for <user>, using current time The profile folder is missing or unreadable; the account looks 0 days old to pass 1

More was deleted than expected

Cause Evidence Notes
Orphan pass ORPHAN_USER_REMOVED No age check at all. Any non-excluded local account without a ProfileList entry ending in \<name> is removed. Add it to exclusions:.
Collision-suffixed profile folder Account jsmith with folder jsmith.MACHINENAME Pass 1's profile join is a \<username> suffix match, so the account looks profile-less and falls to the orphan pass
Regex matched more than intended Rule '<name>' matched on an unexpected device match: values are unanchored regexes. area: "Lab" matches Collaboration. Anchor with ^…$
End-of-term force reason=end-of-term force deletion A matched rule had force_at_end_of_term: true and today satisfies month == M && day >= D
--force was used Force mode — duration set to 0 days Threshold 0 makes every account eligible
Config parse failure discarded exclusions Failed to parse Config.yaml: … — using built-in defaults Only the five built-in exclusions applied for that run
Admin guard did not apply administrator protection may be incomplete NetLocalGroupGetMembers failed; the run continued unprotected
A wildcard was used in exclusions: The account still shows a User: <name> evaluation line Exclusions are exact, case-insensitive strings. No patterns

Profile in use

Symptom: STALE_PROFILE_SKIPPED … reason=hive loaded, profile in use.

Cause: the SID's registry hive is loaded under HKEY_USERS, which means an active or disconnected session, a service running as that user, or a scheduled task holding the profile open. The code refuses to delete underneath a loaded hive, and treats an error reading HKEY_USERS as "in use" too.

Fix: end the session and let the next run retry.

quser
logoff <session id>
Get-ChildItem Registry::HKEY_USERS | Select-Object Name

A disconnected RDP session is the usual culprit and does not show as Active.


Deletion deferred forever

Symptom: USER_DELETE_DEFERRED … reason=console session active on every run, and the name stays in DeferredDeletes in Sessions.yaml.

Cause: quser reports an Active session for that name at the moment of deletion. On a device that is in use whenever the task runs, the deferral repeats indefinitely. Note the check matches the name anywhere on the quser line, so a short account name can be matched by another user's session name or by part of a longer name.

Fix: move the schedule to a time when the device is idle, or add a startup trigger. Inspect the queue:

Get-Content 'C:\ProgramData\Management\ManageUsers\Sessions.yaml'

An entry can be removed by hand; it is re-added on the next run if the condition still holds.


Locked files — profile only partly removed

Symptom: Failed to remove profile directory C:\Users\<name>: …, then STALE_PROFILE_REMOVE_FAILED or USER_DELETE_FAILED …reason=verification failed, and a folder still present under C:\Users.

Cause: the recursive delete swallows UnauthorizedAccessException and IOException per item so one locked file cannot abort the tree. Files held open by antivirus, search indexing, a backup agent, or a process that survived taskkill remain, and the folder cannot be removed.

Fix: find the holder and let the next run finish the job.

Get-Process | Where-Object { $_.Path -like 'C:\Users\<name>\*' } | Select-Object Id, ProcessName, Path
handle.exe -nobanner -u "C:\Users\<name>"

If the account is already gone and only the folder remains, that state is harmless — an orphaned folder with no ProfileList entry is retried as a stale profile on the next run. The dangerous inverse is covered below.


Permission denied

Symptom: Could not open ProfileList registry key, Could not create SpecialAccounts\UserList registry key, Failed to remove registry entry for …, Could not enumerate <drive>:\$Recycle.Bin, or NetUserEnum failed with code 5.

Cause: the process is not running with sufficient privilege. The manifest requests requireAdministrator, but several operations — writing HKLM, deleting other users' profile trees, clearing $Recycle.Bin subfolders — want SYSTEM, not merely an elevated administrator.

Fix: run it the way it is meant to run.

Get-ScheduledTask -TaskName 'WinAdmins-ManageUsers' | Select-Object -ExpandProperty Principal

UserId should be SYSTEM and RunLevel Highest. For interactive testing, use an elevated prompt and accept that some steps may behave differently than they do under the task. See Scheduling.

Windows error code 5 is access denied; code 2221 from NetUserEnum is "user not found".


ProfileList orphan

Two distinct broken states, both handled by pass 4.

State What the user sees Audit/log evidence
ProfileList\<SID> exists, folder is gone Next logon for that SID gets a temporary profile; changes are discarded at sign-out Corrupt profile state: <name> (SID: …) — dangling ProfileList entry (profile folder missing)
Folder exists, NTUSER.DAT is gone Temporary profile, Class not registered errors in Explorer … — profile folder is missing NTUSER.DAT (partial delete)

Both are remediated automatically on the next run, regardless of retention policy, by removing the remaining half. That is why the tool can delete something on a device configured with duration_days: -1.

One exception: if the folder still exists and its name is in the exclusion set, nothing is done and you get Corrupt profile state for excluded account <name>: … — not remediating, manual review needed. Handle that one by hand — the folder may still hold user data.

Inspect the current state:

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' |
    ForEach-Object {
        [pscustomobject]@{
            SID    = $_.PSChildName
            Path   = (Get-ItemProperty $_.PSPath).ProfileImagePath
            Exists = Test-Path (Get-ItemProperty $_.PSPath).ProfileImagePath
        }
    } | Where-Object { -not $_.Exists }

Recycle bins are not being reclaimed

Symptom Cause Fix
ProfileList could not be read; skipping orphaned recycle bin sweep The registry read failed or returned nothing. The sweep refuses to run rather than treat every bin as orphaned Fix registry access; run as SYSTEM
No SID for <name>; recycle bin could not be located and may remain The account's SID could not be resolved before deletion. Bins are named by SID only, so guessing is unsafe The end-of-run orphan sweep should catch it on a later run once the profile is gone
Recycle bin <SID> skipped — hive loaded, profile in use The SID still has a loaded hive End the session
Bins on a removable or network drive untouched Enumeration is limited to DriveType.Fixed Out of scope by design
The $Recycle.Bin\<SID> folder still exists after a successful clear Only the contents are deleted; the folder and its desktop.ini are left because Windows owns them Expected

Measure what is there:

Get-ChildItem 'C:\$Recycle.Bin' -Force -Directory |
    ForEach-Object {
        [pscustomobject]@{
            SID = $_.Name
            MB  = [math]::Round((Get-ChildItem $_.FullName -Recurse -Force -File -EA SilentlyContinue |
                  Measure-Object Length -Sum).Sum / 1MB, 1)
        }
    } | Sort-Object MB -Descending

The tool exits immediately

Exit code Cause Fix
2 Another instance of ManageUsers is already running. — the Global\ManageUsers mutex is held Wait for the other run; check for an earlier task instance that is stuck on a slow profile delete
1 Unhandled exception; message and stack trace are in the log at ERROR Read the trace. The usual causes are an unreadable config path or a registry access failure early in the run
1, with no log entries Invalid command line, or the log directory could not be created Check the arguments, and that C:\ProgramData\ManagedUsers\logs and today's <yyyy-MM-dd> subdirectory under it are creatable

An excluded account was deleted anyway

Work through these in order:

  1. Spelling. Exclusions are exact and case-insensitive. svc_backup does not protect svc-backup.
  2. Wrong file or wrong key. Config.yaml uses exclusions: (lowercase); Sessions.yaml uses Exclusions: (capital E). A key spelled the other way is silently ignored — unmatched YAML properties are dropped without error.
  3. Config did not parse. Failed to parse Config.yaml means built-in defaults were used and your list was discarded for that run.
  4. Sessions.yaml was rewritten. The tool serializes only Exclusions and DeferredDeletes; comments and extra keys do not survive a deferral write.
  5. It was a profile folder, not an account. The exclusion set is compared against the folder name in the stale-profile pass. jsmith.MACHINENAME is not protected by an entry of jsmith.
  6. Corrupt-profile remediation. A dangling ProfileList entry is removed even for an excluded name, because there is no folder left to preserve.

Confirm which of these applies:

Select-String -Path "C:\ProgramData\ManagedUsers\logs\$(Get-Date -Format yyyy-MM-dd)\manageusers.log" -Pattern 'Exclusions loaded|Skipping excluded user|Failed to parse Config'

A dry run deleted something

This is expected behaviour, not a bug, in exactly two cases:

  • Orphan account removal has no simulate guard. net user /delete runs for real. Look for ORPHAN_USER_REMOVED in the audit log.
  • Login-screen hiding writes SpecialAccounts\UserList regardless of mode.

Everything else — account deletion, profile deletion, recycle-bin clearing — is suppressed under --simulate and reported with _SIMULATED actions.


Deleted accounts still appear somewhere

Where Why Fix
Scheduled tasks still registered to the account Nothing removes per-user tasks Remove them yourself: Get-ScheduledTask | Where-Object { $_.Principal.UserId -eq '<name>' }
Credential Manager / IdentityStore entries The credential step enumerates and logs only; it deletes nothing Clear by hand if it matters
BitLocker key protectors The BitLocker step is informational; manage-bde -status C: only manage-bde -protectors -delete by hand
Sign-in screen still hides an account you re-created SpecialAccounts\UserList entries are added, never removed Delete the value from that registry key
The account reappears after a sync ManageUsers only removes local state; a directory or MDM policy may recreate it Fix upstream

Getting a clean diagnostic snapshot

& 'C:\Program Files\sbin\manageusers.exe' --simulate *>&1 | Tee-Object -FilePath "$env:TEMP\manageusers-dryrun.txt"
Get-LocalUser | Select-Object Name, Enabled, LastLogon | Format-Table -AutoSize
Get-CimInstance Win32_UserProfile | Select-Object LocalPath, SID, LastUseTime, Loaded, Special | Format-Table -AutoSize

Those three outputs plus the tail of the audit log are enough to reason about almost any behaviour on this page.

ManageUsers

Home

Before you deploy

Reference

Internals

Help


This tool deletes user accounts and profiles. Run --simulate first.

Clone this wiki locally