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
        (Optional) Number of days to look back in security event logs.
        Default value is 7.
 
    .PARAMETER Credential
        (Optional) Use alternate credentials for AD and event log queries.
 
    .PARAMETER Detailed
        (Optional) Switch parameter to return detailed login events.
 
    .PARAMETER ExportPath
        (Optional) Path to export results to CSV file.
 
    .PARAMETER IncludeBuiltin
        (Optional) Switch parameter to include built-in service accounts like "LOCAL SERVICE", "NETWORK SERVICE".
 
    .PARAMETER Filter
        (Optional) String filter to apply to user names (e.g., '*svc').
        Default value is '*'.
 
    .PARAMETER Server
        (Optional) The AD server or domain controller to query.
        Default value is $env:COMPUTERNAME. Should be a domain controller.
    .OUTPUTS
        PSCustomObject[]
 
    .EXAMPLE
        Find-PSUADServiceAccountMisuse -DaysBack 14 -Detailed -ExportPath "ADServiceAccountMisuse.csv"
 
    .NOTES
        Author : Lakshmanachari Panuganti
        Date: 29th June 2025
        TODO: Add the server names where its used.
 
    .LINK
        https://github.com/lakshmanachari-panuganti/OMG.PSUtilities/tree/main/OMG.PSUtilities.ActiveDirectory
        https://www.linkedin.com/in/lakshmanachari-panuganti/
        https://www.powershellgallery.com/packages/OMG.PSUtilities.ActiveDirectory
        https://learn.microsoft.com/en-us/powershell/module/activedirectory/
    #>


    [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
}