This script lets you see lockout events for a user from the domain controller on which the event(s) occurred. It accepts the user’s distinguished name as input, enumerates the list of domain controllers, then finds the LockoutTime on each domain controller. It then calculates the time +/- 2 seconds, and queries the DC event log for the lockout. The data is returned to Out-Gridview including the source of the lockout. The ActiveDirectory module is not required, but adequate permissions to query the DC event log are required. You will be prompted to elevate if required.
<# GUI to get lockout information. Basic data requires no permissions. With permissions to query a DC's event log, the script will get the lockout event information Alan Kaplan www.akaplan.com Public version 12/24/21 #> [CmdletBinding()] param ( #DistinguishedName [Parameter(Mandatory = $true)] [string] $dN ) Clear-Host Add-Type -AssemblyName Microsoft.visualBasic #This is used to ensure that input box is modal #http://stackoverflow.com/questions/9978727/focus-window-created-by-powershell-script $activateWindow = { $isWindowFound = $false while (-not $isWindowFound) { try { [microsoft.visualbasic.interaction]::AppActivate($args[0]) $isWindowFound = $true } catch { Start-Sleep -Milliseconds 200 } } } Function isToday($TestDate) { ([System.DateTimeOffset]::Parse($TestDate)).date -eq [datetime]::Today } #Chris Stiehl function Convert-UAC([int]$value) { $flags = @("", "ACCOUNTDISABLE", "", "HOMEDIR_REQUIRED", "LOCKOUT", "PASSWD_NOTREQD", "PASSWD_CANT_CHANGE", "ENCRYPTED_TEXT_PWD_ALLOWED", "TEMP_DUPLICATE_ACCOUNT", "NORMAL_ACCOUNT", "", "INTERDOMAIN_TRUST_ACCOUNT", "WORKSTATION_TRUST_ACCOUNT", "SERVER_TRUST_ACCOUNT", "", "", "DONT_EXPIRE_PASSWORD", "MNS_LOGON_ACCOUNT", "SMARTCARD_REQUIRED", "TRUSTED_FOR_DELEGATION", "NOT_DELEGATED", "USE_DES_KEY_ONLY", "DONT_REQ_PREAUTH", "PASSWORD_EXPIRED", "TRUSTED_TO_AUTH_FOR_DELEGATION") (1..($flags.length) | Where-Object { $value -band [math]::Pow(2, $_) } | ForEach-Object { $flags[$_] }) -join ', ' } Try { $DNTest = [adsi]::Exists("LDAP://$dn") if ($DNTest -eq $false) { $msg = 'User account not found!' [void][Microsoft.VisualBasic.Interaction]::MsgBox($msg, 'OkOnly,Critical', "Error") Exit } } Catch { $msg = $Error[0].Exception.Message [void][Microsoft.VisualBasic.Interaction]::MsgBox($msg, 'OkOnly,Critical', "Error") Exit } $domain = $dN.Substring($dN.IndexOf(",DC")).Replace(",DC=", ".").Substring(1) Write-Progress "Getting DCs in $domain" # Connect to the specified domain and retrieve the list of all dcs within this domain $DomainContext = New-Object System.directoryServices.ActiveDirectory.DirectoryContext("Domain", $Domain) $ADSIDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) $DCList = ($ADSIDomain.DomainControllers).name | Sort-Object $LockoutList = foreach ($dc in $DCList) { Write-Progress "Checking for a lockout on $dc" Try { $u = [ADSI]"LDAP://$dc/$dn" $uProps = $u.properties $Name = $uProps.item('name').value $SamAccountname = $uProps.item('SamAccountName').value if ($uProps.item('badpasswordtime')) { $LBPA = [datetime]::fromfiletime($u.ConvertLargeIntegerToInt64($uProps.item('badpasswordtime')[0])) $lockTime = [datetime]::fromfiletime($u.ConvertLargeIntegerToInt64($uProps.item('LockoutTime')[0])) if ($uProps.Item('msDS-LastFailedInteractiveLogonTime')) { $lastFailedTime = [datetime]::fromfiletime($u.ConvertLargeIntegerToInt64($uProps.Item('msDS-LastFailedInteractiveLogonTime')[0])) } Else { $lastFailedTime = '' } } else { $LBPA = '' $lockTime = '' $lastFailedTime = '' } [PSCustomObject]@{ DC = $DC Name = $Name LastBadPasswordAttempt = $LBPA SamAccountName = $SamAccountname BadPwdCount = $uProps.item('BadPwdCount').value LockoutTime = $LockTime LastFailedInteractiveLogonTime = $lastFailedTime IsLocked = $u.IsAccountLocked UAC = convert-UAC($uProps.item('UserAccountControl').value) } } Catch { Write-Warning $Error[0].Exception.Message $errMsg = "Failed to get lockout information from $dc" Write-Warning $errMsg [PSCustomObject]@{ DC = $DC Name = '' LastBadPasswordAttempt = '' SamAccountName = '' BadPwdCount = '' LockoutTime = '' LastFailedInteractiveLogonTime = '' IsLocked = '' UAC = 'Error getting user info' } } } $LockoutList | Sort-Object LastBadPasswordAttempt -Descending | Set-Variable List Write-Progress "List Collected" -Completed $List = $List | Where-Object { $_.isLocked -and ($_.BadPwdCount -gt 0) -And (isToday($_).LastBadPasswordAttempt) } $DCErrors = ($lockoutlist | Where-Object { $_.UAC -eq 'Error getting user info' }).dc -join ', ' if ($null -eq $list) { $msg = "No lockouts found." if ($DCErrors) { #Remove domain from string $msg += " Failed to query these DCs: $DCErrors" } [void][Microsoft.VisualBasic.Interaction]::MsgBox($msg, 'OkOnly,Exclamation', "Error") Exit } Else { $List | Out-GridView -Title "Lockout Status for $name`. Selected information to clipboard" -PassThru | Out-String | Set-Variable toClip if ($toClip) { $toClip | Set-Clipboard } } #Ensure Inputbox is top window $job = Start-Job $activateWindow -ArgumentList "Get Events" $msg = @" If you have domain admin permissions, you can continue by getting the lockout events and reporting the source of the lockout. Do you want to continue with getting logon events from of the DCs which showed a bad password attempt in the previous display? 1) Yes, and prompt me for credentials. 2) Yes, and my current logon has the rights. 0) No, exit. "@ $retval = [Microsoft.VisualBasic.Interaction]::InputBox($msg, "Get Events?", 0) Remove-Job $job -Force switch ($retval) { 1 { $Script:cred = Get-Credential -Message "Domain Admin NMEA"; Break } 2 { Break } Default { Exit } } Clear-Host $SLEvents = @($List | ForEach-Object { $dc = ($_).DC Write-Progress "Querying security event log on $DC" $eventTime = [System.DateTimeOffset]::Parse($_.lockoutTime) #2 seconds before, formatted as required by event log $s = $eventTime.AddSeconds(-2) $StartDate = $s.UtcDateTime.GetDateTimeFormats()[101] #2 seconds after, formatted as required by event log $e = $eventTime.AddSeconds(2) $EndDate = $e.UtcDateTime.GetDateTimeFormats()[101] $EventQueryXML = @" <QueryList> <Query Id="0" Path="Security"> <Select Path="Security">*[System[(EventID=4740) and TimeCreated[@SystemTime>='$StartDate' and @SystemTime<='$EndDate']]]</Select> </Query></QueryList> "@ Clear-Host $Params = @{ 'ComputerName' = $DC erroraction = 'Stop' } if ($retval -eq 1) { $params.add('Credential', $cred) } Try { $events = Invoke-Command -ScriptBlock { [xml](Get-WinEvent -FilterXml $using:EventQueryXML).toXML() } @Params foreach ($XMLevent in $events) { [pscustomObject]@{ DC = $DC SamAccountName = $xmlevent.Event.EventData.Data.Where( { $_.Name -eq 'TargetUserName' }).'#text' TimeCreated = [datetime]::Parse( $xmlevent.event.system.TimeCreated.SystemTime) EventRecordID = $xmlevent.event.system.EventRecordID Origination = $xmlevent.Event.EventData.Data.Where( { $_.Name -eq 'TargetDomainname' }).'#text' -replace '\\', '' Remarks = '' } } } Catch { [pscustomObject]@{ DC = $DC SamAccountName = '' TimeCreated = '' EventRecordID = '' Origination = '' Remarks = $error[0].exception.message } } }) Write-Progress "Data collected." -Completed $SLEvents | Out-GridView -Title 'Selected lockout events are sent to your clipboard' -PassThru | Out-String | Set-Clipboard