Public/Find-PSUADServiceAccountMisuse.ps1

function Find-PSUADServiceAccountMisuse {
    <#
    .SYNOPSIS
        Identifies potential misuse of service accounts by detecting interactive logon events in AD.
 
    .DESCRIPTION
        This function searches for Active Directory user accounts that resemble service accounts (e.g., contain "svc", "sql", etc.),
        and checks if these accounts have interactive logon events (Event ID 4624 with LogonType 2, 10, or 11) over a user-defined period.
        It flags accounts that may indicate risk and outputs results optionally to the console and/or CSV file.
 
        Returns an array of psobject with properties: SamAccountName, LogonCount, RiskScore, RiskLevel, LastLogon, Description
 
    .PARAMETER DaysBack
        Number of days to look back in security event logs. Default is 7.
 
    .PARAMETER Credential
        Optional. Use alternate credentials for AD and event log queries.
 
    .PARAMETER Detailed
        Switch. If specified, returns detailed login events.
 
    .PARAMETER ExportPath
        Optional path to export results to CSV.
 
    .PARAMETER IncludeBuiltin
        Switch. If specified, includes built-in service accounts like "LOCAL SERVICE", "NETWORK SERVICE" etc.
 
    .PARAMETER Filter
        Optional string filter to apply to user names (e.g., '*svc').
 
    .PARAMETER Server
        Optional if you running the function on the domain controler itself. or else pass the AD server or DC.
    .OUTPUTS
        PSCustomObject[]
 
    .EXAMPLE
        Find-PSUADServiceAccountMisuse -DaysBack 14 -Detailed -ExportPath "ADServiceAccountMisuse.csv"
 
    .NOTES
        Author : Lakshmanachari Panuganti
        Date: 29 June 2025
        TODO: Add the server names where its used.
    #>


    [CmdletBinding()]
    param (
        [int]$DaysBack = 7,
        [PSCredential]$Credential,
        [switch]$Detailed,
        [string]$ExportPath,
        [switch]$IncludeBuiltin,
        [string]$Filter = '*',
        [string]$Server = $env:COMPUTERNAME # Should be a domain controler
    )

    Write-Host "[+] Starting AD service account misuse detection..." -ForegroundColor Cyan
    $StartDate = (Get-Date).AddDays(-$DaysBack)
    $EndDate = Get-Date

    $SearchFilter = { Name -like $Filter }
    $ADUsers = if ($Credential) {
        Get-ADUser -Filter $SearchFilter -Credential $Credential -Properties *
    } else {
        Get-ADUser -Filter $SearchFilter -Properties *
    }

    $ServiceAccountPatterns = @('svc', 'sql', 'ora', 'ftp', 'backup', 'sa_', '_svc', 'report')
    $BuiltInAccounts = @('LOCAL SERVICE', 'NETWORK SERVICE', 'SYSTEM')
    $InteractiveLogonTypes = @(2, 10, 11)

    Write-Progress -Activity "Collecting Event Logs..." -Status "Scanning event logs for logon activity..."
    $AllLogonEvents = Get-WinEvent -ComputerName $Server -Credential $Credential -FilterHashtable @{
        LogName = 'Security'; ID = 4624; StartTime = $StartDate; EndTime = $EndDate
    } -ErrorAction SilentlyContinue | Where-Object {
        $_.Properties[8].Value -in $InteractiveLogonTypes
    }

    $Results = @()

    foreach ($Account in $ADUsers) {
        $Username = $Account.SamAccountName

        if (-not $IncludeBuiltin -and ($BuiltInAccounts -contains $Username)) {
            continue
        }

        if (-not ($ServiceAccountPatterns | Where-Object { $Username -like "*$_*" })) {
            continue
        }

        $UserEvents = $AllLogonEvents | Where-Object {
            $_.Properties[5].Value -eq $Username
        }

        $LogonCount = $UserEvents.Count
        $RiskScore = switch ($LogonCount) {
            { $_ -ge 10 } { 10 }
            { $_ -ge 5 }  { 5 }
            { $_ -ge 1 }  { 3 }
            default       { 0 }
        }

        $RiskLevel = switch ($RiskScore) {
            { $_ -ge 10 } { "High" }
            { $_ -ge 5 }  { "Medium" }
            { $_ -ge 1 }  { "Low" }
            default       { "None" }
        }

        $Object = [PSCustomObject]@{
            SamAccountName     = $Username
            DisplayName        = $Account.DisplayName
            Enabled            = $Account.Enabled
            LogonCount         = $LogonCount
            RiskScore          = $RiskScore
            RiskLevel          = $RiskLevel
            LastLogonDate      = $Account.LastLogonDate
            Description        = $Account.Description
            DistinguishedName  = $Account.DistinguishedName
            DetailedLoginEvents = if ($Detailed) { $UserEvents } else { $null }
        }

        $Results += $Object
    }

    Write-Host "[+] Analyzed $($Results.Count) accounts" -ForegroundColor Green
    $Results | Format-Table SamAccountName, LogonCount, RiskLevel, LastLogonDate -AutoSize

    if ($ExportPath) {
        $Results | Select-Object * -ExcludeProperty DetailedLoginEvents | Export-PSUExcel -ExcelPath "$ExportPath\ADServiceAccountMisuse.xlsx"
        $Results | ConvertTo-Json -Depth 50 | Out-File -FilePath "$ExportPath\ADServiceAccountMisuse.Json"
        Write-Host "[+] Results exported to $ExportPath" -ForegroundColor Yellow
        Write-Host " [+] \ADServiceAccountMisuse.xlsx" -ForegroundColor Yellow
        Write-Host " [+] \ADServiceAccountMisuse.Json" -ForegroundColor Yellow
    }

    return $Results
}