Public/Get-AccountAlerts.ps1

function Get-AccountAlerts {
    <#
    .SYNOPSIS
        Checks Active Directory for account-related issues that need attention.
 
    .DESCRIPTION
        Scans AD for locked-out accounts, passwords expiring soon, accounts that have
        expired, recently disabled accounts, and newly created accounts. Returns a
        uniform array of alert objects consumed by Invoke-MorningBrief.
 
    .PARAMETER DaysPasswordExpiry
        Warn when a user's password expires within this many days. Default 14.
 
    .PARAMETER SearchBase
        Optional OU distinguished name to limit the search scope.
 
    .EXAMPLE
        Get-AccountAlerts -DaysPasswordExpiry 7
        Returns alerts for passwords expiring in the next 7 days plus all other account checks.
 
    .OUTPUTS
        PSCustomObject[] - each with AlertType, Priority, Source, AffectedObject, Detail,
        Timestamp, Category, ColorCode, SortOrder.
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateRange(1, 365)]
        [int]$DaysPasswordExpiry = 14,

        [Parameter()]
        [string]$SearchBase
    )

    $alerts = [System.Collections.Generic.List[PSCustomObject]]::new()
    $now    = Get-Date

    # ── Common splatting for Search-ADAccount / Get-ADUser ────────────
    $searchParams = @{}
    if ($SearchBase) { $searchParams['SearchBase'] = $SearchBase }

    # ────────────────────────────────────────────────────────────────────
    # 1. Locked-out accounts (CRITICAL)
    # ────────────────────────────────────────────────────────────────────
    try {
        $lockedOut = Search-ADAccount -LockedOut @searchParams |
            Get-ADUser -Properties LockedOut, lockoutTime, SamAccountName, DisplayName |
            Where-Object { $_.LockedOut -eq $true }

        foreach ($user in $lockedOut) {
            $lockTime = if ($user.lockoutTime) {
                [datetime]::FromFileTime($user.lockoutTime)
            } else { $now }

            $prio = Get-AlertPriority -AlertType 'LockedAccount'
            $alerts.Add([PSCustomObject]@{
                AlertType      = 'LockedAccount'
                Priority       = $prio.Priority
                Source         = 'ActiveDirectory'
                AffectedObject = "$($user.DisplayName) ($($user.SamAccountName))"
                Detail         = "Account locked out at $($lockTime.ToString('yyyy-MM-dd HH:mm'))"
                Timestamp      = $lockTime
                Category       = 'Account'
                ColorCode      = $prio.ColorCode
                SortOrder      = $prio.SortOrder
            })
        }
    }
    catch {
        Write-Warning "AccountAlerts: Failed to query locked accounts - $_"
    }

    # ────────────────────────────────────────────────────────────────────
    # 2. Passwords expiring within N days (HIGH / MEDIUM)
    # ────────────────────────────────────────────────────────────────────
    try {
        # Get domain maximum password age
        $domainPolicy   = Get-ADDefaultDomainPasswordPolicy
        $maxPasswordAge = $domainPolicy.MaxPasswordAge

        if ($maxPasswordAge -and $maxPasswordAge -ne [TimeSpan]::Zero) {
            $users = Get-ADUser -Filter {
                Enabled -eq $true -and PasswordNeverExpires -eq $false
            } -Properties PasswordLastSet, DisplayName, SamAccountName @searchParams

            foreach ($user in $users) {
                if (-not $user.PasswordLastSet) { continue }

                $expiryDate    = $user.PasswordLastSet + $maxPasswordAge
                $daysRemaining = ($expiryDate - $now).Days

                if ($daysRemaining -le $DaysPasswordExpiry -and $daysRemaining -ge 0) {
                    $prio = Get-AlertPriority -AlertType 'PasswordExpiring' -Detail @{ DaysUntilExpiry = $daysRemaining }
                    $alerts.Add([PSCustomObject]@{
                        AlertType      = 'PasswordExpiring'
                        Priority       = $prio.Priority
                        Source         = 'ActiveDirectory'
                        AffectedObject = "$($user.DisplayName) ($($user.SamAccountName))"
                        Detail         = "Password expires in $daysRemaining day(s) on $($expiryDate.ToString('yyyy-MM-dd'))"
                        Timestamp      = $now
                        Category       = 'Account'
                        ColorCode      = $prio.ColorCode
                        SortOrder      = $prio.SortOrder
                    })
                }
            }
        }
    }
    catch {
        Write-Warning "AccountAlerts: Failed to check password expiry - $_"
    }

    # ────────────────────────────────────────────────────────────────────
    # 3. Expired accounts (account expiration date has passed) (HIGH)
    # ────────────────────────────────────────────────────────────────────
    try {
        $expired = Search-ADAccount -AccountExpired @searchParams |
            Get-ADUser -Properties AccountExpirationDate, DisplayName, SamAccountName

        foreach ($user in $expired) {
            $prio = Get-AlertPriority -AlertType 'AccountExpired'
            $alerts.Add([PSCustomObject]@{
                AlertType      = 'AccountExpired'
                Priority       = $prio.Priority
                Source         = 'ActiveDirectory'
                AffectedObject = "$($user.DisplayName) ($($user.SamAccountName))"
                Detail         = "Account expired on $($user.AccountExpirationDate.ToString('yyyy-MM-dd'))"
                Timestamp      = $user.AccountExpirationDate
                Category       = 'Account'
                ColorCode      = $prio.ColorCode
                SortOrder      = $prio.SortOrder
            })
        }
    }
    catch {
        Write-Warning "AccountAlerts: Failed to check expired accounts - $_"
    }

    # ────────────────────────────────────────────────────────────────────
    # 4. Recently disabled accounts (last 24h) (LOW - informational)
    # ────────────────────────────────────────────────────────────────────
    try {
        $yesterday = $now.AddDays(-1)
        $disabled  = Get-ADUser -Filter { Enabled -eq $false } `
            -Properties whenChanged, DisplayName, SamAccountName @searchParams |
            Where-Object { $_.whenChanged -ge $yesterday }

        foreach ($user in $disabled) {
            $prio = Get-AlertPriority -AlertType 'AccountDisabled'
            $alerts.Add([PSCustomObject]@{
                AlertType      = 'AccountDisabled'
                Priority       = $prio.Priority
                Source         = 'ActiveDirectory'
                AffectedObject = "$($user.DisplayName) ($($user.SamAccountName))"
                Detail         = "Account disabled at $($user.whenChanged.ToString('yyyy-MM-dd HH:mm'))"
                Timestamp      = $user.whenChanged
                Category       = 'Account'
                ColorCode      = $prio.ColorCode
                SortOrder      = $prio.SortOrder
            })
        }
    }
    catch {
        Write-Warning "AccountAlerts: Failed to check disabled accounts - $_"
    }

    # ────────────────────────────────────────────────────────────────────
    # 5. New accounts created in last 24h (LOW - informational)
    # ────────────────────────────────────────────────────────────────────
    try {
        $yesterday  = $now.AddDays(-1)
        $newUsers   = Get-ADUser -Filter { whenCreated -ge $yesterday } `
            -Properties whenCreated, DisplayName, SamAccountName @searchParams

        foreach ($user in $newUsers) {
            $prio = Get-AlertPriority -AlertType 'AccountCreated'
            $alerts.Add([PSCustomObject]@{
                AlertType      = 'AccountCreated'
                Priority       = $prio.Priority
                Source         = 'ActiveDirectory'
                AffectedObject = "$($user.DisplayName) ($($user.SamAccountName))"
                Detail         = "Account created at $($user.whenCreated.ToString('yyyy-MM-dd HH:mm'))"
                Timestamp      = $user.whenCreated
                Category       = 'Account'
                ColorCode      = $prio.ColorCode
                SortOrder      = $prio.SortOrder
            })
        }
    }
    catch {
        Write-Warning "AccountAlerts: Failed to check new accounts - $_"
    }

    return , $alerts.ToArray()
}