Public/Get-Lockouts.ps1

Function Get-Lockouts {
    <#
.SYNOPSIS
    Pipe in Search Term or User Object
    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-Lockouts
    Author: Luke Hagar
    Version: 1.0
    DateCreated: January 20th, 2021
     
.EXAMPLE
Single Search
    Get-Lockouts "Luke.Hagar"
    Get-Lockouts "Luke.Hagar" -Verbose
 
.EXAMPLE
Array Search
    $Users = {"SamAccountName","EmployeeID","mail"}
    $Users | Get-Lockouts | Format-Table -AutoSize -GroupBy EventDataTargetUserName -Unique
 
.EXAMPLE
Search All Locked out AD User Objects
    $Users = Search-AdAccount -LockedOut
    $Users | Get-Lockouts | Format-Table -AutoSize -GroupBy EventDataTargetUserName -Unique
 
.LINK
 
#>

    
    [CmdletBinding()]
    param (
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [Alias("SamAccountName", "DistinguishedName", "GUID", "SID", "EmployeeID", "mail", "UserPrincipalName")]
        $Identity = $ENV:USERNAME,
        [string[]]$Properties = @("Name", "EmployeeID", "physicalDeliveryOfficeName", "Title", "mail", "msRTCSIP-PrimaryUserAddress", "CanonicalName", "DistinguishedName", "samaccountname", "UserPrincipalname", "AccountExpirationDate", "Enabled", "Manager", "badPwdCount", "LastBadPasswordAttempt", "LockedOut", "LockOutTime", "lastLogonDate", "PasswordExpired", "PasswordLastSet", "whenCreated", "whenChanged"),
        [string[]]$Select = @("Name", "EmployeeID", "physicalDeliveryOfficeName", "Title", "mail", "msRTCSIP-PrimaryUserAddress", "CanonicalName", "DistinguishedName", "samaccountname", "UserPrincipalname", "AccountExpirationDate", "Enabled", "Manager", "badPwdCount", "LastBadPasswordAttempt", "LockedOut", "LockOutTime", "lastLogonDate", "PasswordExpired", "PasswordLastSet", "whenCreated", "whenChanged")
    )
    
    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 {

        #Find user account to validate input data
        Try { $Users = Get-ADUser -Filter "employeeid -eq '$Identity' -or SamAccountName -eq '$Identity' -or DistinguishedName -eq '$Identity' -or GUID -eq '$Identity' -or SID -eq '$Identity' -or EmployeeID -eq '$Identity' -or mail -eq '$Identity' -or UserPrincipalName -eq '$Identity'" -Properties $Properties | Select-Object $Select }
        Catch { Write-Warning $_.Exception.Message }

        #Create results array
        $Results = @()

        Foreach ($User in $Users) {
            Write-Verbose "$($User.Name) found"
            If ($User.lockedout -eq $true) {

                $LockoutSources = @()

                #output found account name for visibility
                Write-Verbose "$($User.Name)($($User.SamAccountName)) is LockedOut, Searching for source DCs"

                #Merge Queries together around User Search variable
                [xml]$FinalQuery = "$LastHourXMLPart1" + $User.SamAccountName + "$LastHourXMLPart2"
        
                #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())"
                        $LockoutSources += $DC.Hostname
                    }
                }
                if ($LockoutSources.Count -gt 0) {
                    Foreach ($DC in $LockoutSources) {
                        Write-Verbose "Checking $($DC.ToUpper()) for lockout events"
                        $Temp = Get-WinEvent -ComputerName $DC -FilterXml $FinalQuery -ErrorAction SilentlyContinue | Get-WinEventData | Select-Object TimeCreated, EventDataTargetUserName, EventDataTargetDomainName, EventDataSubjectUserName
                        $Results += [PSCustomObject]@{
                            SamAccountName            = $User.SamAccountName
                            DC                        = $DC.Hostname.ToUpper()
                            TimeCreated               = $Temp.TimeCreated
                            EventDataTargetUserName   = $Temp.EventDataTargetUserName
                            EventDataTargetDomainName = $Temp.EventDataTargetDomainName
                            EventDataSubjectUserName  = $Temp.EventDataSubjectUserName
                            Result                    = "Success"
                        }
                    }
                }
                else {
                    Write-Verbose "$($User.Name) No Lockout Sources found"
                }
            }
            else {
                Write-Verbose "$($User.Name) is not locked out"
            }
        }
        Return $Results
    }
    
    END { }
}