I was reviewing the event logs of a Domain controller and notices the Event ID 4724 which is this event "An attempt was made to reset an account's password" - this is the event for a user resetting their password which is a normal event.
However this is usually done by another account which could be an automation account or a service account or some other managed identity - this is what the events usually look like:
Subject:
Security ID: bear\bear.user
Account Name: bear.user
Account Domain: bear
Logon ID: 0x14D26D7AC8
Target Account:
Security ID: bear\sql.2024a
Account Name: sql.2024a
Account Domain: bear
In the example above you can see that "bear.user" has reset the password for "sql.2024a" but would it not be interesting it you could monitor when "bear.user" resets his own password, as per the event below:
Subject:
Security ID: bear\bear.user
Account Name: bear.user
Account Domain: bear
Logon ID: 0x14D26D7AC8
Target Account:
Security ID: bear\bear.user
Account Name: bear.user
Account Domain: bear
This can be a normal operation for certain automation and managed service accounts so if we wish to script this we need to ensure we have exclusions for certain types or account as per the user cases, but this script will check (excluding the exclusions) for where the user as reset their own password as both the caller and the subject.
Script : PasswordResetCompliance.ps1
# Import Active Directory module
Import-Module ActiveDirectory
# Define output log file and timestamp for unique filename
$Timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$LogFile = "PasswordResetLog_$Timestamp.txt"
# Initialize counters
$TotalEventsFound = 0
$ProcessedDCs = 0
# Get a list of all domain controllers
$DomainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
$TotalDCs = $DomainControllers.Count
Write-Host "Starting password reset audit across $TotalDCs domain controllers..." -ForegroundColor Cyan
# Function to process logs remotely
Function Process-SecurityLog {
param (
[string]$ComputerName
)
$EventsFound = 0
$StartTime = Get-Date
Write-Host "`nProcessing DC: $ComputerName" -ForegroundColor Yellow
Write-Host "Time started: $StartTime"
try {
# Get password reset events (4724)
$ResetQuery = @{
LogName = 'Security'
ID = 4724
}
Write-Host "Querying reset events..." -ForegroundColor Yellow
$ResetEvents = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $ResetQuery -ErrorAction Stop
if ($ResetEvents) {
Write-Host "Found $($ResetEvents.Count) reset events to analyze" -ForegroundColor Green foreach ($Event in $ResetEvents) {
$Message = $Event.Message
# Extract account names from reset event
# Get Subject Account
if ($Message -match "Subject:[\s\S]*?Account Name:\s*([^\r\n]+)") {
$SubjectAccountName = $Matches[1].Trim().Trim("'")
}
# Get Target Account separately
if ($Message -match "Target Account:[\s\S]*?Account Name:\s*([^\r\n]+)") {
$TargetAccountName = $Matches[1].Trim().Trim("'")
}
Write-Host "`nProcessing Event:" -ForegroundColor Yellow
Write-Host "Subject Account: $SubjectAccountName" -ForegroundColor Yellow
Write-Host "Target Account: $TargetAccountName" -ForegroundColor Yellow
# Only proceed if subject and target are the same (self-reset)
if ($SubjectAccountName -eq $TargetAccountName) {
Write-Host "Self-reset detected" -ForegroundColor Green
# List of prefixes to exclude (case insensitive)
$ExcludedPrefixes = @('Exclude1', 'Exclude2', 'Exclude3')
# Check if account should be excluded
$ShouldExclude = $false
$AccountLower = $SubjectAccountName.ToLower()
foreach ($prefix in $ExcludedPrefixes) {
if ($AccountLower.StartsWith($prefix.ToLower())) {
Write-Host "Excluding $SubjectAccountName (matches prefix $prefix)" -ForegroundColor DarkGray
$ShouldExclude = $true
break
}
}
# Only log if not excluded
if (-not $ShouldExclude) {
$EventsFound++
$LogEntry = "[{0}] User '{1}' reset their own password (Event ID: {2})" -f `
$Event.TimeCreated, $SubjectAccountName, $Event.Id
Add-Content -Path $LogFile -Value $LogEntry
Write-Host "Logged self-reset for: $SubjectAccountName" -ForegroundColor Green
}
} else {
Write-Host "Skipping - not a self-reset" -ForegroundColor DarkGray
}
}
}
}
catch {
if ($_.Exception.Message -match "No events were found") {
Write-Host "No matching events found in the specified time period" -ForegroundColor Yellow
}
else {
Write-Host "Error processing $ComputerName`: $_" -ForegroundColor Red
Add-Content -Path $LogFile -Value "ERROR [$ComputerName]: $_"
}
}
finally {
$EndTime = Get-Date
$Duration = New-TimeSpan -Start $StartTime -End $EndTime
Write-Host "`nTime completed: $EndTime"
Write-Host "Duration: $($Duration.Minutes) minutes and $($Duration.Seconds) seconds"
Write-Host "Events found: $EventsFound" -ForegroundColor Cyan
}
return $EventsFound
}
# Process each domain controller
foreach ($DC in $DomainControllers) {
$ProcessedDCs++
$PercentComplete = [math]::Round(($ProcessedDCs / $TotalDCs) * 100, 2)
Write-Progress -Activity "Processing Domain Controllers" -Status "$DC ($ProcessedDCs of $TotalDCs)" -PercentComplete $PercentComplete
$DCEvents = Process-SecurityLog -ComputerName $DC
$TotalEventsFound += $DCEvents
}
Write-Progress -Activity "Processing Domain Controllers" -Completed
# Output final summary
Write-Host "`n=== Final Summary ===" -ForegroundColor Cyan
Write-Host "Total DCs Processed: $TotalDCs" -ForegroundColor Green
Write-Host "Total Events Found: $TotalEventsFound" -ForegroundColor Green
Write-Host "Log File Location: $LogFile" -ForegroundColor Green
This will look like then when run:
Notice that if the account does not match a "self reset" or its on the exclusions list its excluded from the audit, so you only get valid accounts.
This will then produce a log file as you can see below with all the matching accounts:
This will then look like this:
This then gives you visibility of people setting their own password, not that anything needs to be done, but this answered a "can I do that" questions - Yes, I can!