Reset and Unlock AD User Accounts in Bulk with PowerShell
Before you run this
This guide resets the passwords of a list of Active Directory user accounts, clears any lockout on them, and flags each account to require a new password at the next sign-in. The usual reason is a batch of expired or compromised accounts, or a group of seasonal staff coming back online.
Read this before you touch anything:
- It needs elevated rights. Run it from an elevated PowerShell session (Run as administrator) under an account that has delegated password-reset rights or Domain Admin on the target accounts. It also needs the ActiveDirectory module, which ships with RSAT on a management workstation and is present by default on a Domain Controller.
- Test on one account first. Point the script at a single throwaway test user before you ever feed it a real list. Read the whole script and understand each line. Do not paste it straight onto a production DC.
- The password reset is irreversible.
Set-ADAccountPassword -Resetthrows the old password away — there is no "undo", no recovery of the previous secret. Everyone in your list will be locked out of their current password the moment it runs. The-ChangePasswordAtLogonflag is reversible (you can turn it back off), and so is an unlock, but the reset itself is not. - Safe by default. Every changing command below carries
-WhatIf, so a first run only reports what it would do and changes nothing. You remove-WhatIfdeliberately, once you trust the output.
What I'm assuming
- Windows Server 2019/2022 or Windows 10/11 with RSAT, joined to the domain.
- Windows PowerShell 5.1 (the version that ships in-box). The commands work the same in PowerShell 7 if you have the ActiveDirectory module imported there.
- A single domain,
example.com. Replace every placeholder — the domain, the OU path, the CSV path — with your own.
Load the module first:
Import-Module ActiveDirectory
Step 1 — Build the list of accounts
Never operate on "everyone". Work from an explicit, filtered scope. The cleanest source is a CSV you can eyeball first. Create users.csv with a single column header:
SamAccountName
jsmith
adavis
mchen
Load and sanity-check it:
# CSV must have a column named exactly SamAccountName
$users = Import-Csv -Path "C:\path\to\users.csv"
# Look at what you're about to touch, and confirm every account resolves
$users | ForEach-Object {
Get-ADUser -Identity $_.SamAccountName |
Select-Object SamAccountName, Enabled, DistinguishedName
}
If any name doesn't resolve, Get-ADUser errors on that line — fix the CSV before going further.
If instead your goal is simply "unlock every account that is currently locked out", you don't need a CSV. AD has a purpose-built cmdlet for that, covered in Step 3.
Step 2 — The bulk reset script (dry run first)
This is the core. As written it changes nothing, because of -WhatIf.
Import-Module ActiveDirectory
# CSV with a SamAccountName column
$users = Import-Csv -Path "C:\path\to\users.csv"
# Prompt once for a temporary password. -AsSecureString keeps it out of the
# console history and off disk. All listed users get this same temp password,
# so force a change at next logon (below) and keep the value short-lived.
$tempPassword = Read-Host -AsSecureString -Prompt "Temporary password"
foreach ($u in $users) {
$sam = $u.SamAccountName
# Reset the password. IRREVERSIBLE - the old password is gone.
Set-ADAccountPassword -Identity $sam -Reset -NewPassword $tempPassword -WhatIf
# Clear any lockout (harmless if the account isn't locked).
Unlock-ADAccount -Identity $sam -WhatIf
# Require the user to set their own password at next sign-in.
Set-ADUser -Identity $sam -ChangePasswordAtLogon $true -WhatIf
}
Run it. With -WhatIf present you'll see a What if: line for each action per user and no changes are made. Read that output. Confirm the SamAccountNames are the ones you expect and nothing extra crept in.
A note on the shared temporary password: giving every account the same temp secret is a real weakness for the window before people change it. -ChangePasswordAtLogon $true closes that window at first sign-in, which is why it's in the script. If you need unique per-user passwords, generate one string per user inside the loop and export it to a protected record you hand out securely — but keep that record off the domain and delete it once used.
Step 3 — Apply it
When the dry-run output is exactly what you want, remove -WhatIf from the three changing commands and run again. That's the only change: delete the -WhatIf tokens.
For the "unlock everything currently locked" case, use the built-in search cmdlet instead of a CSV. Preview first:
# Preview: list locked-out accounts without changing anything
Search-ADAccount -LockedOut | Select-Object SamAccountName, DistinguishedName
Then unlock them, keeping -WhatIf on the first pass:
# Dry run
Search-ADAccount -LockedOut | Unlock-ADAccount -WhatIf
# Live: drop -WhatIf once the preview looks right
Search-ADAccount -LockedOut | Unlock-ADAccount
You can constrain Search-ADAccount to a subtree with -SearchBase "OU=Staff,DC=example,DC=com" if you don't want the whole directory — check the cmdlet's help (Get-Help Search-ADAccount -Full) for the exact parameters before relying on that scope.
Step 4 — Verify it worked
Confirm no listed account is still locked, and that each is flagged to change its password. When ChangePasswordAtLogon is set, PasswordLastSet reads as empty:
$users = Import-Csv -Path "C:\path\to\users.csv"
$users | ForEach-Object {
Get-ADUser -Identity $_.SamAccountName -Properties LockedOut, PasswordLastSet |
Select-Object SamAccountName, Enabled, LockedOut, PasswordLastSet
}
You want LockedOut = False for every row, and PasswordLastSet blank (that's the signal the change-at-logon flag is active — it fills in once the user sets their own password).
To double-check that nothing in the domain is still locked:
Search-ADAccount -LockedOut | Select-Object SamAccountName
An empty result means everything is unlocked.
Undoing what can be undone
Be honest with yourself here: the password reset cannot be reversed. If you reset the wrong people, the fix is to reset them again to a known value and let them back in — the previous password is unrecoverable.
The two flags are reversible. To clear the "must change password at next logon" requirement on the listed accounts (for example, if you set it in error):
$users = Import-Csv -Path "C:\path\to\users.csv"
$users | ForEach-Object {
Set-ADUser -Identity $_.SamAccountName -ChangePasswordAtLogon $false -WhatIf
}
Remove -WhatIf to apply. There is no "re-lock" cmdlet and you would never want one — lockout is a state the system sets, not something you restore by hand.
Where to check the exact syntax
Every cmdlet used here is part of Microsoft's ActiveDirectory module and documented on Microsoft Learn. Before a production run, read the reference pages for Set-ADAccountPassword, Unlock-ADAccount, Set-ADUser, and Search-ADAccount — confirm the parameters against the version installed on your box, since RSAT tracks the OS. Get-Help <cmdlet> -Full gives you the same detail offline on the machine you're actually running on.
The whole procedure is just those four cmdlets, a CSV, and the discipline to run with -WhatIf first. Keep that habit and a bulk reset stays a routine chore instead of an incident.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
