<#
.SYNOPSIS
    Windows Security Baseline Audit Script
.DESCRIPTION
    Audits a Windows Server or Workstation against Microsoft Security Baselines
    and CIS Benchmarks. Checks account policies, audit policies, user rights,
    security options, firewall configuration, Windows Update, SMB hardening,
    TLS settings, and service configuration. Produces a summary report with
    pass/fail/warning counts and an HTML report.
.PARAMETER OutputPath
    Path for the HTML report (default: C:\SecurityAudit\baseline-audit.html)
.PARAMETER Format
    Output format: 'html' (default), 'csv', or 'json'
.PARAMETER ChecksFile
    Path to a custom checks JSON file (optional)
.NOTES
    Author:  Phil Connor
    Contact: contact@mylinux.work
    Website: https://mylinux.work
    License: MIT
    Version: 1.0
#>

param(
    [string]$OutputPath = "C:\SecurityAudit\baseline-audit-$(Get-Date -Format 'yyyyMMdd-HHmmss').html",

    [ValidateSet('html', 'csv', 'json')]
    [string]$Format = 'html',

    [string]$ChecksFile
)

$ErrorActionPreference = 'SilentlyContinue'

# ============================================================================
# RESULTS COLLECTION
# ============================================================================

$script:Results = [System.Collections.ArrayList]::new()
$script:PassCount = 0
$script:FailCount = 0
$script:WarnCount = 0
$script:InfoCount = 0

function Add-AuditResult {
    param(
        [string]$Category,
        [string]$Check,
        [ValidateSet('Pass', 'Fail', 'Warn', 'Info')]
        [string]$Status,
        [string]$Expected,
        [string]$Actual,
        [string]$Description
    )

    switch ($Status) {
        'Pass' { $script:PassCount++ }
        'Fail' { $script:FailCount++ }
        'Warn' { $script:WarnCount++ }
        'Info' { $script:InfoCount++ }
    }

    [void]$script:Results.Add([PSCustomObject]@{
        Category    = $Category
        Check       = $Check
        Status      = $Status
        Expected    = $Expected
        Actual      = $Actual
        Description = $Description
    })
}

# ============================================================================
# ACCOUNT POLICIES
# ============================================================================

