The event you have a lookout that doesn’t really tell you where it’s locking out from, I need to do a slightly deep dive on your netlogon.log file.
When do you get a lockout event that happens almost immediately this will be caused because you’ve got lots of bad passwords that lead to the lockout event, depending on your lockout threshold, that would depend how many pre-failures you require before your account is locked.
If we take the example that would be six attempts before the lockout that means when you get the lock out, it goes something like this:
Note : Remember that the 6th bad password is the actual lockout event and that is what the domain controllers will show with the event ID 4740 in the security log.
If you have any chance of figuring out where this has come from, because in this scenario, you could not rely on the caller computer because that will either be did the main controller itself, or it will be blank, depending on the cause of the lockout.
This means you need to ensure you monitor all your domain controllers for the netlogon.log file after setting it to verbose - if you do not set this mode, you will simply not get any information you can act on.
Bad password attempt #1
Bad password attempt #2
Bad password attempt #3
Bad password attempt #4
Bad password attempt #5
Account locked out (Attempt #6)
This sounds like a perfect opportunity to create a script that can complete the following actions:
- Checks if a Logs directory exists, creates if missing
- Asks user to choose between Login ID or UPN input method
- If Login ID entered, converts it to UPN using AD lookup
- Discovers all Domain Controllers in the domain
- For each DC:
- Enables NetLogon verbose logging using "nltest /DBFlag:2080FFFF"
- Optionally restarts NetLogon service if -RestartNetlogon switch used - Starts 15-minute monitoring period:
- Shows countdown timer
- Checks each DC's netlogon.log every 5 seconds
- Displays matching events in real-time
- Marks events in red for visibility
- Logs events to file when found - After monitoring period:
- Shows "No events detected" if nothing found
- For each DC, asks user if they want to disable verbose logging
- If no, double-checks if user wants to leave logging enabled
- If confirmed no, leaves logging enabled
- If yes or changed mind, disables logging with "nltest /DBFlag:0x0" - 8. Saves all activities and found events to log file in Logs folder with timestamp
Note : If you have a DC that cannot be communicated with on any valid ports, which should not the case then please ensure you add this DC to this variable (shown below) else the script may hang indefinitely!
[string[]]$ExcludedDCs = @("<troublesome_dc>")
Lets move onto the script.
Script : LockoutTracer.ps1
[CmdletBinding()]
param(
[string]$PsExecPath = "PsExec.exe",
[string[]]$ExcludedDCs = @("<troublesome_dc>")
)
# Get script directory and create Logs subdirectory
$ScriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path
$LogDirectory = Join-Path -Path $ScriptDirectory -ChildPath "Logs"
if (-not (Test-Path -Path $LogDirectory)) {
New-Item -ItemType Directory -Path $LogDirectory
}
$LogFile = Join-Path -Path $LogDirectory -ChildPath "LockoutAnalysis_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
$NetlogonPath = "C:\Windows\debug\netlogon.log"
function Write-Log {
param(
[string]$Message,
[string]$Color = "White"
)
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$TimeStamp] $Message" -ForegroundColor $Color
Add-Content -Path $LogFile -Value "[$TimeStamp] $Message"
}
function Get-UserSamAccountInfo {
param (
[Parameter(Mandatory=$true)]
[string]$UserInput
)
try {
if ([string]::IsNullOrWhiteSpace($UserInput)) {
throw "User input is empty"
}
if ($UserInput -like "*@*") {
# Input is UPN
Write-Log "Looking up user by UPN" -Color "Yellow"
$user = Get-ADUser -Filter "UserPrincipalName -eq '$UserInput'" -Properties SamAccountName
} else {
# Input is Login ID
Write-Log "Looking up user by Login ID" -Color "Yellow"
$user = Get-ADUser -Identity $UserInput -Properties SamAccountName
}
if ($user) {
$domain = $env:USERDOMAIN.ToLower()
$result = "$domain\$($user.SamAccountName)"
Write-Log "Found user: $result" -Color "Green"
return $result
} else {
throw "User not found in Active Directory"
}
}
catch {
Write-Log ("Error looking up user '$UserInput': " + $_.Exception.Message) -Color "Red"
throw
}
}
function Set-NetlogonDebugFlag {
param (
[string]$DC,
[string]$Flag
)
try {
# Check if PsExec exists
if (-not (Test-Path $PsExecPath)) {
Write-Log "PsExec not found at $PsExecPath. Please ensure PsExec is installed." -Color "Red"
return $false
}
# Use PsExec to run nltest remotely
$result = & $PsExecPath "\\$DC" -s nltest /DBFlag:$Flag 2>&1
if ($result -match "SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters set to") {
Write-Log ("Successfully set Netlogon debug flag on " + $DC) -Color "Green"
return $true
} else {
Write-Log ("Failed to set Netlogon debug flag on " + $DC + ": " + $result) -Color "Red"
return $false
}
}
catch {
Write-Log ("Error setting Netlogon debug flag on " + $DC + ": " + $_.Exception.Message) -Color "Red"
return $false
}
}
function Restart-NetlogonService {
param (
[string]$DC
)
try {
Write-Log ("Stopping Netlogon service on " + $DC) -Color "Yellow"
& $PsExecPath "\\$DC" -s net stop netlogon
Start-Sleep -Seconds 2
Write-Log ("Starting Netlogon service on " + $DC) -Color "Yellow"
& $PsExecPath "\\$DC" -s net start netlogon
Write-Log ("Successfully restarted Netlogon service on " + $DC) -Color "Green"
return $true
}
catch {
Write-Log ("Error restarting Netlogon service on " + $DC + ": " + $_.Exception.Message) -Color "Red"
return $false
}
}
function Disable-NetlogonDebugAll {
param (
[string[]]$DCs
)
foreach ($DC in $DCs) {
if (Set-NetlogonDebugFlag -DC $DC -Flag "0x0") {
Write-Log ("Verbose logging disabled on " + $DC) -Color "Green"
} else {
Write-Log ("Failed to disable verbose logging on " + $DC) -Color "Red"
}
}
}
function Search-NetlogonEvents {
param (
[string]$DC,
[string]$UserToSearch
)
try {
$allEvents = @()
# Check and read netlogon.log
$netlogonPath = "\\$DC\C$\Windows\debug\netlogon.log"
if (Test-Path $netlogonPath) {
Write-Log "Reading netlogon.log from $DC..." -Color "Yellow"
$events = Get-Content -Path $netlogonPath -ReadCount 0 -ErrorAction Stop | Where-Object {
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Entered"
or
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Returns 0xC000006A" -or
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Returns 0xC0000234"
}
if ($events) {
$allEvents += $events
}
} else {
Write-Log "netlogon.log not found on $DC" -Color "Yellow"
}
# Check and read netlogon.bak
$netlogonBakPath = "\\$DC\C$\Windows\debug\netlogon.bak"
if (Test-Path $netlogonBakPath) {
Write-Log "Reading netlogon.bak from $DC..." -Color "Yellow"
$bakEvents = Get-Content -Path $netlogonBakPath -ReadCount 0 -ErrorAction Stop | Where-Object {
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Entered" -or
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Returns 0xC000006A" -or
$_ -match "\[LOGON\].*SamLogon: Transitive Network logon of $UserToSearch.*Returns 0xC0000234"
}
if ($bakEvents) {
$allEvents += $bakEvents
}
} else {
Write-Log "netlogon.bak not found on $DC" -Color "Yellow"
}
return $allEvents
}
catch {
Write-Log ("Error reading logs from " + $DC + ": " + $_.Exception.Message) -Color "Red"
return $null
}
}
try {
Write-Log "Starting account lockout investigation script" -Color "Green"
# Get user information
do {
$inputType = Read-Host "Enter '1' for Login ID or '2' for UPN"
} while ($inputType -ne "1" -and $inputType -ne "2")
$userToSearch = ""
if ($inputType -eq "1") {
$loginID = Read-Host "Enter the Login ID"
$userToSearch = Get-UserSamAccountInfo -UserInput $loginID
Write-Log ("Searching for authentication events for account: " + $userToSearch) -Color "Green"
} else {
$upn = Read-Host "Enter the UPN"
$userToSearch = Get-UserSamAccountInfo -UserInput $upn
Write-Log ("Searching for authentication events for account: " + $userToSearch) -Color "Green"
}
# Get Domain Controllers and filter out excluded DCs
Write-Log "Discovering Domain Controllers..." -Color "Yellow"
$DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName | Where-Object { $_ -notin $ExcludedDCs }
Write-Log ("Found domain controllers: " + ($DCs -join ', ')) -Color "Green"
if ($ExcludedDCs) {
Write-Log ("Excluded domain controllers: " + ($ExcludedDCs -join ', ')) -Color "Yellow"
}
# Enable verbose logging and optionally restart Netlogon on each DC
Write-Log "NOTE: Restarting Netlogon service is recommended to ensure all new events are captured" -Color "Yellow"
$restartConfirmation = Read-Host "Do you want to restart Netlogon service on ALL DCs? This will be done with a 15-second gap between each DC (Y/N)"
foreach ($DC in $DCs) {
Write-Log ("Processing DC: " + $DC) -Color "Yellow"
Write-Log ("Enabling verbose logging on " + $DC) -Color "Yellow"
if (-not (Set-NetlogonDebugFlag -DC $DC -Flag "2080FFFF")) {
Write-Log ("Skipping " + $DC + " due to error enabling verbose logging") -Color "Red"
continue
}
if ($restartConfirmation -eq 'Y') {
Restart-NetlogonService -DC $DC
Write-Log ("Waiting 15 seconds before processing next DC...") -Color "Yellow"
Start-Sleep -Seconds 15
} else {
Write-Log ("WARNING: Skipping Netlogon restart on $DC - some events might not be captured until the service is restarted") -Color "Yellow"
}
}
$totalEventsFound = 0
foreach ($DC in $DCs) {
Write-Log ("Searching logs on " + $DC) -Color "Yellow"
$events = Search-NetlogonEvents -DC $DC -UserToSearch $userToSearch
if ($events) {
$eventCount = ($events | Measure-Object).Count
Write-Log ("Found $eventCount authentication events on $DC") -Color "Green"
$totalEventsFound += $eventCount
foreach ($event in $events) {
Write-Log "[$DC] $event" -Color "Red"
}
} else {
Write-Log ("No authentication events found on $DC") -Color "Yellow"
}
}
Write-Log ("Search complete - Found total of $totalEventsFound authentication events across all DCs") -Color "Green"
# Cleanup phase - disable debug logging on all DCs at once
$disableLogging = Read-Host "Do you want to disable verbose logging on ALL DCs? (Y/N)"
if ($disableLogging -eq 'Y') {
Write-Log "Disabling verbose logging on all DCs..." -Color "Yellow"
Disable-NetlogonDebugAll -DCs $DCs
} else {
Write-Log "WARNING: Verbose logging remains enabled on DCs. This may impact performance." -Color "Red"
}
Write-Log ("Complete - Results saved in: " + $LogFile) -Color "Green"
}
catch {
Write-Log ("Error: " + $_.Exception.Message) -Color "Red"
throw
}
Enabling : Manual Mode and Audit Review
This will the produce a log file with all the details you need to troubleshoot the issue further if any valid events are found, however the issue you may be experiencing may not only be those two events we are looking for, if this is the case then you need to dump all the data that couple be relevant and manually go though it.
If you wish to engage manual mode based on user data then you can use this script to dump all the data found for that samAccountName.
Script : NetLogon-AllEvents.ps1
[CmdletBinding()]
param(
[string[]]$ExcludedDCs = @("<troublesome_dc>")
)
# Get script directory and create Logs subdirectory
$ScriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path
$LogDirectory = Join-Path -Path $ScriptDirectory -ChildPath "Logs"
if (-not (Test-Path -Path $LogDirectory)) {
New-Item -ItemType Directory -Path $LogDirectory
}
$LogFile = Join-Path -Path $LogDirectory -ChildPath "NetlogonSearch_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
function Write-Log {
param(
[string]$Message,
[string]$Color = "White"
)
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path $LogFile -Value "[$TimeStamp] $Message"
}
function Get-UserSamAccountInfo {
param (
[Parameter(Mandatory=$true)]
[string]$UserInput
)
try {
if ([string]::IsNullOrWhiteSpace($UserInput)) {
throw "User input is empty"
}
if ($UserInput -like "*@*") {
# Input is UPN
Write-Log "Looking up user by UPN" -Color "Yellow"
$user = Get-ADUser -Filter "UserPrincipalName -eq '$UserInput'" -Properties SamAccountName
} else {
# Input is Login ID
Write-Log "Looking up user by Login ID" -Color "Yellow"
$user = Get-ADUser -Identity $UserInput -Properties SamAccountName
}
if ($user) {
$result = "$domain\$($user.SamAccountName)"
Write-Log "Found user: $result" -Color "Green"
return $result
} else {
throw "User not found in Active Directory"
}
}
catch {
Write-Log ("Error looking up user '$UserInput': " + $_.Exception.Message) -Color "Red"
throw
}
}
function Get-NetlogonEvents {
[string]$DC,
[string]$UserToSearch
)
try {
$allEvents = @()
$logFiles = @{
"Current" = "\\$DC\C$\Windows\debug\netlogon.log"
"Backup" = "\\$DC\C$\Windows\debug\netlogon.bak"
}
foreach ($logType in $logFiles.Keys) {
$logPath = $logFiles[$logType]
if (Test-Path $logPath) {
Write-Log "Reading $logType log file from $DC" -Color "Yellow"
# Read the file content with read sharing enabled
$events = Get-Content -Path $logPath -ReadCount 0 -ErrorAction Stop
# Extract just the username part from the domain\username format
$username = $UserToSearch.Split('\')[1]
# Filter events that contain the username
$matchedEvents = $events | Where-Object {
$_ -match "\[$username\]" -or # Match exact username in brackets
$_ -match "\\$username\s" -or # Match domain\username format
$_ -match "\\$username$" -or # Match at end of line
$_ -match "logon of.*\\$username" # Match in logon context
}
if ($matchedEvents) {
$allEvents += $matchedEvents | ForEach-Object { "[$logType] $_" }
Write-Log "Found $($matchedEvents.Count) events in $logType log" -Color "Green"
} else {
Write-Log "No events found in $logType log" -Color "Yellow"
}
} else {
Write-Log ($logType + " log not found on " + $DC + " at path " + $logPath) -Color "Yellow"
}
}
return $allEvents
}
catch {
Write-Log ("Error reading logs from " + $DC + ": " + $_.Exception.Message) -Color "Red"
return $null
}
}
try {
Write-Log "Starting Netlogon log search script" -Color "Green"
# Get user information
do {
$inputType = Read-Host "Enter '1' for Login ID or '2' for UPN"
} while ($inputType -ne "1" -and $inputType -ne "2")
$userToSearch = ""
if ($inputType -eq "1") {
$loginID = Read-Host "Enter the Login ID"
$userToSearch = Get-UserSamAccountInfo -UserInput $loginID
Write-Log ("Searching for events for account: " + $userToSearch) -Color "Green"
} else {
$upn = Read-Host "Enter the UPN"
$userToSearch = Get-UserSamAccountInfo -UserInput $upn
Write-Log ("Searching for events for account: " + $userToSearch) -Color "Green"
}
# Get Domain Controllers and filter out excluded DCs
Write-Log "Discovering Domain Controllers..." -Color "Yellow"
$DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName | Where-Object { $_ -notin $ExcludedDCs }
Write-Log ("Found domain controllers: " + ($DCs -join ', ')) -Color "Green"
if ($ExcludedDCs) {
Write-Log ("Excluded domain controllers: " + ($ExcludedDCs -join ', ')) -Color "Yellow"
}
$totalEventsFound = 0
foreach ($DC in $DCs) {
Write-Log ("Searching logs on DC: " + $DC) -Color "Yellow"
$events = Get-NetlogonEvents -DC $DC -UserToSearch $userToSearch
if ($events) {
$eventCount = ($events | Measure-Object).Count
Write-Log ("Found $eventCount events on $DC") -Color "Green"
$totalEventsFound += $eventCount
foreach ($event in $events) {
Write-Log "[$DC] $event" -Color "Cyan"
}
} else {
Write-Log ("No events found on $DC") -Color "Yellow"
}
}
Write-Log ("Search complete - Found total of $totalEventsFound events across all DCs") -Color "Green"
Write-Log ("Results saved in: " + $LogFile) -Color "Green"
} catch {
Write-Log ("Error: " + $_.Exception.Message) -Color "Red"
throw
}
This will then give an output of every entry that contains the samAccountName specified like this, but remember that a 0x0 is not an error when reviewing these logs.
[2024-12-16 17:30:05] [claws.bear.local] 12/16 16:54:26 [LOGON] [19148] BEAR: SamLogon: Transitive Network logon of BEAR/lee from RDP1-Manager (via TSGateway.bear.local) Entered
[2024-12-16 17:30:05] [claws.bear.local] 12/16 16:54:26 [LOGON] [19148] BEAR: SamLogon: Transitive Network logon of BEAR\lee from RDP1-Manager (via TSGateway.bear.local) Returns 0x0
This is only required if you have used the manual script, as you do not want ideally want to leave NetLogon in verbose mode.