<# Finds the source of an Active Directory user object password reset or password change and whether or not the change was made by the local user
This is a Very good security script:
4723 = 'An attempt was made to change an account’s password '(user‑initiated change, e.g. Ctrl+Alt+Del → Change Password).
4724 = 'An attempt was made to reset an account’s password' (admin‑initiated reset).
#>
# Target user
$user = read-Host -prompt 'What SamAccountName in the present user domain?'
# Get all DCs in the domain in one line
$dcs = (Get-ADDomainController -Filter *).Hostname
# Define cutoff time
$since = (Get-Date).AddHours(-48)
$results = foreach ($dc in $dcs) {
try {
Get-WinEvent -ComputerName $dc -FilterHashtable @{
LogName = 'Security'
ID = 4723,4724
StartTime = $since
} -ErrorAction Stop |
Where-Object { $_.Properties.Value -contains $user } |
Select-Object @{n='DC';e={$dc}},
TimeCreated,
@{n='TargetUser';e={($_.Properties[0].Value)}},
@{n='Initiator';e={($_.Properties[1].Value)}},
Message
} catch {
Write-Warning "Failed to query $dc"
}
}
$results | Format-Table DC, TimeCreated, TargetUser, Initiator
CoPIlot suggests these changes:
Centralized error handling Instead of inline try/catch with Write-Warning, capture failures into a structured object so you can see which DCs failed and why.
Timeouts and fallbacks Get-WinEvent can hang if RPC is flaky. Use Invoke-Command with -AsJob or Parallel to query DCs concurrently, with a timeout per job. That way one bad DC doesn’t stall the whole script.
Structured output Return a single object array with both successes and failures. That makes it easy to export to CSV or hand off to another admin.
Parameterization Instead of hardcoding cutoff time, expose it as a parameter (-HoursBack, -User). Makes the script reusable.
Event parsing Don’t rely on Properties[0] or [1] indexes — those can vary. Use named XML fields ($_.Properties[n].Value) mapped to event schema. Or parse the Message with regex for reliability.
param(
[Parameter(Mandatory)]
[string]$User,
[int]$HoursBack = 48
)
Import-Module ActiveDirectory
$dcs = (Get-ADDomainController -Filter *).Hostname
$since = (Get-Date).AddHours(-$HoursBack)
$results = foreach ($dc in $dcs) {
try {
Get-WinEvent -ComputerName $dc -FilterHashtable @{
LogName = 'Security'
ID = 4723,4724
StartTime = $since
} -ErrorAction Stop |
Where-Object { $_.Properties.Value -contains $User } |
Select-Object @{n='DomainController';e={$dc}},
TimeCreated,
@{n='TargetUser';e={($_.Properties[0].Value)}},
@{n='Initiator';e={($_.Properties[1].Value)}},
@{n='EventID';e={$_.Id}},
@{n='Status';e={'Success'}},
Message
} catch {
[PSCustomObject]@{
DomainController = $dc
TimeCreated = $null
TargetUser = $User
Initiator = $null
EventID = $null
Status = "Failed: $($_.Exception.Message)"
Message = $null
}
}
}
$results | Format-Table DomainController, TimeCreated, TargetUser, Initiator, EventID, Status

