RDP error: unlock a locked out Windows account (PowerShell)
A locked out Windows account on RDP login is usually the result of automated attacks. This PowerShell script unlocks the account, shows the source IPs and adjusts the lockout policy.
Your Remote Desktop login suddenly fails and Windows reports something along the lines of: "For security reasons the user account has been locked because there were too many logon attempts or password change attempts." That means the account has tripped the Windows account lockout policy. The cause is almost always the same: someone or something has been hammering your RDP access from the internet with login attempts.
In this guide we show you how to unlock the account, find out which IP addresses are triggering the lockouts, and adjust or disable the lockout policy. A single PowerShell script takes care of all of it, with no clicking around in the Group Policy editor.
Why does a Windows account get locked out on RDP login?
Windows counts failed sign-in attempts and locks an account as soon as the configured threshold is reached. That protection makes sense, but it turns into a problem the moment the RDP port sits open on the internet: automated bots then work through passwords around the clock and lock out exactly the account you want to work with yourself.
Three values control the behavior:
- Lockout threshold: number of failed attempts before the account is locked
- Lockout duration: minutes the account stays locked
- Observation window: minutes over which failed attempts are added up
Open PowerShell as an administrator
Open Windows PowerShell as an administrator. Right-click "Windows PowerShell" in the Start menu and choose "Run as administrator". Without elevated rights the script can neither unlock the account nor change the policy.
The script: unlock the account and expose the attackers
Adjust the account name and the values you want in the settings section, then paste the script into PowerShell:
#Requires -RunAsAdministrator
# KernelHost: unlock a locked out Windows RDP account and track down the cause
# Run in Windows PowerShell as an administrator.
# ----------------------------------------------------------------------------
# Settings: please adapt these to your environment
# ----------------------------------------------------------------------------
$Username = 'Administrator' # the local account that should be unlocked
$ApplyPolicy = $true # $false if the lockout policy should stay unchanged
$LockoutThreshold = 0 # failed attempts before lockout (0 = lockout OFF, see text)
$LockoutDuration = 15 # minutes the account stays locked
$LockoutWindow = 15 # minutes over which failed attempts are counted
# ----------------------------------------------------------------------------
# 1) Show the current lockout policy
Write-Host "`n=== 1) Current lockout policy ===" -ForegroundColor Cyan
net accounts | Select-String 'Lockout|Sperr'
# 2) Unlock the account
Write-Host "`n=== 2) Unlock account '$Username' ===" -ForegroundColor Cyan
try {
$user = [ADSI]"WinNT://$env:COMPUTERNAME/$Username,user"
if ($user.InvokeGet('IsAccountLocked')) {
$user.InvokeSet('IsAccountLocked', $false)
$user.SetInfo()
Write-Host " '$Username' was locked and is now UNLOCKED." -ForegroundColor Green
} else {
Write-Host " '$Username' is not locked." -ForegroundColor Yellow
}
} catch {
Write-Warning " '$Username' could not be unlocked: $($_.Exception.Message)"
}
# 3) Optional: adjust or disable the lockout policy
if ($ApplyPolicy) {
Write-Host "`n=== 3) Apply lockout policy ===" -ForegroundColor Cyan
if ($LockoutThreshold -eq 0) {
net accounts /lockoutthreshold:0 | Out-Null
Write-Host " Account lockout DISABLED (threshold 0)." -ForegroundColor Yellow
Write-Host " Now restrict RDP to your own IP address in the firewall." -ForegroundColor Yellow
} else {
net accounts /lockoutthreshold:$LockoutThreshold /lockoutduration:$LockoutDuration /lockoutwindow:$LockoutWindow | Out-Null
Write-Host " Threshold=$LockoutThreshold Duration=$LockoutDuration min Window=$LockoutWindow min" -ForegroundColor Green
}
}
# 4) Who is triggering the lockouts? (sources of the RDP attacks)
Write-Host "`n=== 4a) Recent lockout events (Security ID 4740) ===" -ForegroundColor Cyan
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4740} -MaxEvents 10 -ErrorAction SilentlyContinue |
ForEach-Object {
$x = [xml]$_.ToXml()
[pscustomobject]@{
Time = $_.TimeCreated
LockedAccount = ($x.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
Source = ($x.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetDomainName' }).'#text'
}
} | Format-Table -AutoSize
Write-Host "=== 4b) Most frequent source IPs of failed logons, last 24h (Security ID 4625) ===" -ForegroundColor Cyan
$since = (Get-Date).AddHours(-24)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$since} -ErrorAction SilentlyContinue |
ForEach-Object {
([xml]$_.ToXml()).Event.EventData.Data |
Where-Object { $_.Name -eq 'IpAddress' } | Select-Object -ExpandProperty '#text'
} |
Where-Object { $_ -and $_ -ne '-' } |
Group-Object | Sort-Object Count -Descending | Select-Object -First 10 |
Format-Table Count, @{N='SourceIP'; E={$_.Name}} -AutoSize
What the script does in detail
- It uses
net accountsto show the current account lockout policy, meaning threshold, lockout duration and observation window. - It unlocks the account configured under
$Usernamethrough the WinNT directory provider. That is the equivalent ofUnlock-ADAccountfor local accounts. - It adjusts the policy, provided
$ApplyPolicyis set to$true. With the default values the lockout becomes less aggressive, and with$LockoutThreshold = 0you switch it off entirely. - It lists the most recent lockout events (event ID 4740) as well as the most frequent source IP addresses behind failed sign-ins over the last 24 hours (event ID 4625). Those are the addresses your RDP access is being attacked from.
A note on language: On a German-language Windows, net accounts prints the values under German labels. That is why the script above filters for both "Lockout" and "Sperr".
Local account or domain account?
The script targets a local account, which is the typical case on a standalone VPS or root server. If you are dealing with a domain account, or working on a domain controller, unlock the account with this instead:
Unlock-ADAccount -Identity <username>
Adjusting or disabling the lockout threshold
The script presets the threshold to 0 and thereby switches account lockout off. That sounds wrong at first, but on RDP access that stands open to the internet it is the better choice. The reason: an attacker does not have to guess your password at all. It is enough for them to send a few wrong attempts every couple of minutes to keep the account permanently locked. The lockout then hits you rather than them. A protective feature turns into a convenient tool for keeping you off your own server.
Important: switching the lockout off does not replace protection, it only removes the wrong one. Protection against someone working through passwords belongs one layer down, namely at the firewall: once RDP can only be reached from your own IP address, no attacker gets as far as the sign-in prompt and there is nothing left to count. So work through the next section before you switch the lockout off, not afterwards.
If operational reasons force you to leave RDP open to changing addresses, set a high threshold with a short lockout duration instead, for example $LockoutThreshold = 50 and $LockoutDuration = 5. That keeps a bit of braking effect without locking you out on every scan. In a domain with internal RDP the classic recommendation of a low threshold still applies, because access there is not reachable from the internet anyway.
Fixing the cause: securing RDP for good
A locked account is only the symptom. The real cause is RDP access that stands open to the entire internet. These four measures deal with the problem at the root.
Restrict RDP to your own IP address
This is by far the most effective measure. Once RDP can only be reached from your own address, every automated attack runs into a wall:
# Allow inbound RDP from your own IP address only
Set-NetFirewallRule -DisplayName 'Remote Desktop - User Mode (TCP-In)' -RemoteAddress '203.0.113.5'
The display name used here applies to an English-language Windows. On a German-language system it reads differently. You can list the existing rules and their exact names with Get-NetFirewallRule -DisplayGroup 'Remotedesktop'.
Block an individual attacker IP
For the addresses that step 4b of the script prints out, create a targeted block rule:
New-NetFirewallRule -DisplayName 'Block RDP attacker 203.0.113.9' -Direction Inbound -Action Block -RemoteAddress '203.0.113.9'
This helps against individual persistent sources, but it does not replace restricting access to your own IP address, because attackers keep moving to new addresses.
Change the RDP port
Once RDP no longer listens on the default port 3389, the usual mass scans will not even find your service. Our guide Change the RDP port on Windows without a reboot shows how to switch the port without restarting the server.
Strong password and Network Level Authentication
Use a long, random password and leave Network Level Authentication (NLA) enabled. NLA requires authentication before a session is even established, which fends off a large share of the automated attempts up front.
Frequently asked questions
Why does my Windows account get locked out when I log in over RDP?
How long does a locked account stay locked?
Should I disable account lockout completely?
How do I find out which IP addresses are locking my account?
The script does not unlock my account, why?
2026 KernelHost GmbH. All rights reserved. This guide is protected by copyright. Republishing it on other websites, in whole, in part or in edited form, is not permitted without our written consent. Quoting with a source credit and a link is expressly welcome.

