Public/Get-Lockouts.ps1

Function Get-Lockouts {
    <#
.SYNOPSIS
    Pipe in distinguishedName or
    Queries AD for all Domain Controllers
    Queries the list of DCs to find lockout sources with bad password counts greater then 5
    Runs Get-WinEvent with a custom XML formatted around the provided usernames
    Outputs all lockout events with relevant source information
     
.NOTES
    Name: Get-LockoutEvents
    Author: Luke Hagar
    Version: 1.0
    DateCreated: January 20th, 2021
   
   
.EXAMPLE
Single Search
 
    Get-LockoutEvents "Luke.Hagar"
    Get-LockoutEvents "Luke.Hagar" -Verbose
 
Array Search
    $LockedOutUsers = {"SamAccountName","EmployeeID","mail"}
    $LockedOutUsers | Get-LockoutEvents | Format-Table -AutoSize -GroupBy EventDataTargetUserName -Unique
     
Search All Locked out AD User Objects
    $LockedOutUsers = Search-AdAccount -LockedOut
    $LockedOutUsers | Get-LockoutEvents | Format-Table -AutoSize -GroupBy EventDataTargetUserName -Unique
     
.LINK
      
#>

    
    [CmdletBinding()]
    param (
        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$distinguishedName
    )
    
    BEGIN {
        #get list of Domain Controllers
        $DomainControllers = Get-ADDomainController -Filter *
        
        #XML part 1 to be joined later around username variable
        $LastHourXMLPart1 = "<QueryList>
        <Query Id='0' Path='Security'>
        <Select Path='Security'>*[System[Provider[@Name='Microsoft-Windows-Security-Auditing']
        and (Level=4 or Level=0)
        and (band(Keywords, 13510798882111488))
        and (EventID=4740)
        and TimeCreated[timediff(@SystemTime) &lt; = 604800000]]]
        and *[EventData[Data[@Name='TargetUserName'] = '"

        
        #XML part 2 to be joined later around username variable
        $LastHourXMLPart2 = "']]
        </Select>
        </Query>
        </QueryList>"

        
    }
    
    PROCESS {
        Try { $User = Get-User $distinguishedName -Properties lockedout }
        Catch { Throw $_.Exception.Message }
        
        If ($User.lockedout -eq $true) {
            $TestingList = @()

            Write-Verbose "$($User.Name) found"
            #Merge Queries together around User Search variable
            [xml]$FinalQuery = "$LastHourXMLPart1" + $User.SamAccountName + "$LastHourXMLPart2"
        
            If ($User.lockout -eq $True) {
                #Test each DCs to find sources
                Foreach ($DC in $DomainControllers) {
                    Write-Verbose "Checking $($DC.Hostname.ToUpper())"
                    $DCQuery = Get-ADUser -Identity $User.SamAccountName -Server $DC.Hostname -Properties BadPwdCount
                    If ($DCQuery.BadPwdCount -gt 5) {
                        Write-Verbose "$($DCQuery.BadPwdCount) Bad Passwords found on $($DC.Hostname.ToUpper())"
                        $TestingList += $DC.Hostname
                    }
                }
                if ($null -ne $TestingList) {
                    Foreach ($TestingDC in $TestingList) {
                        Write-Verbose ""
                        Write-Verbose "Checking $($TestingDC.ToUpper()) for lockout events"
                        Get-WinEvent -ComputerName $TestingDC -FilterXml $FinalQuery -ErrorAction SilentlyContinue | Get-WinEventData | Select-Object TimeCreated, EventDataTargetUserName, EventDataTargetDomainName, EventDataSubjectUserName
                    }
                }
            
            }
            else {
                Write-Verbose "No Lockout Events found"
                return [PSCustomObject]@{
                    Username = $User.SamAccountName
                    Results  = "No Lockout Found / No Bad Passwords Found"
                }
            }
        }
    }
    
    END { }
}