Powershell : Password Reset Compliance


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!

Previous Post Next Post

نموذج الاتصال