Repository navigation
Safe Deployment
ManageUsers deletes user accounts, profile directories and recycle-bin contents. There is no undo, no quarantine, and no staging area — a deleted profile is gone. This page is the rollout order that keeps that from being a story you tell later.
Work through the stages in order. Do not skip stage 0 because the tool "isn't armed yet" — installing the package is what arms it, if your packaging registers the schedule.
Install the binary but do not register the scheduled task yet. The MSI's
postinstall deliberately does not create the task; it verifies the binary, creates
C:\ProgramData\Management\ManageUsers and C:\ProgramData\ManagedUsers\logs,
and seeds Sessions.yaml if it is missing. Scheduling is a separate, deliberate
step (Scheduling). The postinstall creates only the logs root; the
<yyyy-MM-dd> day directory the operational log lives in is created by the first
run, so an empty logs directory after install is expected.
msiexec /i ManageUsers-x64-<version>.msi /qnConfirm the binary is present and reports a version:
& 'C:\Program Files\sbin\manageusers.exe' --versionIf Config.yaml is absent, ManageUsers logs a warning and falls back to built-in
defaults: 28 days, login_and_creation, end-of-term dates of 30 April,
31 August and 31 December, and an empty exclusion list beyond the five built-ins.
That default will run against your device. Never let the first run be the one
that discovers the config is missing.
Write C:\ProgramData\Management\ManageUsers\Config.yaml first. For the very
first pass, use a never-delete policy so the tool can only report:
delete_admins: false
deletable_admins: []
exclusions:
- Administrator
- DefaultAccount
- Guest
- WDAGUtilityAccount
- defaultuser0
policies:
- name: "Observe only"
match:
usage: ".*"
duration_days: -1
strategy: login_and_creation
force_at_end_of_term: false
default_policy:
duration_days: -1
strategy: login_and_creation
force_at_end_of_term: false
end_of_term_dates: []duration_days: -1 means never delete in the retention and stale-profile passes.
A never-delete policy does not stop the orphan pass or corrupt-profile remediation. Those two passes ignore
duration_daysentirely. This is exactly why stage 2 is a dry run and not a live "safe" run.
Keep end_of_term_dates: empty until you have decided you want forced end-of-term
reaping. An end-of-term force on a matched rule deletes every non-excluded account
on the device with no age check.
Enumerate the machine's real accounts and decide, one by one. Start from what is actually there rather than from a template:
Get-LocalUser | Select-Object Name, Enabled, Description, LastLogon | Sort-Object NameGet-LocalGroupMember -Group (Get-LocalGroup -SID S-1-5-32-544).NameGet-CimInstance Win32_UserProfile | Select-Object LocalPath, SID, LastUseTime, LoadedEvery account in the following categories must be in exclusions: before any
live run, because most of them have no profile and are therefore targets of
the no-age-check orphan pass:
| Category | Why |
|---|---|
| SSH / remote-management accounts | Authenticate non-interactively, usually no profile |
| Local service accounts for agents, backup, imaging, monitoring | Same |
| Build/render worker accounts that run as a service | Same |
| Break-glass local administrator accounts | Protected by delete_admins: false, but belt and braces |
| Kiosk / autologon accounts | Have a profile, but you never want them reaped mid-term |
| Any account a scheduled task runs as | Deleting the account orphans the task silently |
Remember the matching rules: exact, case-insensitive, no wildcards. One line per account.
Run from an elevated prompt on one representative device:
& 'C:\Program Files\sbin\manageusers.exe' --simulateThen read the audit log, which is where the decisions are:
Get-Content 'C:\ProgramData\ManagedUsers\logs\manageusers.audit.log' -Tail 200What to look for:
| Audit action | Meaning |
|---|---|
RUN_START |
Confirms mode, force flag, resolved policy, and end-of-term state |
DELETE_DECISION |
An account or profile that would be deleted, with the reason and the ages |
USER_DELETE_SIMULATED |
An account a live run would delete |
STALE_PROFILE_REMOVE_SIMULATED |
A profile folder a live run would delete |
RECYCLE_BIN_REMOVE_SIMULATED / ORPHANED_RECYCLE_BIN_REMOVE_SIMULATED
|
Bins a live run would clear, with byte counts |
RUN_SUMMARY |
The full list of items in one line |
Cross-check against the operational log for the guards:
Select-String -Path "C:\ProgramData\ManagedUsers\logs\$(Get-Date -Format yyyy-MM-dd)\manageusers.log" -Pattern 'Exclusions loaded|Administrator protection|Console user detected|No rule matched|Rule .* matched|Orphaned user'The line Orphaned user (no profile): <name> is your warning that pass 2 has a
target. In a dry run that account is still deleted — see the next section.
--simulate guards account deletion, stale/corrupt profile deletion and
recycle-bin clearing. Two things run for real regardless:
-
The orphan pass deletes local accounts even under
--simulate.RemoveOrphanedUserscallsnet user /deletewith no simulate check. Any local account with no matching profile — and not excluded, and not a protected administrator — is really removed by a dry run. -
The login-screen registry write happens. Every excluded name is written as
a
DWORD 0value underHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList, hiding those accounts from the sign-in screen. Entries are only ever added, never removed.
Because of (1), do your very first --simulate on a device you are willing to
lose accounts on, with the exclusion list already correct. That is the honest
version of "dry run" here.
Pick one non-critical device, or a small group with an inventory area/usage
you control, and give it a real but generous policy. Everything else stays on
duration_days: -1.
policies:
- name: "Pilot — one room"
match:
area: "^Pilot Lab$"
room: "^B120$"
duration_days: 60
strategy: login_and_creation
force_at_end_of_term: false
default_policy:
duration_days: -1
strategy: login_and_creation
force_at_end_of_term: falseAnchoring with ^…$ matters: policy match: values are regexes and match as
substrings otherwise, so area: "Lab" would also select Open Lab, Lab Annex
and Collaboration.
Run it manually, live, once, and watch:
& 'C:\Program Files\sbin\manageusers.exe'Then verify:
Select-String -Path 'C:\ProgramData\ManagedUsers\logs\manageusers.audit.log' -Pattern 'USER_DELETED|STALE_PROFILE_REMOVED|_FAILED|DEFERRED'Get-LocalUser | Select-Object Name, EnabledGet-CimInstance Win32_UserProfile | Where-Object { -not $_.Special } | Select-Object LocalPath, SIDThe account list, the profile folders under C:\Users, and the ProfileList
registry entries must agree with each other. A ProfileList entry with no folder
is the state described in Troubleshooting → ProfileList orphan.
Leave the pilot on a manual or daily schedule for at least one full user cycle — a week for a teaching lab, a term if the policy is term-length — before widening.
Widen in one dimension at a time. Either add devices at the same threshold, or lower the threshold on the same devices — never both in one change.
A workable progression for a teaching lab:
| Step | duration_days |
Devices |
|---|---|---|
| Pilot | 60 | 1 room |
| Widen | 60 | 1 building |
| Tighten | 42 | 1 building |
| Widen | 42 | all shared devices |
| Tighten to target | 28 or 30 | all shared devices |
Only after that is stable should you consider force_at_end_of_term: true and
populate end_of_term_dates:. Understand before you do that the forced branch
deletes every non-excluded account and stale profile on matching devices,
every run, for the whole window the date condition covers, and that
force_at_end_of_term on default_policy: is parsed but never acted on — only
a matched rule can force.
Register the scheduled task only when the manual live runs have been boring for a full cycle. See Scheduling for the task definition, the SYSTEM requirement, and the single-instance mutex.
Choose a run time when nobody is logged in. Accounts with an Active session are
deferred rather than deleted, but a deferred account is retried on the next run,
so a schedule that always overlaps with use will keep deferring and never
converge.
| Don't | Why |
|---|---|
Deploy --force to a fleet |
Threshold becomes 0 days: every non-excluded account on the device is deleted. It is a targeted recovery tool, not a policy. |
Rely on a wildcard in exclusions:
|
Not supported. svc-* protects nothing but an account literally named svc-*. |
Leave Config.yaml absent and rely on defaults |
The fallback is a live 28-day policy, not a no-op. |
Assume --simulate is inert |
The orphan pass and the login-screen registry write are not simulated. |
Trust a Get-LocalUser snapshot alone |
The orphan pass keys on ProfileList, not on account age; check profiles too. |
Set end_of_term_dates before you want forced deletion |
A matched rule with force_at_end_of_term: true empties the device on those dates. |
There is no rollback of deleted data. What you can roll back is the tool's authority:
Unregister-ScheduledTask -TaskName 'WinAdmins-ManageUsers' -Confirm:$falseSetting every policy and the default to duration_days: -1 stops passes 1 and 3
but not the orphan or corrupt-profile passes. To stop the tool completely, remove
the schedule.
ManageUsers — local account and profile retention for shared Windows devices · source repository · see the repository for license terms
Before you deploy
Reference
Internals
Help
This tool deletes user
accounts and profiles.
Run --simulate first.