function Test-AccountPolicies {
    Write-Host "Checking account policies..." -ForegroundColor Cyan

    # Export security policy
    $tempFile = "$env:TEMP\secpol_export.cfg"
    secedit /export /cfg $tempFile /quiet 2>$null

    if (-not (Test-Path $tempFile)) {
        Add-AuditResult "Account Policies" "Security Policy Export" "Fail" "Exported" "Failed" "Cannot export security policy"
        return
    }

    $secpol = Get-Content $tempFile

    # Password length
    $minLen = ($secpol | Select-String 'MinimumPasswordLength\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
    $expected = 14
    if ($minLen -ge $expected) {
        Add-AuditResult "Account Policies" "Minimum Password Length" "Pass" ">= $expected" "$minLen" "CIS: Minimum password length"
    } else {
        Add-AuditResult "Account Policies" "Minimum Password Length" "Fail" ">= $expected" "$minLen" "CIS: Minimum password length should be $expected+"
    }

    # Password history
    $history = ($secpol | Select-String 'PasswordHistorySize\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
    if ($history -ge 24) {
        Add-AuditResult "Account Policies" "Password History" "Pass" ">= 24" "$history" "CIS: Enforce password history"
    } else {
        Add-AuditResult "Account Policies" "Password History" "Fail" ">= 24" "$history" "CIS: Should remember 24+ passwords"
    }

    # Maximum password age
    $maxAge = ($secpol | Select-String 'MaximumPasswordAge\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
    if ($maxAge -le 365 -and $maxAge -gt 0) {
        Add-AuditResult "Account Policies" "Maximum Password Age" "Pass" "<= 365 days" "$maxAge days" "CIS: Maximum password age"
    } else {
        Add-AuditResult "Account Policies" "Maximum Password Age" "Fail" "<= 365 days" "$maxAge days" "Password age too high or unlimited"
    }

    # Account lockout threshold
    $lockout = ($secpol | Select-String 'LockoutBadCount\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
    if ($lockout -ge 1 -and $lockout -le 5) {
        Add-AuditResult "Account Policies" "Account Lockout Threshold" "Pass" "1-5 attempts" "$lockout" "CIS: Account lockout threshold"
    } else {
        Add-AuditResult "Account Policies" "Account Lockout Threshold" "Fail" "1-5 attempts" "$lockout" "Should lock after 1-5 failed attempts"
    }

    # Lockout duration
    $lockDuration = ($secpol | Select-String 'LockoutDuration\s*=\s*(\d+)' | ForEach-Object { $_.Matches.Groups[1].Value }) -as [int]
    if ($lockDuration -ge 15) {
        Add-AuditResult "Account Policies" "Account Lockout Duration" "Pass" ">= 15 min" "$lockDuration min" "CIS: Lockout duration"
    } else {
        Add-AuditResult "Account Policies" "Account Lockout Duration" "Fail" ">= 15 min" "$lockDuration min" "Should be 15+ minutes"
    }

    # Password complexity
    $complexity = $secpol | Select-String 'PasswordComplexity\s*=\s*1'
    if ($complexity) {
        Add-AuditResult "Account Policies" "Password Complexity" "Pass" "Enabled" "Enabled" "CIS: Password must meet complexity requirements"
    } else {
        Add-AuditResult "Account Policies" "Password Complexity" "Fail" "Enabled" "Disabled" "Enable password complexity"
    }

    Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}

# ============================================================================
# AUDIT POLICIES
# ============================================================================

function Test-AuditPolicies {
    Write-Host "Checking audit policies..." -ForegroundColor Cyan

    $auditPolicies = @{
        "Credential Validation"          = "Success and Failure"
        "Logon"                          = "Success and Failure"
        "Logoff"                         = "Success"
        "Account Lockout"                = "Failure"
        "User Account Management"        = "Success and Failure"
        "Security Group Management"      = "Success"
        "Process Creation"               = "Success"
        "Audit Policy Change"            = "Success and Failure"
        "Authentication Policy Change"   = "Success"
        "Sensitive Privilege Use"        = "Success and Failure"
        "System Integrity"               = "Success and Failure"
        "Security State Change"          = "Success"
    }

    $auditpolOutput = auditpol /get /category:* 2>$null

    foreach ($policy in $auditPolicies.GetEnumerator()) {
        $line = $auditpolOutput | Select-String $policy.Key | Select-Object -First 1
        if ($line) {
            $currentSetting = $line.ToString().Trim()
            if ($currentSetting -match $policy.Value -or $currentSetting -match "Success and Failure") {
                Add-AuditResult "Audit Policies" $policy.Key "Pass" $policy.Value "Configured" "Advanced audit policy"
            } else {
                Add-AuditResult "Audit Policies" $policy.Key "Fail" $policy.Value "Not configured" "Enable in Advanced Audit Policy"
            }
        } else {
            Add-AuditResult "Audit Policies" $policy.Key "Warn" $policy.Value "Not found" "Cannot determine audit policy status"
        }
    }
}

# ============================================================================
# FIREWALL
# ============================================================================

function Test-FirewallConfiguration {
    Write-Host "Checking firewall configuration..." -ForegroundColor Cyan

    $profiles = @('Domain', 'Private', 'Public')
    foreach ($profile in $profiles) {
        $fw = Get-NetFirewallProfile -Name $profile -ErrorAction SilentlyContinue
        if ($fw) {
            if ($fw.Enabled) {
                Add-AuditResult "Firewall" "$profile Profile Enabled" "Pass" "Enabled" "Enabled" "Windows Firewall $profile profile"
            } else {
                Add-AuditResult "Firewall" "$profile Profile Enabled" "Fail" "Enabled" "Disabled" "Enable Windows Firewall for $profile"
            }

            if ($fw.DefaultInboundAction -eq 'Block') {
                Add-AuditResult "Firewall" "$profile Inbound Default" "Pass" "Block" "Block" "Default inbound action"
            } else {
                Add-AuditResult "Firewall" "$profile Inbound Default" "Fail" "Block" "$($fw.DefaultInboundAction)" "Set default inbound to Block"
            }

            if ($fw.LogFileName) {
                Add-AuditResult "Firewall" "$profile Logging" "Pass" "Enabled" $fw.LogFileName "Firewall logging path"
            } else {
                Add-AuditResult "Firewall" "$profile Logging" "Warn" "Enabled" "Not configured" "Enable firewall logging"
            }
        }
    }
}

# ============================================================================
# SMB HARDENING
# ============================================================================

function Test-SMBHardening {
    Write-Host "Checking SMB hardening..." -ForegroundColor Cyan

    # SMBv1
    $smb1 = Get-SmbServerConfiguration -ErrorAction SilentlyContinue
    if ($smb1) {
        if (-not $smb1.EnableSMB1Protocol) {
            Add-AuditResult "SMB" "SMBv1 Disabled" "Pass" "Disabled" "Disabled" "SMBv1 should be disabled (security risk)"
        } else {
            Add-AuditResult "SMB" "SMBv1 Disabled" "Fail" "Disabled" "Enabled" "Disable SMBv1: Set-SmbServerConfiguration -EnableSMB1Protocol $false"
        }

        if ($smb1.RequireSecuritySignature) {
            Add-AuditResult "SMB" "SMB Signing Required" "Pass" "Required" "Required" "SMB signing prevents MITM attacks"
        } else {
            Add-AuditResult "SMB" "SMB Signing Required" "Fail" "Required" "Not required" "Enable SMB signing"
        }

        if ($smb1.EncryptData) {
            Add-AuditResult "SMB" "SMB Encryption" "Pass" "Enabled" "Enabled" "SMB 3.0+ encryption"
        } else {
            Add-AuditResult "SMB" "SMB Encryption" "Warn" "Enabled" "Disabled" "Consider enabling SMB encryption"
        }
    }
}

# ============================================================================
# TLS CONFIGURATION
# ============================================================================

function Test-TLSConfiguration {
    Write-Host "Checking TLS configuration..." -ForegroundColor Cyan

    $protocols = @{
        'SSL 2.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server'; Expected = 'Disabled' }
        'SSL 3.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server'; Expected = 'Disabled' }
        'TLS 1.0' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server'; Expected = 'Disabled' }
        'TLS 1.1' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server'; Expected = 'Disabled' }
        'TLS 1.2' = @{ Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server'; Expected = 'Enabled' }
    }

    foreach ($proto in $protocols.GetEnumerator()) {
        $regPath = $proto.Value.Path
        $expected = $proto.Value.Expected
        $enabled = $true

        if (Test-Path $regPath) {
            $val = Get-ItemProperty -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
            if ($val -and $val.Enabled -eq 0) { $enabled = $false }
        }

        if ($expected -eq 'Disabled') {
            if (-not $enabled -or (Test-Path $regPath)) {
                $disabledCheck = Get-ItemProperty -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
                if ($disabledCheck -and $disabledCheck.Enabled -eq 0) {
                    Add-AuditResult "TLS" "$($proto.Key) Disabled" "Pass" "Disabled" "Disabled" "Legacy protocol disabled"
                } else {
                    Add-AuditResult "TLS" "$($proto.Key) Disabled" "Warn" "Disabled" "Not explicitly disabled" "Disable $($proto.Key) via registry"
                }
            } else {
                Add-AuditResult "TLS" "$($proto.Key) Disabled" "Warn" "Disabled" "Registry key not set" "Explicitly disable $($proto.Key)"
            }
        } else {
            Add-AuditResult "TLS" "$($proto.Key) Enabled" "Info" "Enabled" "Default" "TLS 1.2 enabled by default on modern Windows"
        }
    }
}

# ============================================================================
# WINDOWS UPDATE
# ============================================================================

function Test-WindowsUpdate {
    Write-Host "Checking Windows Update status..." -ForegroundColor Cyan

    try {
        $lastUpdate = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1
        if ($lastUpdate) {
            $daysSince = ((Get-Date) - $lastUpdate.InstalledOn).Days
            if ($daysSince -le 30) {
                Add-AuditResult "Windows Update" "Last Update" "Pass" "<= 30 days" "$daysSince days ago" "HotFix: $($lastUpdate.HotFixID)"
            } elseif ($daysSince -le 90) {
                Add-AuditResult "Windows Update" "Last Update" "Warn" "<= 30 days" "$daysSince days ago" "Updates may be overdue"
            } else {
                Add-AuditResult "Windows Update" "Last Update" "Fail" "<= 30 days" "$daysSince days ago" "System needs patching"
            }
        }
    } catch {
        Add-AuditResult "Windows Update" "Last Update" "Warn" "<= 30 days" "Unknown" "Cannot determine update status"
    }
}

# ============================================================================
# SERVICES
# ============================================================================

function Test-ServiceConfiguration {
    Write-Host "Checking service configuration..." -ForegroundColor Cyan

    $riskyServices = @{
        'RemoteRegistry'  = 'Remote Registry - should be disabled'
        'Spooler'         = 'Print Spooler - disable on servers not used for printing'
        'SNMP'            = 'SNMP - legacy protocol, disable if unused'
        'TelnetClient'    = 'Telnet - insecure, use SSH instead'
        'W3SVC'           = 'IIS - disable if not serving web content'
    }

    foreach ($svc in $riskyServices.GetEnumerator()) {
        $service = Get-Service -Name $svc.Key -ErrorAction SilentlyContinue
        if ($service) {
            if ($service.Status -eq 'Running') {
                Add-AuditResult "Services" "$($svc.Key)" "Warn" "Stopped/Disabled" "Running" $svc.Value
            } elseif ($service.StartType -eq 'Automatic') {
                Add-AuditResult "Services" "$($svc.Key)" "Warn" "Disabled" "Auto-start" $svc.Value
            } else {
                Add-AuditResult "Services" "$($svc.Key)" "Pass" "Disabled" "$($service.StartType)" $svc.Value
            }
        }
    }
}

# ============================================================================
# ADDITIONAL CHECKS
# ============================================================================

function Test-AdditionalSecurity {
    Write-Host "Checking additional security settings..." -ForegroundColor Cyan

    # RDP NLA
    $nla = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -ErrorAction SilentlyContinue
    if ($nla -and $nla.UserAuthentication -eq 1) {
        Add-AuditResult "RDP" "Network Level Authentication" "Pass" "Enabled" "Enabled" "NLA required for RDP"
    } else {
        Add-AuditResult "RDP" "Network Level Authentication" "Fail" "Enabled" "Disabled" "Enable NLA for RDP connections"
    }

    # BitLocker
    $bitlocker = Get-BitLockerVolume -MountPoint 'C:' -ErrorAction SilentlyContinue
    if ($bitlocker -and $bitlocker.ProtectionStatus -eq 'On') {
        Add-AuditResult "Encryption" "BitLocker C: Drive" "Pass" "On" "On" "System drive encrypted"
    } else {
        Add-AuditResult "Encryption" "BitLocker C: Drive" "Warn" "On" "Off or N/A" "Consider enabling BitLocker"
    }

    # Windows Defender
    $defender = Get-MpComputerStatus -ErrorAction SilentlyContinue
    if ($defender) {
        if ($defender.RealTimeProtectionEnabled) {
            Add-AuditResult "Defender" "Real-Time Protection" "Pass" "Enabled" "Enabled" "Windows Defender real-time protection"
        } else {
            Add-AuditResult "Defender" "Real-Time Protection" "Fail" "Enabled" "Disabled" "Enable real-time protection"
        }

        $sigAge = $defender.AntivirusSignatureAge
        if ($sigAge -le 3) {
            Add-AuditResult "Defender" "Signature Age" "Pass" "<= 3 days" "$sigAge days" "AV signatures up to date"
        } else {
            Add-AuditResult "Defender" "Signature Age" "Fail" "<= 3 days" "$sigAge days" "Update AV signatures"
        }
    }

    # Guest account
    $guest = Get-LocalUser -Name 'Guest' -ErrorAction SilentlyContinue
    if ($guest -and -not $guest.Enabled) {
        Add-AuditResult "Accounts" "Guest Account Disabled" "Pass" "Disabled" "Disabled" "Guest account should be disabled"
    } elseif ($guest) {
        Add-AuditResult "Accounts" "Guest Account Disabled" "Fail" "Disabled" "Enabled" "Disable the Guest account"
    }

    # Administrator account renamed
    $admin = Get-LocalUser | Where-Object { $_.SID -match 'S-1-5-21-.*-500$' }
    if ($admin -and $admin.Name -ne 'Administrator') {
        Add-AuditResult "Accounts" "Administrator Renamed" "Pass" "Renamed" $admin.Name "Built-in admin account renamed"
    } else {
        Add-AuditResult "Accounts" "Administrator Renamed" "Warn" "Renamed" "Administrator" "Consider renaming the built-in admin account"
    }
}

# ============================================================================
# REPORT GENERATION
# ============================================================================

function New-HTMLReport {
    $total = $script:PassCount + $script:FailCount + $script:WarnCount + $script:InfoCount
    $scorePercent = if (($script:PassCount + $script:FailCount) -gt 0) {
        [math]::Round($script:PassCount / ($script:PassCount + $script:FailCount) * 100, 1)
    } else { 0 }

    $html = @"
<!DOCTYPE html>
<html>
<head>
<title>Windows Security Baseline Audit</title>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background: #1a1a2e; color: #e0e0e0; }
h1 { color: #ff6a00; }
h2 { color: #ffffff; border-bottom: 1px solid #333; padding-bottom: 5px; }
.summary { display: flex; gap: 20px; margin: 20px 0; }
.stat { background: #2a2a3e; padding: 15px 25px; border-radius: 8px; text-align: center; }
.stat .number { font-size: 2em; font-weight: bold; }
.pass { color: #4caf50; }
.fail { color: #f44336; }
.warn { color: #ff9800; }
.info { color: #2196f3; }
table { border-collapse: collapse; width: 100%; margin: 15px 0; }
th, td { padding: 8px 12px; text-align: left; border: 1px solid #333; }
th { background: #2a2a3e; color: #ff6a00; }
tr:nth-child(even) { background: #222233; }
.status-pass { color: #4caf50; font-weight: bold; }
.status-fail { color: #f44336; font-weight: bold; }
.status-warn { color: #ff9800; font-weight: bold; }
.status-info { color: #2196f3; }
.score { font-size: 1.5em; color: #ff6a00; margin: 10px 0; }
</style>
</head>
<body>
<h1>Windows Security Baseline Audit</h1>
<p>Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Host: $env:COMPUTERNAME | OS: $((Get-CimInstance Win32_OperatingSystem).Caption)</p>
<div class="score">Compliance Score: ${scorePercent}%</div>
<div class="summary">
<div class="stat"><div class="number pass">$($script:PassCount)</div>Pass</div>
<div class="stat"><div class="number fail">$($script:FailCount)</div>Fail</div>
<div class="stat"><div class="number warn">$($script:WarnCount)</div>Warn</div>
<div class="stat"><div class="number info">$($script:InfoCount)</div>Info</div>
<div class="stat"><div class="number">$total</div>Total</div>
</div>
"@

    $categories = $script:Results | Group-Object Category
    foreach ($cat in $categories) {
        $html += "<h2>$($cat.Name)</h2>`n<table><tr><th>Check</th><th>Status</th><th>Expected</th><th>Actual</th><th>Description</th></tr>`n"
        foreach ($result in $cat.Group) {
            $statusClass = "status-$($result.Status.ToLower())"
            $html += "<tr><td>$($result.Check)</td><td class='$statusClass'>$($result.Status)</td><td>$($result.Expected)</td><td>$($result.Actual)</td><td>$($result.Description)</td></tr>`n"
        }
        $html += "</table>`n"
    }

    $html += "</body></html>"

    $outputDir = Split-Path $OutputPath -Parent
    if (-not (Test-Path $outputDir)) {
        New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
    }

    $html | Out-File -FilePath $OutputPath -Encoding utf8
}

# ============================================================================
# MAIN
# ============================================================================

Write-Host ""
Write-Host "===== Windows Security Baseline Audit =====" -ForegroundColor Green
Write-Host "Host: $env:COMPUTERNAME" -ForegroundColor Cyan
Write-Host ""

Test-AccountPolicies
Test-AuditPolicies
Test-FirewallConfiguration
Test-SMBHardening
Test-TLSConfiguration
Test-WindowsUpdate
Test-ServiceConfiguration
Test-AdditionalSecurity

# Generate report
switch ($Format) {
    'html' { New-HTMLReport }
    'csv'  {
        $outputDir = Split-Path $OutputPath -Parent
        if (-not (Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null }
        $csvPath = $OutputPath -replace '\.html$', '.csv'
        $script:Results | Export-Csv -Path $csvPath -NoTypeInformation
        $OutputPath = $csvPath
    }
    'json' {
        $outputDir = Split-Path $OutputPath -Parent
        if (-not (Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory -Force | Out-Null }
        $jsonPath = $OutputPath -replace '\.html$', '.json'
        $script:Results | ConvertTo-Json -Depth 3 | Out-File -FilePath $jsonPath -Encoding utf8
        $OutputPath = $jsonPath
    }
}

# Summary
$total = $script:PassCount + $script:FailCount + $script:WarnCount + $script:InfoCount
$scorePercent = if (($script:PassCount + $script:FailCount) -gt 0) {
    [math]::Round($script:PassCount / ($script:PassCount + $script:FailCount) * 100, 1)
} else { 0 }

Write-Host ""
Write-Host "===== Audit Summary =====" -ForegroundColor Green
Write-Host "  Pass:  $($script:PassCount)" -ForegroundColor Green
Write-Host "  Fail:  $($script:FailCount)" -ForegroundColor Red
Write-Host "  Warn:  $($script:WarnCount)" -ForegroundColor Yellow
Write-Host "  Info:  $($script:InfoCount)" -ForegroundColor Cyan
Write-Host "  Total: $total"
Write-Host ""
Write-Host "  Compliance Score: ${scorePercent}%" -ForegroundColor $(if ($scorePercent -ge 80) { 'Green' } elseif ($scorePercent -ge 60) { 'Yellow' } else { 'Red' })
Write-Host ""
Write-Host "  Report: $OutputPath" -ForegroundColor Cyan
Write-Host ""
