Public/Invoke-ADSecurityAudit.ps1

function Invoke-ADSecurityAudit {
    <#
    .SYNOPSIS
        Runs a comprehensive Active Directory security audit and generates an HTML report.
 
    .DESCRIPTION
        Executes all audit checks and consolidates results into a single HTML dashboard:
        - Stale user and computer accounts
        - Local administrator audit across domain computers
        - Orphaned SIDs in ACLs
        - Privileged group membership review
        - Password policy compliance
 
        Designed to support compliance frameworks (SOC2, HIPAA, NIST) by providing
        auditable evidence of AD security posture.
 
    .PARAMETER OutputPath
        Directory to save the HTML report. Defaults to .\Reports.
 
    .PARAMETER DaysInactive
        Threshold for flagging stale accounts. Defaults to 90 days.
 
    .PARAMETER ComputerSearchBase
        OU to scope the local admin audit. Defaults to entire domain.
 
    .PARAMETER SkipLocalAdminAudit
        Skip the local admin check (useful if network scanning is slow or restricted).
 
    .PARAMETER SkipOrphanedSIDs
        Skip orphaned SID scanning.
 
    .PARAMETER SharePaths
        One or more UNC paths to scan for orphaned SIDs. If not specified, orphaned
        SID check is skipped unless specific paths are provided.
 
    .EXAMPLE
        Invoke-ADSecurityAudit
 
        Runs all checks with defaults and saves the report to .\Reports.
 
    .EXAMPLE
        Invoke-ADSecurityAudit -DaysInactive 60 -SkipLocalAdminAudit -OutputPath "\\server\audits"
 
        Runs stale account and privileged group checks only, with a 60-day threshold.
 
    .EXAMPLE
        Invoke-ADSecurityAudit -SharePaths "\\fileserver\shared","\\fileserver\departments"
 
        Includes orphaned SID scanning on the specified share paths.
 
    .NOTES
        Requires: ActiveDirectory module, appropriate permissions.
        Local admin audit requires network access to target computers.
    #>

    [CmdletBinding()]
    param(
        [string]$OutputPath = '.\Reports',

        [int]$DaysInactive = 90,

        [string]$ComputerSearchBase,

        [switch]$SkipLocalAdminAudit,

        [switch]$SkipOrphanedSIDs,

        [string[]]$SharePaths,

        [string]$LogPath = '.\Logs'
    )

    begin {
        Import-Module ActiveDirectory -ErrorAction Stop

        foreach ($dir in @($OutputPath, $LogPath)) {
            if (-not (Test-Path $dir)) {
                New-Item -Path $dir -ItemType Directory -Force | Out-Null
            }
        }

        $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
        $logFile = Join-Path $LogPath "SecurityAudit-$timestamp.log"
        Start-Transcript -Path $logFile -Append

        $auditResults = @{}
        $domain = (Get-ADDomain).DNSRoot
    }

    process {
        Write-Verbose "=== AD Security Audit: $domain ==="

        # 1. Stale Accounts
        Write-Verbose "Running stale account check..."
        $staleResults = Get-StaleADObjects -DaysInactive $DaysInactive
        $auditResults['StaleUsers']     = @($staleResults | Where-Object ObjectClass -eq 'user')
        $auditResults['StaleComputers'] = @($staleResults | Where-Object ObjectClass -eq 'computer')

        # 2. Privileged Groups
        Write-Verbose "Running privileged group audit..."
        $auditResults['PrivilegedGroups'] = @(Get-PrivilegedGroupReport)

        # 3. Local Admin Audit (optional)
        if (-not $SkipLocalAdminAudit) {
            Write-Verbose "Running local admin audit (this may take a while)..."
            $localAdminParams = @{}
            if ($ComputerSearchBase) { $localAdminParams['SearchBase'] = $ComputerSearchBase }
            $auditResults['LocalAdmins'] = @(Get-LocalAdminAudit @localAdminParams)
        }

        # 4. Orphaned SIDs (optional)
        if (-not $SkipOrphanedSIDs -and $SharePaths) {
            Write-Verbose "Scanning for orphaned SIDs..."
            $auditResults['OrphanedSIDs'] = @(Get-OrphanedSIDs -Path $SharePaths)
        }

        # 5. Password Policy Summary
        Write-Verbose "Checking password policy..."
        $auditResults['PasswordPolicy'] = Get-ADDefaultDomainPasswordPolicy

        # Generate HTML Report
        $htmlFile = Join-Path $OutputPath "AD-SecurityAudit-$domain-$timestamp.html"
        $htmlContent = _New-SecurityAuditHtml -AuditResults $auditResults -Domain $domain -DaysInactive $DaysInactive
        $htmlContent | Out-File -FilePath $htmlFile -Encoding UTF8
        Write-Verbose "Report saved: $htmlFile"

        # Output summary to pipeline
        [PSCustomObject]@{
            Domain            = $domain
            AuditDate         = Get-Date -Format 'yyyy-MM-dd HH:mm'
            StaleUsers        = $auditResults['StaleUsers'].Count
            StaleComputers    = $auditResults['StaleComputers'].Count
            PrivilegedMembers = ($auditResults['PrivilegedGroups'] | Measure-Object -Property MemberCount -Sum).Sum
            LocalAdmins       = if ($auditResults['LocalAdmins']) { $auditResults['LocalAdmins'].Count } else { 'Skipped' }
            OrphanedSIDs      = if ($auditResults['OrphanedSIDs']) { $auditResults['OrphanedSIDs'].Count } else { 'Skipped' }
            ReportPath        = $htmlFile
        }
    }

    end {
        Stop-Transcript
    }
}