# ==========================================
# UNIVERSAL WINDOWS SERVER AUDIT SCRIPT
# Works on Windows Server 2012 -> 2025
# Outputs: HOSTNAME_server_audit_DATE.json to C:\Temp
# ==========================================

# Auto-relaunch with ExecutionPolicy Bypass if needed
if ($MyInvocation.MyCommand.Path) {
    $currentPolicy = Get-ExecutionPolicy -Scope Process
    if ($currentPolicy -eq 'Restricted' -or $currentPolicy -eq 'AllSigned') {
        Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden -File `"$($MyInvocation.MyCommand.Path)`"" -Wait -NoNewWindow
        exit
    }
}

# Verify running as admin
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Host "ERROR: This script must be run as Administrator." -ForegroundColor Red
    exit 1
}

$Date = Get-Date -Format 'yyyy-MM-dd'
$OutputDir = "C:\Temp"
$Name = $env:COMPUTERNAME
$JsonFile = "$OutputDir\${Name}_server_audit_$Date.json"

if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null }

# Structured data collector
$audit = [ordered]@{
    _metadata = [ordered]@{
        ScriptVersion = "2.1"
        ScriptType    = "Server"
        RunDate       = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        RunBy         = "$env:USERDOMAIN\$env:USERNAME"
        Hostname      = $env:COMPUTERNAME
    }
    _errors = @()
}

Write-Host "======================================="
Write-Host "      UNIVERSAL SERVER AUDIT v2.0"
Write-Host "      $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))"
Write-Host "======================================="
Write-Host ""

# =====================================================
# 1. SYSTEM INFO
# =====================================================
Write-Host "=== 1. SYSTEM INFO ===" -ForegroundColor Cyan
Write-Host "  Running: Get-CimInstance Win32_OperatingSystem, Win32_ComputerSystem"
try {
    $os = Get-CimInstance Win32_OperatingSystem
    $cs = Get-CimInstance Win32_ComputerSystem
    $uptime = (Get-Date) - $os.LastBootUpTime

    $sysInfo = [ordered]@{
        Hostname      = $env:COMPUTERNAME
        OS            = $os.Caption
        OSVersion     = $os.Version
        BuildNumber   = $os.BuildNumber
        Architecture  = $os.OSArchitecture
        InstallDate   = $os.InstallDate.ToString("yyyy-MM-dd")
        LastBoot      = $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss")
        UptimeDays    = [math]::Round($uptime.TotalDays, 1)
        Domain        = $cs.Domain
        DomainJoined  = $cs.PartOfDomain
        DomainRole    = switch ($cs.DomainRole) {
            0 {"Standalone Workstation"} 1 {"Member Workstation"} 2 {"Standalone Server"}
            3 {"Member Server"} 4 {"Backup DC"} 5 {"Primary DC"} default {"Unknown ($($cs.DomainRole))"}
        }
        TotalRAM_GB   = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1)
    }

    $sysInfo.GetEnumerator() | ForEach-Object { Write-Host "  $($_.Key): $($_.Value)" }
    $audit.SystemInfo = $sysInfo
    Write-Host "  [OK] System info collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "SystemInfo"; Error = $_.Exception.Message }
}

# =====================================================
# 2. HARDWARE
# =====================================================
Write-Host ""
Write-Host "=== 2. HARDWARE ===" -ForegroundColor Cyan
Write-Host "  Running: Get-CimInstance Win32_ComputerSystem, Win32_Processor, Win32_BIOS, Win32_SystemEnclosure"
try {
    $hw = Get-CimInstance Win32_ComputerSystem
    $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
    $bios = Get-CimInstance Win32_BIOS
    $encl = Get-CimInstance Win32_SystemEnclosure | Select-Object -First 1

    $hardware = [ordered]@{
        Manufacturer  = $hw.Manufacturer
        Model         = $hw.Model
        SerialNumber  = if ($bios.SerialNumber) { $bios.SerialNumber } else { $encl.SerialNumber }
        CPU           = $cpu.Name
        CPUCores      = $cpu.NumberOfCores
        CPULogical    = $cpu.NumberOfLogicalProcessors
        RAM_GB        = [math]::Round($hw.TotalPhysicalMemory / 1GB, 1)
        BIOSVersion   = $bios.SMBIOSBIOSVersion
        BIOSDate      = if ($bios.ReleaseDate) { $bios.ReleaseDate.ToString("yyyy-MM-dd") } else { "N/A" }
    }

    $hardware.GetEnumerator() | ForEach-Object { Write-Host "  $($_.Key): $($_.Value)" }
    $audit.Hardware = $hardware
    Write-Host "  [OK] Hardware info collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Hardware"; Error = $_.Exception.Message }
}

# =====================================================
# 3. STORAGE
# =====================================================
Write-Host ""
Write-Host "=== 3. STORAGE ===" -ForegroundColor Cyan

Write-Host "  Running: Get-Volume"
try {
    $volumes = Get-Volume | Where-Object { $_.DriveLetter } | Select-Object DriveLetter,
        FileSystemLabel, FileSystem,
        @{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}},
        @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,1)}},
        @{N='UsedPct';E={if($_.Size -gt 0){[math]::Round(($_.Size - $_.SizeRemaining)/$_.Size * 100,0)}else{0}}},
        HealthStatus
    $volumes | Format-Table -AutoSize
    $audit.Volumes = @($volumes | ForEach-Object {
        [ordered]@{
            Drive = "$($_.DriveLetter):"
            Label = $_.FileSystemLabel
            FileSystem = $_.FileSystem
            SizeGB = $_.SizeGB
            FreeGB = $_.FreeGB
            UsedPct = $_.UsedPct
            Health = "$($_.HealthStatus)"
        }
    })
    Write-Host "  [OK] Volumes collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Volumes"; Error = $_.Exception.Message }
}

Write-Host "  Running: Get-PhysicalDisk"
try {
    $disks = Get-PhysicalDisk | Select-Object DeviceId, FriendlyName, MediaType,
        @{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}, HealthStatus, OperationalStatus
    $disks | Format-Table -AutoSize
    $audit.PhysicalDisks = @($disks | ForEach-Object {
        [ordered]@{
            DeviceId = "$($_.DeviceId)"
            Name = $_.FriendlyName
            MediaType = "$($_.MediaType)"
            SizeGB = $_.SizeGB
            Health = "$($_.HealthStatus)"
            Status = "$($_.OperationalStatus)"
        }
    })
    Write-Host "  [OK] Physical disks collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "PhysicalDisks"; Error = $_.Exception.Message }
}

# =====================================================
# 4. NETWORK CONFIG
# =====================================================
Write-Host ""
Write-Host "=== 4. NETWORK CONFIG ===" -ForegroundColor Cyan

Write-Host "  Running: Get-NetAdapter"
try {
    $adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } |
        Select-Object Name, InterfaceDescription, MacAddress, Status, LinkSpeed
    $adapters | Format-Table -AutoSize
    $audit.NetworkAdapters = @($adapters | ForEach-Object {
        [ordered]@{
            Name = $_.Name
            Description = $_.InterfaceDescription
            MAC = $_.MacAddress
            Status = "$($_.Status)"
            LinkSpeed = $_.LinkSpeed
        }
    })
    Write-Host "  [OK] Adapters collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "NetworkAdapters"; Error = $_.Exception.Message }
}

Write-Host "  Running: Get-NetIPConfiguration"
try {
    $ipConfigs = Get-NetIPConfiguration | Where-Object { $_.IPv4Address }
    $audit.IPConfig = @($ipConfigs | ForEach-Object {
        [ordered]@{
            Interface = $_.InterfaceAlias
            IPv4 = "$($_.IPv4Address.IPAddress)"
            Gateway = "$($_.IPv4DefaultGateway.NextHop)"
            DNS = @($_.DNSServer | Where-Object { $_.AddressFamily -eq 2 } | ForEach-Object { $_.ServerAddresses }) | Select-Object -Unique
        }
    })
    $ipConfigs | Format-List InterfaceAlias, IPv4Address, IPv4DefaultGateway, DnsServer
    Write-Host "  [OK] IP config collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "IPConfig"; Error = $_.Exception.Message }
}

Write-Host "  Running: Get-DnsClientServerAddress"
try {
    Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 } |
        Format-Table InterfaceAlias, ServerAddresses -AutoSize
    Write-Host "  [OK] DNS client config collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "DnsClient"; Error = $_.Exception.Message }
}

# =====================================================
# 5. ROUTING TABLE
# =====================================================
Write-Host ""
Write-Host "=== 5. ROUTING TABLE ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NetRoute"
try {
    Get-NetRoute -AddressFamily IPv4 | Where-Object { $_.DestinationPrefix -ne '255.255.255.255/32' } |
        Sort-Object DestinationPrefix |
        Format-Table DestinationPrefix, NextHop, InterfaceAlias, RouteMetric -AutoSize
    Write-Host "  [OK] Routes collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Routes"; Error = $_.Exception.Message }
}

# =====================================================
# 6. ARP TABLE
# =====================================================
Write-Host ""
Write-Host "=== 6. ARP TABLE ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NetNeighbor"
try {
    $arp = Get-NetNeighbor -AddressFamily IPv4 | Where-Object { $_.State -ne 'Unreachable' -and $_.State -ne 'Permanent' } |
        Select-Object IPAddress, LinkLayerAddress, State, InterfaceAlias
    $arp | Format-Table -AutoSize
    $audit.ARPTable = @($arp | ForEach-Object {
        [ordered]@{ IP = $_.IPAddress; MAC = $_.LinkLayerAddress; State = "$($_.State)"; Interface = $_.InterfaceAlias }
    })
    Write-Host "  [OK] ARP table collected - $($arp.Count) entries" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ARP"; Error = $_.Exception.Message }
}

# =====================================================
# 7. LISTENING PORTS
# =====================================================
Write-Host ""
Write-Host "=== 7. LISTENING PORTS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NetTCPConnection -State Listen"
try {
    $listeners = Get-NetTCPConnection -State Listen | ForEach-Object {
        $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
        [PSCustomObject]@{
            LocalAddress = $_.LocalAddress
            LocalPort    = $_.LocalPort
            PID          = $_.OwningProcess
            ProcessName  = $proc.ProcessName
        }
    } | Sort-Object LocalPort
    $listeners | Format-Table -AutoSize
    $audit.ListeningPorts = @($listeners | ForEach-Object {
        [ordered]@{ Address = $_.LocalAddress; Port = $_.LocalPort; PID = $_.PID; Process = $_.ProcessName }
    })
    Write-Host "  [OK] Listening ports collected - $($listeners.Count) ports" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ListeningPorts"; Error = $_.Exception.Message }
}

# =====================================================
# 8. WINDOWS FIREWALL RULES
# =====================================================
Write-Host ""
Write-Host "=== 8. WINDOWS FIREWALL RULES (Enabled) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NetFirewallRule | Get-NetFirewallPortFilter"
try {
    $fwRules = Get-NetFirewallRule | Where-Object { $_.Enabled -eq 'True' } | ForEach-Object {
        $portFilter = $_ | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue
        [PSCustomObject]@{
            DisplayName = $_.DisplayName
            Direction   = "$($_.Direction)"
            Action      = "$($_.Action)"
            Protocol    = $portFilter.Protocol
            LocalPort   = $portFilter.LocalPort
            RemotePort  = $portFilter.RemotePort
        }
    }
    $fwRules | Format-Table DisplayName, Direction, Action, Protocol, LocalPort -AutoSize
    $audit.FirewallRules = @($fwRules | ForEach-Object {
        [ordered]@{
            Name = $_.DisplayName; Direction = $_.Direction; Action = $_.Action
            Protocol = $_.Protocol; LocalPort = $_.LocalPort; RemotePort = $_.RemotePort
        }
    })
    Write-Host "  [OK] Firewall rules collected - $($fwRules.Count) enabled rules" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "FirewallRules"; Error = $_.Exception.Message }
}

# =====================================================
# 9. AD DOMAIN INFO
# =====================================================
Write-Host ""
Write-Host "=== 9. AD DOMAIN INFO ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADDomain, Get-ADForest"
try {
    Import-Module ActiveDirectory -ErrorAction Stop
    $script:domain = Get-ADDomain
    $script:forest = Get-ADForest
    $domain = $script:domain
    $forest = $script:forest

    $adInfo = [ordered]@{
        DomainName       = $domain.DNSRoot
        NetBIOSName      = $domain.NetBIOSName
        DomainMode       = "$($domain.DomainMode)"
        ForestName       = $forest.Name
        ForestMode       = "$($forest.ForestMode)"
        PDCEmulator      = $domain.PDCEmulator
        SchemaMaster     = $forest.SchemaMaster
        Sites            = @($forest.Sites)
        GlobalCatalogs   = @($forest.GlobalCatalogs)
    }

    $adInfo.GetEnumerator() | ForEach-Object {
        $val = if ($_.Value -is [array]) { $_.Value -join ", " } else { $_.Value }
        Write-Host "  $($_.Key): $val"
    }
    $audit.ADDomain = $adInfo

    # Trusts
    Write-Host "  Running: Get-ADTrust"
    try {
        $trusts = Get-ADTrust -Filter *
        if ($trusts) {
            $trusts | Format-Table Name, Direction, TrustType, IntraForest -AutoSize
            $audit.ADTrusts = @($trusts | ForEach-Object {
                [ordered]@{ Name = $_.Name; Direction = "$($_.Direction)"; Type = "$($_.TrustType)"; IntraForest = $_.IntraForest }
            })
        } else {
            Write-Host "  No trusts configured"
            $audit.ADTrusts = @()
        }
    }
    catch {
        Write-Host "  [FAIL] Trusts: $($_.Exception.Message)" -ForegroundColor Red
        $audit._errors += @{ Section = "ADTrusts"; Error = $_.Exception.Message }
    }

    Write-Host "  [OK] AD domain info collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (AD module not available - this may not be a domain controller)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "ADDomain"; Error = $_.Exception.Message }
}

# =====================================================
# 10. DOMAIN CONTROLLERS
# =====================================================
Write-Host ""
Write-Host "=== 10. DOMAIN CONTROLLERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADDomainController -Filter *"
try {
    $dcs = Get-ADDomainController -Filter *
    $dcs | Format-Table HostName, IPv4Address, Site, IsGlobalCatalog, OperatingSystem -AutoSize
    $audit.DomainControllers = @($dcs | ForEach-Object {
        [ordered]@{
            Hostname = $_.HostName; IP = $_.IPv4Address; Site = $_.Site
            IsGC = $_.IsGlobalCatalog; OS = $_.OperatingSystem
        }
    })
    Write-Host "  [OK] Domain controllers collected - $($dcs.Count) DCs" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "DomainControllers"; Error = $_.Exception.Message }
}

# =====================================================
# 11. AD OU STRUCTURE
# =====================================================
Write-Host ""
Write-Host "=== 11. AD OU STRUCTURE ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADOrganizationalUnit -Filter *"
try {
    $ous = Get-ADOrganizationalUnit -Filter * -Properties Description, ProtectedFromAccidentalDeletion |
        Sort-Object DistinguishedName
    $ous | Format-Table Name, DistinguishedName, Description, ProtectedFromAccidentalDeletion -AutoSize -Wrap
    $audit.ADOUs = @($ous | ForEach-Object {
        [ordered]@{
            Name = $_.Name; DN = $_.DistinguishedName; Description = $_.Description
            Protected = $_.ProtectedFromAccidentalDeletion
        }
    })
    Write-Host "  [OK] OU structure collected - $($ous.Count) OUs" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ADOUs"; Error = $_.Exception.Message }
}

# =====================================================
# 12. AD USERS
# =====================================================
Write-Host ""
Write-Host "=== 12. AD USERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADUser -Filter * -Properties ..."
try {
    $users = Get-ADUser -Filter * -Properties LastLogonDate, Enabled, Description,
        PasswordLastSet, PasswordNeverExpires, DistinguishedName, EmailAddress, WhenCreated |
        Select-Object Name, SamAccountName, Enabled, LastLogonDate, PasswordLastSet,
            PasswordNeverExpires, EmailAddress, Description, DistinguishedName, WhenCreated
    $users | Format-Table Name, SamAccountName, Enabled, LastLogonDate, EmailAddress -AutoSize
    $audit.ADUsers = @($users | ForEach-Object {
        [ordered]@{
            Name = $_.Name; SAM = $_.SamAccountName; Enabled = $_.Enabled
            LastLogon = if ($_.LastLogonDate) { $_.LastLogonDate.ToString("yyyy-MM-dd") } else { $null }
            PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null }
            PasswordNeverExpires = $_.PasswordNeverExpires
            Email = $_.EmailAddress; Description = $_.Description
            OU = $_.DistinguishedName; Created = if ($_.WhenCreated) { $_.WhenCreated.ToString("yyyy-MM-dd") } else { $null }
        }
    })
    Write-Host "  [OK] AD users collected - $($users.Count) users" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ADUsers"; Error = $_.Exception.Message }
}

# =====================================================
# 13. AD COMPUTERS
# =====================================================
Write-Host ""
Write-Host "=== 13. AD COMPUTERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADComputer -Filter * -Properties ..."
try {
    $computers = Get-ADComputer -Filter * -Properties LastLogonDate, OperatingSystem,
        OperatingSystemVersion, IPv4Address, DistinguishedName, Description, WhenCreated |
        Select-Object Name, OperatingSystem, OperatingSystemVersion, IPv4Address,
            LastLogonDate, Description, DistinguishedName, WhenCreated
    $computers | Format-Table Name, OperatingSystem, IPv4Address, LastLogonDate -AutoSize
    $audit.ADComputers = @($computers | ForEach-Object {
        [ordered]@{
            Name = $_.Name; OS = $_.OperatingSystem; OSVersion = $_.OperatingSystemVersion
            IP = $_.IPv4Address
            LastLogon = if ($_.LastLogonDate) { $_.LastLogonDate.ToString("yyyy-MM-dd") } else { $null }
            Description = $_.Description; OU = $_.DistinguishedName
            Created = if ($_.WhenCreated) { $_.WhenCreated.ToString("yyyy-MM-dd") } else { $null }
        }
    })
    Write-Host "  [OK] AD computers collected - $($computers.Count) computers" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ADComputers"; Error = $_.Exception.Message }
}

# =====================================================
# 14. AD GROUPS WITH MEMBERS
# =====================================================
Write-Host ""
Write-Host "=== 14. AD GROUPS WITH MEMBERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADGroup -Filter * + Get-ADGroupMember"
try {
    $groups = Get-ADGroup -Filter * -Properties Description, Members |
        Where-Object { $_.Members.Count -gt 0 -and $_.GroupCategory -eq 'Security' } |
        Sort-Object Name

    $audit.ADGroups = @()
    foreach ($g in $groups) {
        Write-Host "  Group: $($g.Name) [$($g.GroupScope)]" -ForegroundColor Yellow
        try {
            $members = Get-ADGroupMember $g.DistinguishedName -ErrorAction Stop |
                Select-Object Name, SamAccountName, objectClass
            $members | Format-Table -AutoSize

            $audit.ADGroups += [ordered]@{
                Name = $g.Name; Scope = "$($g.GroupScope)"; Category = "$($g.GroupCategory)"
                Description = $g.Description
                Members = @($members | ForEach-Object { [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; Type = $_.objectClass } })
            }
        }
        catch {
            Write-Host "    [FAIL] Could not enumerate members: $($_.Exception.Message)" -ForegroundColor Red
            $audit.ADGroups += [ordered]@{
                Name = $g.Name; Scope = "$($g.GroupScope)"; Error = $_.Exception.Message
            }
        }
    }
    Write-Host "  [OK] AD groups collected - $($groups.Count) groups with members" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ADGroups"; Error = $_.Exception.Message }
}

# =====================================================
# 15. PRIVILEGED GROUPS
# =====================================================
Write-Host ""
Write-Host "=== 15. PRIVILEGED GROUPS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ADGroupMember for privileged groups"

$privGroups = @("Domain Admins", "Enterprise Admins", "Schema Admins", "Administrators",
    "Account Operators", "Backup Operators", "Server Operators")
$audit.PrivilegedGroups = @()

foreach ($pg in $privGroups) {
    try {
        $members = Get-ADGroupMember $pg -ErrorAction Stop | Select-Object Name, SamAccountName, objectClass
        Write-Host "  $pg`:" -ForegroundColor Yellow
        if ($members) {
            $members | ForEach-Object { Write-Host "    $($_.Name) ($($_.SamAccountName)) [$($_.objectClass)]" }
            $audit.PrivilegedGroups += [ordered]@{
                Group = $pg
                Members = @($members | ForEach-Object { [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; Type = $_.objectClass } })
            }
        } else {
            Write-Host "    (empty)"
            $audit.PrivilegedGroups += [ordered]@{ Group = $pg; Members = @() }
        }
    }
    catch {
        Write-Host "  $pg`: [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    }
}
Write-Host "  [OK] Privileged groups checked" -ForegroundColor Green

# =====================================================
# 16. GROUP POLICY
# =====================================================
Write-Host ""
Write-Host "=== 16. GROUP POLICY ===" -ForegroundColor Cyan
Write-Host "  Running: Get-GPO -All + Get-GPOReport (for links)"
try {
    Import-Module GroupPolicy -ErrorAction Stop
    $gpos = Get-GPO -All

    $audit.GPOs = @()
    foreach ($gpo in $gpos) {
        Write-Host "  GPO: $($gpo.DisplayName)" -ForegroundColor Yellow
        Write-Host "    Status: $($gpo.GpoStatus)  Modified: $($gpo.ModificationTime)"

        $links = @()
        try {
            [xml]$report = Get-GPOReport -Guid $gpo.Id -ReportType Xml -ErrorAction Stop
            if ($report.GPO.LinksTo) {
                $report.GPO.LinksTo | ForEach-Object {
                    Write-Host "    Link: $($_.SOMPath) [Enabled: $($_.Enabled)]"
                    $links += [ordered]@{ Path = $_.SOMPath; Enabled = $_.Enabled }
                }
            } else {
                Write-Host "    Link: (not linked)"
            }
        }
        catch {
            Write-Host "    [FAIL] Could not get links: $($_.Exception.Message)" -ForegroundColor Red
        }

        $audit.GPOs += [ordered]@{
            Name = $gpo.DisplayName; Status = "$($gpo.GpoStatus)"
            Created = $gpo.CreationTime.ToString("yyyy-MM-dd")
            Modified = $gpo.ModificationTime.ToString("yyyy-MM-dd")
            Links = $links
        }
    }
    Write-Host "  [OK] GPOs collected - $($gpos.Count) GPOs" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "GPOs"; Error = $_.Exception.Message }
}

# =====================================================
# 17. LOCAL USERS
# =====================================================
Write-Host ""
Write-Host "=== 17. LOCAL USERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-LocalUser"
try {
    $localUsers = Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordRequired, PasswordLastSet
    $localUsers | Format-Table -AutoSize
    $audit.LocalUsers = @($localUsers | ForEach-Object {
        [ordered]@{
            Name = $_.Name; Enabled = $_.Enabled
            LastLogon = if ($_.LastLogon) { $_.LastLogon.ToString("yyyy-MM-dd") } else { $null }
            PasswordRequired = $_.PasswordRequired
            PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null }
        }
    })
    Write-Host "  [OK] Local users collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message) - trying net user fallback" -ForegroundColor Red
    try { net user } catch {}
    $audit._errors += @{ Section = "LocalUsers"; Error = $_.Exception.Message }
}

# =====================================================
# 18. LOCAL ADMINISTRATORS
# =====================================================
Write-Host ""
Write-Host "=== 18. LOCAL ADMINISTRATORS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-LocalGroupMember Administrators"
try {
    $localAdmins = Get-LocalGroupMember Administrators | Select-Object Name, PrincipalSource, ObjectClass
    $localAdmins | Format-Table -AutoSize
    $audit.LocalAdmins = @($localAdmins | ForEach-Object {
        [ordered]@{ Name = $_.Name; Source = "$($_.PrincipalSource)"; Type = "$($_.ObjectClass)" }
    })
    Write-Host "  [OK] Local admins collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message) - trying net localgroup fallback" -ForegroundColor Red
    try { net localgroup Administrators } catch {}
    $audit._errors += @{ Section = "LocalAdmins"; Error = $_.Exception.Message }
}

# =====================================================
# 19. SHARES WITH PERMISSIONS
# =====================================================
Write-Host ""
Write-Host "=== 19. SHARES WITH PERMISSIONS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-SmbShare + Get-SmbShareAccess + Get-Acl"
try {
    $shares = Get-SmbShare | Where-Object { $_.Name -notlike '*$' }
    $audit.Shares = @()

    foreach ($s in $shares) {
        Write-Host "  Share: \\$env:COMPUTERNAME\$($s.Name)" -ForegroundColor Yellow
        Write-Host "    Path: $($s.Path)"
        Write-Host "    Description: $($s.Description)"

        $shareData = [ordered]@{
            Name = $s.Name; Path = $s.Path; Description = $s.Description
            SMBPermissions = @(); NTFSPermissions = @()
        }

        # SMB permissions
        Write-Host "    SMB Permissions:" -ForegroundColor DarkGray
        try {
            $smbPerms = Get-SmbShareAccess -Name $s.Name
            $smbPerms | ForEach-Object {
                Write-Host "      $($_.AccountName): $($_.AccessRight) ($($_.AccessControlType))"
                $shareData.SMBPermissions += [ordered]@{
                    Account = $_.AccountName; Right = "$($_.AccessRight)"; Type = "$($_.AccessControlType)"
                }
            }
        }
        catch {
            Write-Host "      [FAIL] $($_.Exception.Message)" -ForegroundColor Red
        }

        # NTFS permissions
        Write-Host "    NTFS Permissions:" -ForegroundColor DarkGray
        try {
            if (Test-Path $s.Path) {
                $acl = Get-Acl $s.Path
                $acl.Access | ForEach-Object {
                    Write-Host "      $($_.IdentityReference): $($_.FileSystemRights) ($($_.AccessControlType))"
                    $shareData.NTFSPermissions += [ordered]@{
                        Identity = "$($_.IdentityReference)"; Rights = "$($_.FileSystemRights)"
                        Type = "$($_.AccessControlType)"; Inherited = $_.IsInherited
                    }
                }
            }
        }
        catch {
            Write-Host "      [FAIL] $($_.Exception.Message)" -ForegroundColor Red
        }

        $audit.Shares += $shareData
    }
    Write-Host "  [OK] Shares collected - $($shares.Count) shares" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Shares"; Error = $_.Exception.Message }
}

# =====================================================
# 20. INSTALLED ROLES/FEATURES
# =====================================================
Write-Host ""
Write-Host "=== 20. INSTALLED ROLES/FEATURES ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WindowsFeature | Where Installed"
try {
    $roles = Get-WindowsFeature | Where-Object { $_.Installed } | Select-Object Name, DisplayName
    $roles | Format-Table -AutoSize
    $audit.InstalledRoles = @($roles | ForEach-Object {
        [ordered]@{ Name = $_.Name; DisplayName = $_.DisplayName }
    })
    Write-Host "  [OK] Roles collected - $($roles.Count) installed" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "InstalledRoles"; Error = $_.Exception.Message }
}

# =====================================================
# 21. DHCP
# =====================================================
Write-Host ""
Write-Host "=== 21. DHCP ===" -ForegroundColor Cyan
Write-Host "  Running: Get-DhcpServerv4Scope + options + reservations + leases"
try {
    Import-Module DhcpServer -ErrorAction Stop
    $scopes = Get-DhcpServerv4Scope
    $audit.DHCP = [ordered]@{ Scopes = @() }

    # Server-level options
    Write-Host "  Server-level DHCP options:" -ForegroundColor Yellow
    try {
        $serverOpts = Get-DhcpServerv4OptionValue -ErrorAction Stop
        $serverOpts | Format-Table OptionId, Name, Value -AutoSize
        $audit.DHCP.ServerOptions = @($serverOpts | ForEach-Object {
            [ordered]@{ OptionId = $_.OptionId; Name = $_.Name; Value = @($_.Value) }
        })
    }
    catch {
        Write-Host "    [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    }

    # Failover
    try {
        $failover = Get-DhcpServerv4Failover -ErrorAction Stop
        if ($failover) {
            $failover | Format-Table Name, PartnerServer, Mode, State -AutoSize
            $audit.DHCP.Failover = @($failover | ForEach-Object {
                [ordered]@{ Name = $_.Name; Partner = $_.PartnerServer; Mode = "$($_.Mode)"; State = "$($_.State)" }
            })
        }
    }
    catch { Write-Host "  No DHCP failover configured" -ForegroundColor DarkGray }

    foreach ($scope in $scopes) {
        $scopeData = [ordered]@{
            ScopeId = "$($scope.ScopeId)"; Name = $scope.Name
            SubnetMask = "$($scope.SubnetMask)"; StartRange = "$($scope.StartRange)"
            EndRange = "$($scope.EndRange)"; State = "$($scope.State)"
            LeaseDuration = "$($scope.LeaseDuration)"
            Options = @(); Exclusions = @(); Reservations = @(); Leases = @()
        }

        Write-Host "  Scope: $($scope.ScopeId) ($($scope.Name))" -ForegroundColor Yellow
        Write-Host "    Range: $($scope.StartRange) - $($scope.EndRange), Mask: $($scope.SubnetMask), State: $($scope.State)"

        # Scope options
        try {
            $scopeOpts = Get-DhcpServerv4OptionValue -ScopeId $scope.ScopeId -ErrorAction Stop
            Write-Host "    Options:"
            $scopeOpts | ForEach-Object { Write-Host "      $($_.OptionId) $($_.Name): $($_.Value -join ', ')" }
            $scopeData.Options = @($scopeOpts | ForEach-Object {
                [ordered]@{ OptionId = $_.OptionId; Name = $_.Name; Value = @($_.Value) }
            })
        }
        catch { Write-Host "    Options: [FAIL] $($_.Exception.Message)" -ForegroundColor Red }

        # Exclusions
        try {
            $exclusions = Get-DhcpServerv4ExclusionRange -ScopeId $scope.ScopeId -ErrorAction Stop
            if ($exclusions) {
                Write-Host "    Exclusions:"
                $exclusions | ForEach-Object { Write-Host "      $($_.StartRange) - $($_.EndRange)" }
                $scopeData.Exclusions = @($exclusions | ForEach-Object {
                    [ordered]@{ Start = "$($_.StartRange)"; End = "$($_.EndRange)" }
                })
            }
        }
        catch {}

        # Reservations
        try {
            $reservations = Get-DhcpServerv4Reservation -ScopeId $scope.ScopeId -ErrorAction Stop
            if ($reservations) {
                Write-Host "    Reservations:"
                $reservations | Format-Table IPAddress, Name, ClientId, Description -AutoSize
                $scopeData.Reservations = @($reservations | ForEach-Object {
                    [ordered]@{ IP = "$($_.IPAddress)"; Name = $_.Name; MAC = $_.ClientId; Description = $_.Description }
                })
            }
        }
        catch { Write-Host "    Reservations: [FAIL] $($_.Exception.Message)" -ForegroundColor Red }

        # Leases
        try {
            $leases = Get-DhcpServerv4Lease -ScopeId $scope.ScopeId -ErrorAction Stop
            if ($leases) {
                Write-Host "    Active Leases:"
                $leases | Format-Table IPAddress, HostName, ClientId, AddressState -AutoSize
                $scopeData.Leases = @($leases | ForEach-Object {
                    [ordered]@{ IP = "$($_.IPAddress)"; Hostname = $_.HostName; MAC = $_.ClientId; State = "$($_.AddressState)" }
                })
            }
        }
        catch { Write-Host "    Leases: [FAIL] $($_.Exception.Message)" -ForegroundColor Red }

        $audit.DHCP.Scopes += $scopeData
    }
    Write-Host "  [OK] DHCP collected - $($scopes.Count) scopes" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (DHCP role not installed or not running)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "DHCP"; Error = $_.Exception.Message }
}

# =====================================================
# 22. DNS SERVER
# =====================================================
Write-Host ""
Write-Host "=== 22. DNS SERVER ===" -ForegroundColor Cyan
Write-Host "  Running: Get-DnsServerZone, Get-DnsServerForwarder, Get-DnsServerResourceRecord"
try {
    Import-Module DnsServer -ErrorAction Stop

    # Forwarders
    Write-Host "  Forwarders:" -ForegroundColor Yellow
    $fwd = Get-DnsServerForwarder
    $fwd.IPAddress | ForEach-Object { Write-Host "    $_" }
    $audit.DNS = [ordered]@{
        Forwarders = @($fwd.IPAddress | ForEach-Object { "$_" })
        ConditionalForwarders = @()
        Zones = @()
    }

    # Conditional forwarders
    Write-Host "  Conditional Forwarders:" -ForegroundColor Yellow
    $condFwd = Get-DnsServerZone | Where-Object { $_.ZoneType -eq 'Forwarder' }
    if ($condFwd) {
        $condFwd | ForEach-Object {
            Write-Host "    $($_.ZoneName) -> $($_.MasterServers -join ', ')"
            $audit.DNS.ConditionalForwarders += [ordered]@{ Zone = $_.ZoneName; ForwardTo = @($_.MasterServers | ForEach-Object { "$_" }) }
        }
    } else {
        Write-Host "    (none)"
    }

    # Zones and records
    Write-Host "  Zones:" -ForegroundColor Yellow
    $zones = Get-DnsServerZone | Where-Object { $_.ZoneType -ne 'Forwarder' }
    foreach ($z in $zones) {
        $zoneData = [ordered]@{
            Name = $z.ZoneName; Type = "$($z.ZoneType)"; IsReverse = $z.IsReverseLookupZone
            DynamicUpdate = "$($z.DynamicUpdate)"; Records = @()
        }

        Write-Host "    $($z.ZoneName) [$($z.ZoneType)] Reverse=$($z.IsReverseLookupZone)" -ForegroundColor DarkGray

        # Get records for primary zones (skip if >500 records to avoid flooding)
        if ($z.ZoneType -eq 'Primary' -and -not $z.IsAutoCreated) {
            try {
                $records = Get-DnsServerResourceRecord -ZoneName $z.ZoneName -ErrorAction Stop
                if ($records.Count -le 500) {
                    $zoneData.Records = @($records | ForEach-Object {
                        [ordered]@{
                            Name = $_.HostName; Type = "$($_.RecordType)"; TTL = "$($_.TimeToLive)"
                            Data = "$($_.RecordData.IPv4Address)$($_.RecordData.HostNameAlias)$($_.RecordData.NameServer)$($_.RecordData.DescriptiveText)$($_.RecordData.DomainName)"
                        }
                    })
                    Write-Host "      $($records.Count) records"
                } else {
                    Write-Host "      $($records.Count) records (too many to list, count only)" -ForegroundColor Yellow
                    $zoneData.RecordCount = $records.Count
                }
            }
            catch {
                Write-Host "      [FAIL] Records: $($_.Exception.Message)" -ForegroundColor Red
            }
        }

        $audit.DNS.Zones += $zoneData
    }
    Write-Host "  [OK] DNS collected - $($zones.Count) zones" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (DNS Server role not installed)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "DNS"; Error = $_.Exception.Message }
}

# =====================================================
# 23. HYPER-V VMs
# =====================================================
Write-Host ""
Write-Host "=== 23. HYPER-V VMs ===" -ForegroundColor Cyan
Write-Host "  Running: Get-VM"
try {
    $vms = Get-VM -ErrorAction Stop
    if ($vms) {
        $audit.HyperV = @($vms | ForEach-Object {
            $vm = $_
            Write-Host "  VM: $($vm.Name) [State: $($vm.State)]" -ForegroundColor Yellow
            Write-Host "    CPU: $($vm.ProcessorCount), Memory: $([math]::Round($vm.MemoryAssigned/1MB))MB, Gen: $($vm.Generation)"

            $netAdapters = Get-VMNetworkAdapter -VMName $vm.Name -ErrorAction SilentlyContinue
            $disks = Get-VMHardDiskDrive -VMName $vm.Name -ErrorAction SilentlyContinue

            [ordered]@{
                Name = $vm.Name; State = "$($vm.State)"; CPUs = $vm.ProcessorCount
                MemoryMB = [math]::Round($vm.MemoryAssigned/1MB); Generation = $vm.Generation
                Networks = @($netAdapters | ForEach-Object { [ordered]@{ Switch = $_.SwitchName; MAC = $_.MacAddress; IPs = @($_.IPAddresses) } })
                Disks = @($disks | ForEach-Object { $_.Path })
            }
        })
        Write-Host "  [OK] Hyper-V VMs collected - $($vms.Count) VMs" -ForegroundColor Green
    } else {
        Write-Host "  No VMs found" -ForegroundColor DarkGray
        $audit.HyperV = @()
    }
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (Hyper-V role not installed)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "HyperV"; Error = $_.Exception.Message }
}

# =====================================================
# 24. IIS SITES
# =====================================================
Write-Host ""
Write-Host "=== 24. IIS SITES ===" -ForegroundColor Cyan
Write-Host "  Running: Get-Website"
try {
    Import-Module WebAdministration -ErrorAction Stop
    $sites = Get-Website
    if ($sites) {
        $audit.IIS = @($sites | ForEach-Object {
            Write-Host "  Site: $($_.Name) [State: $($_.State)]" -ForegroundColor Yellow
            Write-Host "    Path: $($_.PhysicalPath)"
            Write-Host "    Bindings: $(($_.Bindings.Collection | ForEach-Object { $_.bindingInformation }) -join ', ')"
            [ordered]@{
                Name = $_.Name; State = "$($_.State)"; Path = $_.PhysicalPath
                AppPool = $_.applicationPool
                Bindings = @($_.Bindings.Collection | ForEach-Object { $_.bindingInformation })
            }
        })
        Write-Host "  [OK] IIS sites collected" -ForegroundColor Green
    } else {
        Write-Host "  No websites configured" -ForegroundColor DarkGray
    }
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (IIS not installed)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "IIS"; Error = $_.Exception.Message }
}

# =====================================================
# 25. NPS / RADIUS
# =====================================================
Write-Host ""
Write-Host "=== 25. NPS / RADIUS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NpsRadiusClient, Get-NpsNetworkPolicy"
try {
    Import-Module NPS -ErrorAction Stop
    $clients = Get-NpsRadiusClient -ErrorAction Stop
    $policies = Get-NpsNetworkPolicy -ErrorAction Stop

    $audit.NPS = [ordered]@{
        Clients = @($clients | ForEach-Object {
            Write-Host "  RADIUS Client: $($_.Name) ($($_.Address))" -ForegroundColor Yellow
            [ordered]@{ Name = $_.Name; Address = $_.Address }
        })
        Policies = @($policies | ForEach-Object {
            Write-Host "  Policy: $($_.Name) [Enabled: $($_.Enabled)]" -ForegroundColor Yellow
            [ordered]@{ Name = $_.Name; Enabled = $_.Enabled; Order = $_.ProcessingOrder }
        })
    }
    Write-Host "  [OK] NPS collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (NPS/RADIUS not installed)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "NPS"; Error = $_.Exception.Message }
}

# =====================================================
# 26. WINDOWS SERVER BACKUP
# =====================================================
Write-Host ""
Write-Host "=== 26. WINDOWS SERVER BACKUP ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WBPolicy, Get-WBSummary"
try {
    Add-PSSnapin Windows.ServerBackup -ErrorAction Stop
    $policy = Get-WBPolicy -ErrorAction Stop

    $wsbData = [ordered]@{
        Schedule = @(Get-WBSchedule -Policy $policy)
        Volumes = @((Get-WBVolume -Policy $policy).MountPoint)
        SystemState = (Get-WBSystemState -Policy $policy)
    }

    try {
        $target = Get-WBBackupTarget -Policy $policy
        $wsbData.Target = "$($target.Label) ($($target.TargetPath))"
    }
    catch { $wsbData.Target = "Unknown" }

    try {
        $summary = Get-WBSummary
        $wsbData.LastSuccess = if ($summary.LastSuccessfulBackupTime) { $summary.LastSuccessfulBackupTime.ToString("yyyy-MM-dd HH:mm") } else { $null }
        $wsbData.LastResult = "$($summary.LastBackupResultHR)"
        $wsbData.NextRun = if ($summary.NextBackupTime) { $summary.NextBackupTime.ToString("yyyy-MM-dd HH:mm") } else { $null }
    }
    catch {}

    $wsbData.GetEnumerator() | ForEach-Object {
        $val = if ($_.Value -is [array]) { $_.Value -join ", " } else { $_.Value }
        Write-Host "  $($_.Key): $val"
    }

    $audit.WindowsServerBackup = $wsbData
    Write-Host "  [OK] WSB info collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    Write-Host "  (Windows Server Backup not installed or no policy configured)" -ForegroundColor Yellow
    $audit._errors += @{ Section = "WindowsServerBackup"; Error = $_.Exception.Message }
}

# =====================================================
# 27. PRINTERS
# =====================================================
Write-Host ""
Write-Host "=== 27. PRINTERS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-Printer, Get-PrinterPort"
try {
    $printers = Get-Printer | Select-Object Name, DriverName, PortName, Shared, ShareName, Published
    $printers | Format-Table -AutoSize

    $ports = Get-PrinterPort | Where-Object { $_.Name -like 'TCP_*' -or $_.Name -like 'IP_*' } |
        Select-Object Name, PrinterHostAddress, PortNumber
    if ($ports) {
        Write-Host "  Printer Ports:" -ForegroundColor DarkGray
        $ports | Format-Table -AutoSize
    }

    $audit.Printers = @($printers | ForEach-Object {
        [ordered]@{
            Name = $_.Name; Driver = $_.DriverName; Port = $_.PortName
            Shared = $_.Shared; ShareName = $_.ShareName
        }
    })
    $audit.PrinterPorts = @($ports | ForEach-Object {
        [ordered]@{ Name = $_.Name; IP = $_.PrinterHostAddress; Port = $_.PortNumber }
    })
    Write-Host "  [OK] Printers collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Printers"; Error = $_.Exception.Message }
}

# =====================================================
# 28. CERTIFICATES
# =====================================================
Write-Host ""
Write-Host "=== 28. CERTIFICATES ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ChildItem Cert:\LocalMachine\My"
try {
    $certs = Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, NotAfter, NotBefore, Issuer, Thumbprint
    $certs | Format-Table Subject, NotAfter, Issuer -AutoSize

    $expiringSoon = $certs | Where-Object { $_.NotAfter -lt (Get-Date).AddDays(90) }
    if ($expiringSoon) {
        Write-Host "  WARNING: Certificates expiring within 90 days:" -ForegroundColor Yellow
        $expiringSoon | ForEach-Object { Write-Host "    $($_.Subject) expires $($_.NotAfter)" -ForegroundColor Yellow }
    }

    $audit.Certificates = @($certs | ForEach-Object {
        [ordered]@{
            Subject = $_.Subject; Issuer = $_.Issuer; Thumbprint = $_.Thumbprint
            NotBefore = $_.NotBefore.ToString("yyyy-MM-dd"); NotAfter = $_.NotAfter.ToString("yyyy-MM-dd")
            ExpiringSoon = ($_.NotAfter -lt (Get-Date).AddDays(90))
        }
    })
    Write-Host "  [OK] Certificates collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Certificates"; Error = $_.Exception.Message }
}

# =====================================================
# 29. INSTALLED SOFTWARE (registry-based)
# =====================================================
Write-Host ""
Write-Host "=== 29. INSTALLED SOFTWARE ===" -ForegroundColor Cyan
Write-Host "  Running: Registry query (HKLM Uninstall keys)"
try {
    $regPaths = @(
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )
    $software = Get-ItemProperty $regPaths -ErrorAction SilentlyContinue |
        Where-Object { $_.DisplayName } |
        Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
        Sort-Object DisplayName -Unique

    $software | Format-Table -AutoSize

    $audit.InstalledSoftware = @($software | ForEach-Object {
        [ordered]@{
            Name = $_.DisplayName; Version = $_.DisplayVersion
            Publisher = $_.Publisher; InstallDate = $_.InstallDate
        }
    })
    Write-Host "  [OK] Software collected - $($software.Count) packages" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "InstalledSoftware"; Error = $_.Exception.Message }
}

# =====================================================
# 30. SERVICES (non-default running)
# =====================================================
Write-Host ""
Write-Host "=== 30. SERVICES (non-default, running) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-Service | Where Running + Automatic"
try {
    $services = Get-Service | Where-Object { $_.Status -eq 'Running' -and $_.StartType -eq 'Automatic' } |
        Where-Object { $_.DisplayName -notmatch '^(Windows|Microsoft|Net\.|COM\+|DCOM|WMI|Plug|Task|CNG|Base|Crypto|Security Center|DHCP Client|DNS Client|Group Policy|IP Helper|Server$|Workstation$|TCP/IP|Remote Procedure|User Profile|Background|Connected|CoreMessaging|State Repository|Storage|System Events|Time Broker|User Manager|WinHTTP|Diagnostic)' } |
        Select-Object Name, DisplayName, StartType |
        Sort-Object DisplayName

    $services | Format-Table -AutoSize

    $audit.Services = @($services | ForEach-Object {
        [ordered]@{ Name = $_.Name; DisplayName = $_.DisplayName; StartType = "$($_.StartType)" }
    })
    Write-Host "  [OK] Services collected - $($services.Count) non-default" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Services"; Error = $_.Exception.Message }
}

# =====================================================
# 31. SCHEDULED TASKS (non-Microsoft)
# =====================================================
Write-Host ""
Write-Host "=== 31. SCHEDULED TASKS (non-Microsoft) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-ScheduledTask"
try {
    $tasks = Get-ScheduledTask |
        Where-Object { $_.Author -notlike 'Microsoft*' -and $_.State -ne 'Disabled' } |
        Select-Object TaskName, State, Author, TaskPath
    $tasks | Format-Table -AutoSize

    $audit.ScheduledTasks = @($tasks | ForEach-Object {
        [ordered]@{ Name = $_.TaskName; State = "$($_.State)"; Author = $_.Author; Path = $_.TaskPath }
    })
    Write-Host "  [OK] Scheduled tasks collected - $($tasks.Count) non-Microsoft" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ScheduledTasks"; Error = $_.Exception.Message }
}

# =====================================================
# 32. WINDOWS UPDATES
# =====================================================
Write-Host ""
Write-Host "=== 32. WINDOWS UPDATES (last 20) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-HotFix"
try {
    $updates = Get-HotFix | Sort-Object InstalledOn -Descending -ErrorAction SilentlyContinue | Select-Object -First 20 |
        Select-Object HotFixID, Description, InstalledOn, InstalledBy
    $updates | Format-Table -AutoSize

    $audit.WindowsUpdates = @($updates | ForEach-Object {
        [ordered]@{
            KB = $_.HotFixID; Description = $_.Description
            InstalledOn = if ($_.InstalledOn) { $_.InstalledOn.ToString("yyyy-MM-dd") } else { $null }
            InstalledBy = $_.InstalledBy
        }
    })
    Write-Host "  [OK] Updates collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "WindowsUpdates"; Error = $_.Exception.Message }
}

# =====================================================
# 33. TIME CONFIG
# =====================================================
Write-Host ""
Write-Host "=== 33. TIME CONFIG ===" -ForegroundColor Cyan
Write-Host "  Running: w32tm /query /configuration"
try {
    $timeConfig = w32tm /query /configuration 2>&1
    $timeConfig | ForEach-Object { Write-Host "  $_" }
    Write-Host "  [OK] Time config collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "TimeConfig"; Error = $_.Exception.Message }
}

# =====================================================
# 34. EVENT LOG ERRORS (last 14 days)
# =====================================================
Write-Host ""
Write-Host "=== 34. EVENT LOG ERRORS (Critical/Error, last 14 days) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WinEvent -FilterHashtable System log"
try {
    $since = (Get-Date).AddDays(-14)
    $events = Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2; StartTime=$since} -MaxEvents 30 -ErrorAction Stop |
        Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}}
    $events | Format-Table TimeCreated, Id, ProviderName -AutoSize

    $audit.EventLogErrors = @($events | ForEach-Object {
        [ordered]@{
            Time = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
            EventId = $_.Id; Level = $_.LevelDisplayName; Source = $_.ProviderName
            Message = $_.Message
        }
    })
    Write-Host "  [OK] Events collected - $($events.Count) critical/error events" -ForegroundColor Green
}
catch {
    Write-Host "  No critical/error events in System log (last 14 days)" -ForegroundColor Green
    $audit.EventLogErrors = @()
}

# =====================================================
# 35. EVENT LOG WARNINGS (last 14 days)
# =====================================================
Write-Host ""
Write-Host "=== 35. EVENT LOG WARNINGS (last 14 days) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WinEvent - Warning level, last 14 days"
try {
    $warnSince = (Get-Date).AddDays(-14)
    $warnings = Get-WinEvent -FilterHashtable @{LogName='System'; Level=3; StartTime=$warnSince} -MaxEvents 50 -ErrorAction Stop |
        Select-Object TimeCreated, Id, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}}
    Write-Host "  $($warnings.Count) warning events found"
    $warnings | Format-Table TimeCreated, Id, ProviderName -AutoSize
    $audit.EventLogWarnings = @($warnings | ForEach-Object {
        [ordered]@{ Time = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss"); EventId = $_.Id; Source = $_.ProviderName; Message = $_.Message }
    })
    Write-Host "  [OK] Event log warnings collected" -ForegroundColor Green
}
catch {
    Write-Host "  No warning events in System log (last 14 days)" -ForegroundColor Green
    $audit.EventLogWarnings = @()
}

# =====================================================
# 36. EVENT LOG SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 36. EVENT LOG SETTINGS ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WinEvent -ListLog"
try {
    $importantLogs = @('Application', 'Security', 'System', 'Setup', 'Microsoft-Windows-PowerShell/Operational')
    $audit.EventLogSettings = @()
    foreach ($logName in $importantLogs) {
        try {
            $log = Get-WinEvent -ListLog $logName -ErrorAction Stop
            $logInfo = [ordered]@{
                LogName = $log.LogName
                MaxSizeKB = [math]::Round($log.MaximumSizeInBytes / 1KB)
                CurrentSizeKB = [math]::Round($log.FileSize / 1KB)
                RecordCount = $log.RecordCount
                LogMode = "$($log.LogMode)"
                IsEnabled = $log.IsEnabled
            }
            Write-Host "  $($log.LogName): Max=$($logInfo.MaxSizeKB)KB, Current=$($logInfo.CurrentSizeKB)KB, Mode=$($log.LogMode), Records=$($log.RecordCount)"
            $audit.EventLogSettings += $logInfo
        }
        catch { Write-Host "  $logName`: not available" -ForegroundColor DarkGray }
    }

    # HIPAA requires adequate log retention
    $securityLog = $audit.EventLogSettings | Where-Object { $_.LogName -eq 'Security' }
    if ($securityLog -and $securityLog.MaxSizeKB -lt 131072) {
        Write-Host "  [WARN] Security log max size is $($securityLog.MaxSizeKB)KB - recommend at least 128MB for compliance" -ForegroundColor Yellow
    }
    if ($securityLog -and $securityLog.LogMode -eq 'Circular') {
        Write-Host "  [WARN] Security log is in Circular mode - old events will be overwritten. Consider archiving for HIPAA" -ForegroundColor Yellow
    }
    Write-Host "  [OK] Event log settings collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "EventLogSettings"; Error = $_.Exception.Message }
}

# =====================================================
# 37. WINDOWS FIREWALL PROFILES
# =====================================================
Write-Host ""
Write-Host "=== 37. WINDOWS FIREWALL PROFILES ===" -ForegroundColor Cyan
Write-Host "  Running: Get-NetFirewallProfile"
try {
    $fwProfiles = Get-NetFirewallProfile -ErrorAction Stop
    $audit.FirewallProfiles = @($fwProfiles | ForEach-Object {
        $p = [ordered]@{
            Profile = "$($_.Name)"; Enabled = $_.Enabled
            DefaultInboundAction = "$($_.DefaultInboundAction)"
            DefaultOutboundAction = "$($_.DefaultOutboundAction)"
            LogAllowed = $_.LogAllowed; LogBlocked = $_.LogBlocked
            LogFileName = $_.LogFileName; LogMaxSizeKilobytes = $_.LogMaxSizeKilobytes
        }
        Write-Host "  $($_.Name): Enabled=$($_.Enabled), Inbound=$($_.DefaultInboundAction), Outbound=$($_.DefaultOutboundAction)"
        if (-not $_.Enabled) {
            Write-Host "    [WARN] $($_.Name) firewall profile is DISABLED" -ForegroundColor Red
        }
        if (-not $_.LogBlocked) {
            Write-Host "    [WARN] $($_.Name) blocked connections are NOT being logged" -ForegroundColor Yellow
        }
        $p
    })
    Write-Host "  [OK] Firewall profiles collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "FirewallProfiles"; Error = $_.Exception.Message }
}

# =====================================================
# 38. RDP SECURITY SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 38. RDP SECURITY SETTINGS ===" -ForegroundColor Cyan
Write-Host "  Running: Registry check for RDP settings"
try {
    $tsReg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server'
    $rdpReg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp'

    $rdpEnabled = (Get-ItemProperty $tsReg -ErrorAction Stop).fDenyTSConnections
    $nla = (Get-ItemProperty $rdpReg -ErrorAction SilentlyContinue).UserAuthentication
    $secLayer = (Get-ItemProperty $rdpReg -ErrorAction SilentlyContinue).SecurityLayer
    $minEncrypt = (Get-ItemProperty $rdpReg -ErrorAction SilentlyContinue).MinEncryptionLevel

    $rdpSettings = [ordered]@{
        RDPEnabled = ($rdpEnabled -eq 0)
        NLARequired = ($nla -eq 1)
        SecurityLayer = switch ($secLayer) { 0 {"RDP Security"} 1 {"Negotiate"} 2 {"SSL/TLS"} default {"Unknown ($secLayer)"} }
        MinEncryptionLevel = switch ($minEncrypt) { 1 {"Low"} 2 {"Client Compatible"} 3 {"High"} 4 {"FIPS"} default {"Unknown ($minEncrypt)"} }
    }
    $rdpSettings.GetEnumerator() | ForEach-Object { Write-Host "  $($_.Key): $($_.Value)" }

    if ($rdpSettings.RDPEnabled -and -not $rdpSettings.NLARequired) {
        Write-Host "  [WARN] RDP is enabled but NLA is NOT required - credential theft risk" -ForegroundColor Red
    }
    if ($rdpSettings.RDPEnabled -and $secLayer -lt 2) {
        Write-Host "  [WARN] RDP security layer is not SSL/TLS - downgrade attack risk" -ForegroundColor Yellow
    }

    $audit.RDPSettings = $rdpSettings
    Write-Host "  [OK] RDP settings collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "RDPSettings"; Error = $_.Exception.Message }
}

# =====================================================
# 39. TLS/SSL CONFIGURATION
# =====================================================
Write-Host ""
Write-Host "=== 39. TLS/SSL CONFIGURATION ===" -ForegroundColor Cyan
Write-Host "  Running: Registry check for TLS protocol versions"
try {
    $tlsVersions = @('SSL 2.0', 'SSL 3.0', 'TLS 1.0', 'TLS 1.1', 'TLS 1.2', 'TLS 1.3')
    $audit.TLSConfig = [ordered]@{}

    foreach ($ver in $tlsVersions) {
        $serverPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$ver\Server"
        $enabled = $null
        if (Test-Path $serverPath) {
            $enabled = (Get-ItemProperty $serverPath -ErrorAction SilentlyContinue).Enabled
        }
        $status = if ($null -eq $enabled) { "OS Default" } elseif ($enabled -eq 0) { "Disabled" } else { "Enabled" }
        Write-Host "  $ver`: $status"
        $audit.TLSConfig[$ver] = $status

        if ($ver -in @('SSL 2.0', 'SSL 3.0', 'TLS 1.0', 'TLS 1.1') -and $status -ne 'Disabled') {
            Write-Host "    [WARN] $ver should be explicitly DISABLED for compliance" -ForegroundColor Yellow
        }
    }
    Write-Host "  [OK] TLS config collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "TLSConfig"; Error = $_.Exception.Message }
}

# =====================================================
# 40. CREDENTIAL GUARD / DEVICE GUARD
# =====================================================
Write-Host ""
Write-Host "=== 40. CREDENTIAL GUARD / DEVICE GUARD ===" -ForegroundColor Cyan
Write-Host "  Running: Get-CimInstance Win32_DeviceGuard"
try {
    $dg = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -ErrorAction Stop
    $cgStatus = switch ($dg.SecurityServicesRunning) {
        { 1 -in $_ } { "Running" }
        default { "Not running" }
    }
    $vbsStatus = switch ($dg.VirtualizationBasedSecurityStatus) {
        0 { "Not enabled" } 1 { "Enabled but not running" } 2 { "Running" } default { "Unknown" }
    }

    $guardInfo = [ordered]@{
        VBSStatus = $vbsStatus
        CredentialGuard = $cgStatus
        SecurityServicesConfigured = @($dg.SecurityServicesConfigured)
        SecurityServicesRunning = @($dg.SecurityServicesRunning)
    }
    $guardInfo.GetEnumerator() | ForEach-Object {
        $val = if ($_.Value -is [array]) { $_.Value -join ", " } else { $_.Value }
        Write-Host "  $($_.Key): $val"
    }
    if ($cgStatus -ne "Running") {
        Write-Host "  [WARN] Credential Guard is not running - pass-the-hash risk" -ForegroundColor Yellow
    }
    $audit.CredentialGuard = $guardInfo
    Write-Host "  [OK] Credential/Device Guard checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "CredentialGuard"; Error = $_.Exception.Message }
}

# =====================================================
# 41. UAC SETTINGS
# =====================================================
Write-Host ""
Write-Host "=== 41. UAC SETTINGS ===" -ForegroundColor Cyan
Write-Host "  Running: Registry check for UAC"
try {
    $uacReg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction Stop
    $uacSettings = [ordered]@{
        EnableLUA = $uacReg.EnableLUA
        ConsentPromptBehaviorAdmin = switch ($uacReg.ConsentPromptBehaviorAdmin) {
            0 {"Elevate without prompting"} 1 {"Prompt for credentials on secure desktop"}
            2 {"Prompt for consent on secure desktop"} 3 {"Prompt for credentials"}
            4 {"Prompt for consent"} 5 {"Prompt for consent for non-Windows binaries"} default {"Unknown"}
        }
        PromptOnSecureDesktop = $uacReg.PromptOnSecureDesktop
        FilterAdministratorToken = $uacReg.FilterAdministratorToken
    }
    $uacSettings.GetEnumerator() | ForEach-Object { Write-Host "  $($_.Key): $($_.Value)" }

    if ($uacReg.EnableLUA -ne 1) {
        Write-Host "  [WARN] UAC is DISABLED - all processes run with full admin rights" -ForegroundColor Red
    }
    if ($uacReg.ConsentPromptBehaviorAdmin -eq 0) {
        Write-Host "  [WARN] Admin elevation without prompting - malware risk" -ForegroundColor Yellow
    }
    $audit.UACSettings = $uacSettings
    Write-Host "  [OK] UAC settings collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "UACSettings"; Error = $_.Exception.Message }
}

# =====================================================
# 42. SCREEN LOCK / INACTIVITY TIMEOUT
# =====================================================
Write-Host ""
Write-Host "=== 42. SCREEN LOCK / INACTIVITY TIMEOUT ===" -ForegroundColor Cyan
Write-Host "  Running: Registry check for screen lock policy"
try {
    $screenLock = [ordered]@{}

    # Machine inactivity limit
    $inactivity = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction SilentlyContinue).InactivityTimeoutSecs
    $screenLock.InactivityTimeoutSecs = $inactivity
    if ($inactivity) {
        Write-Host "  Machine inactivity timeout: $inactivity seconds ($([math]::Round($inactivity/60)) minutes)"
    } else {
        Write-Host "  Machine inactivity timeout: Not configured"
        Write-Host "  [WARN] No machine inactivity timeout - HIPAA requires automatic session lock" -ForegroundColor Yellow
    }

    # Screensaver settings (applied via GPO)
    $ssActive = (Get-ItemProperty 'HKCU:\Control Panel\Desktop' -ErrorAction SilentlyContinue).ScreenSaveActive
    $ssTimeout = (Get-ItemProperty 'HKCU:\Control Panel\Desktop' -ErrorAction SilentlyContinue).ScreenSaveTimeOut
    $ssSecure = (Get-ItemProperty 'HKCU:\Control Panel\Desktop' -ErrorAction SilentlyContinue).ScreenSaverIsSecure
    $screenLock.ScreenSaverActive = $ssActive
    $screenLock.ScreenSaverTimeout = $ssTimeout
    $screenLock.ScreenSaverSecure = $ssSecure

    Write-Host "  Screensaver active: $ssActive, Timeout: $ssTimeout sec, Require password: $ssSecure"

    $audit.ScreenLockPolicy = $screenLock
    Write-Host "  [OK] Screen lock settings collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ScreenLock"; Error = $_.Exception.Message }
}

# =====================================================
# 43. USB STORAGE POLICY
# =====================================================
Write-Host ""
Write-Host "=== 43. USB STORAGE POLICY ===" -ForegroundColor Cyan
Write-Host "  Running: Registry check for USB storage restrictions"
try {
    $usbStorage = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\USBSTOR' -ErrorAction Stop).Start
    $usbStatus = switch ($usbStorage) {
        3 { "Enabled (normal)" } 4 { "Disabled" } default { "Unknown ($usbStorage)" }
    }
    Write-Host "  USB Storage: $usbStatus"

    # Check for GPO-based USB restrictions
    $usbGPO = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices' -ErrorAction SilentlyContinue
    if ($usbGPO) {
        Write-Host "  GPO removable storage restrictions detected"
    }

    if ($usbStorage -eq 3 -and -not $usbGPO) {
        Write-Host "  [WARN] USB storage is unrestricted - data exfiltration risk / HIPAA concern" -ForegroundColor Yellow
    }

    $audit.USBStoragePolicy = [ordered]@{ USBSTORStart = $usbStorage; Status = $usbStatus; GPORestrictions = ($null -ne $usbGPO) }
    Write-Host "  [OK] USB storage policy collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "USBStorage"; Error = $_.Exception.Message }
}

# =====================================================
# 44. FAILED LOGON ATTEMPTS (last 7 days)
# =====================================================
Write-Host ""
Write-Host "=== 44. FAILED LOGON ATTEMPTS (last 7 days) ===" -ForegroundColor Cyan
Write-Host "  Running: Get-WinEvent Security log - Event ID 4625"
$failedSince = (Get-Date).AddDays(-7)
$failedLogons = @(Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$failedSince} -MaxEvents 100 -ErrorAction SilentlyContinue)
if ($failedLogons.Count -gt 0) {
    # Group by target account
    $grouped = $failedLogons | ForEach-Object {
        $xml = [xml]$_.ToXml()
        $targetUser = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
        $sourceIP = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
        [PSCustomObject]@{ Time = $_.TimeCreated; User = $targetUser; SourceIP = $sourceIP }
    }
    $summary = $grouped | Group-Object User | Sort-Object Count -Descending | Select-Object Count, Name
    Write-Host "  $($failedLogons.Count) failed logon attempts in last 7 days:" -ForegroundColor Yellow
    $summary | ForEach-Object { Write-Host "    $($_.Name): $($_.Count) failures" -ForegroundColor Yellow }

    $audit.FailedLogons = [ordered]@{
        TotalCount = $failedLogons.Count
        ByUser = @($summary | ForEach-Object { [ordered]@{ User = $_.Name; Count = $_.Count } })
        Recent = @($grouped | Select-Object -First 20 | ForEach-Object {
            [ordered]@{ Time = $_.Time.ToString("yyyy-MM-dd HH:mm:ss"); User = $_.User; SourceIP = $_.SourceIP }
        })
    }
} else {
    Write-Host "  No failed logon attempts" -ForegroundColor Green
    $audit.FailedLogons = [ordered]@{ TotalCount = 0 }
}
Write-Host "  [OK] Failed logons collected" -ForegroundColor Green

# =====================================================
# 45. SECURITY QUICK CHECKS
# =====================================================
Write-Host ""
Write-Host "=== 45. SECURITY QUICK CHECKS ===" -ForegroundColor Cyan
$audit.SecurityChecks = [ordered]@{}

# --- Password Policy ---
Write-Host "  Running: Get-ADDefaultDomainPasswordPolicy"
try {
    $pp = Get-ADDefaultDomainPasswordPolicy -ErrorAction Stop
    $passPolicy = [ordered]@{
        MinLength            = $pp.MinPasswordLength
        ComplexityEnabled    = $pp.ComplexityEnabled
        MaxAge               = "$($pp.MaxPasswordAge)"
        MinAge               = "$($pp.MinPasswordAge)"
        HistoryCount         = $pp.PasswordHistoryCount
        LockoutThreshold     = $pp.LockoutThreshold
        LockoutDuration      = "$($pp.LockoutDuration)"
        LockoutWindow        = "$($pp.LockoutObservationWindow)"
        ReversibleEncryption = $pp.ReversibleEncryptionEnabled
    }
    $passPolicy.GetEnumerator() | ForEach-Object { Write-Host "    $($_.Key): $($_.Value)" }

    # Flag weak settings
    $passIssues = @()
    if ($pp.MinPasswordLength -lt 12) { $passIssues += "Min password length is $($pp.MinPasswordLength) - recommend 12+" }
    if (-not $pp.ComplexityEnabled) { $passIssues += "Complexity is DISABLED" }
    if ($pp.LockoutThreshold -eq 0) { $passIssues += "Account lockout is DISABLED - brute force risk" }
    if ($pp.ReversibleEncryptionEnabled) { $passIssues += "Reversible encryption is ENABLED - critical risk" }
    if ($pp.PasswordHistoryCount -lt 12) { $passIssues += "Password history is only $($pp.PasswordHistoryCount) - recommend 12+" }

    $passPolicy.Issues = $passIssues
    if ($passIssues) {
        $passIssues | ForEach-Object { Write-Host "    [WARN] $_" -ForegroundColor Yellow }
    }
    $audit.SecurityChecks.PasswordPolicy = $passPolicy
    Write-Host "  [OK] Password policy checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "PasswordPolicy"; Error = $_.Exception.Message }
}

# --- Fine-Grained Password Policies ---
Write-Host "  Running: Get-ADFineGrainedPasswordPolicy"
try {
    $fgpp = Get-ADFineGrainedPasswordPolicy -Filter * -ErrorAction Stop
    if ($fgpp) {
        $audit.SecurityChecks.FineGrainedPolicies = @($fgpp | ForEach-Object {
            Write-Host "    FGPP: $($_.Name) - MinLen=$($_.MinPasswordLength), MaxAge=$($_.MaxPasswordAge), AppliesTo=$($_.AppliesTo -join ', ')"
            [ordered]@{
                Name = $_.Name; Precedence = $_.Precedence; MinLength = $_.MinPasswordLength
                MaxAge = "$($_.MaxPasswordAge)"; LockoutThreshold = $_.LockoutThreshold
                AppliesTo = @($_.AppliesTo)
            }
        })
        Write-Host "  [OK] Fine-grained policies checked" -ForegroundColor Green
    } else {
        Write-Host "    No fine-grained password policies configured"
        $audit.SecurityChecks.FineGrainedPolicies = @()
    }
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "FineGrainedPolicies"; Error = $_.Exception.Message }
}

# --- LAPS Status ---
Write-Host "  Running: LAPS check"
try {
    $lapsCheck = [ordered]@{ LegacyLAPS = $false; WindowsLAPS = $false }

    # Check for Legacy LAPS schema extension
    $schemaNC = (Get-ADRootDSE).schemaNamingContext
    $lapsSchema = Get-ADObject -SearchBase $schemaNC -Filter "Name -eq 'ms-Mcs-AdmPwd'" -ErrorAction SilentlyContinue
    if ($lapsSchema) {
        $lapsCheck.LegacyLAPS = $true
        Write-Host "    Legacy LAPS (ms-Mcs-AdmPwd): INSTALLED" -ForegroundColor Green
        # Count computers with LAPS passwords
        $lapsComputers = (Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwd | Where-Object { $_.'ms-Mcs-AdmPwd' }).Count
        $totalComputers = (Get-ADComputer -Filter *).Count
        $lapsCheck.LegacyLAPSCoverage = "$lapsComputers / $totalComputers computers"
        Write-Host "    Coverage: $lapsComputers / $totalComputers computers have LAPS passwords"
    } else {
        Write-Host "    Legacy LAPS: NOT INSTALLED" -ForegroundColor Yellow
    }

    # Check for Windows LAPS schema
    $winLapsSchema = Get-ADObject -SearchBase $schemaNC -Filter "Name -eq 'ms-LAPS-Password'" -ErrorAction SilentlyContinue
    if ($winLapsSchema) {
        $lapsCheck.WindowsLAPS = $true
        Write-Host "    Windows LAPS (ms-LAPS-Password): INSTALLED" -ForegroundColor Green
    } else {
        Write-Host "    Windows LAPS: NOT INSTALLED" -ForegroundColor Yellow
    }

    if (-not $lapsCheck.LegacyLAPS -and -not $lapsCheck.WindowsLAPS) {
        Write-Host "    [WARN] No LAPS deployment detected - local admin passwords are likely shared/static" -ForegroundColor Red
    }
    $audit.SecurityChecks.LAPS = $lapsCheck
    Write-Host "  [OK] LAPS checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "LAPS"; Error = $_.Exception.Message }
}

# --- SMBv1 + SMB Signing ---
Write-Host "  Running: SMB configuration check"
try {
    $smbConfig = Get-SmbServerConfiguration -ErrorAction Stop

    # SMBv1
    if ($smbConfig.EnableSMB1Protocol) {
        Write-Host "    [WARN] SMBv1 is ENABLED - known vulnerability, should be disabled" -ForegroundColor Red
    } else {
        Write-Host "    SMBv1: Disabled" -ForegroundColor Green
    }
    $audit.SecurityChecks.SMBv1Enabled = $smbConfig.EnableSMB1Protocol

    # SMB Signing
    $smbSigning = [ordered]@{
        RequireSecuritySignature = $smbConfig.RequireSecuritySignature
        EnableSecuritySignature  = $smbConfig.EnableSecuritySignature
        EncryptData              = $smbConfig.EncryptData
    }
    $smbSigning.GetEnumerator() | ForEach-Object { Write-Host "    $($_.Key): $($_.Value)" }
    if (-not $smbConfig.RequireSecuritySignature) {
        Write-Host "    [WARN] SMB signing is NOT required - relay attack risk" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.SMBSigning = $smbSigning
    Write-Host "  [OK] SMB config checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "SMBConfig"; Error = $_.Exception.Message }
}

# --- Machine Account Quota ---
Write-Host "  Running: Machine Account Quota check"
try {
    $maq = (Get-ADObject -Identity $script:domain.DistinguishedName -Properties ms-DS-MachineAccountQuota).'ms-DS-MachineAccountQuota'
    Write-Host "    Machine Account Quota: $maq"
    if ($maq -gt 0) {
        Write-Host "    [WARN] Any domain user can join up to $maq computers to the domain" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.MachineAccountQuota = $maq
    Write-Host "  [OK] Machine account quota checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "MachineAccountQuota"; Error = $_.Exception.Message }
}

# --- AD Recycle Bin ---
Write-Host "  Running: AD Recycle Bin check"
try {
    $recycleBin = Get-ADOptionalFeature -Filter "Name -eq 'Recycle Bin Feature'" -ErrorAction Stop
    if ($recycleBin.EnabledScopes.Count -gt 0) {
        Write-Host "    AD Recycle Bin: ENABLED" -ForegroundColor Green
        $audit.SecurityChecks.RecycleBinEnabled = $true
    } else {
        Write-Host "    [WARN] AD Recycle Bin is NOT enabled - accidental deletions are unrecoverable" -ForegroundColor Yellow
        $audit.SecurityChecks.RecycleBinEnabled = $false
    }
    Write-Host "  [OK] Recycle Bin checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "RecycleBin"; Error = $_.Exception.Message }
}

# --- Accounts with Password Never Expires ---
Write-Host "  Running: Accounts with PasswordNeverExpires"
try {
    $neverExpire = Get-ADUser -Filter { PasswordNeverExpires -eq $true -and Enabled -eq $true } -Properties PasswordNeverExpires, PasswordLastSet |
        Select-Object Name, SamAccountName, PasswordLastSet
    if ($neverExpire) {
        Write-Host "    [WARN] $($neverExpire.Count) enabled accounts have PasswordNeverExpires:" -ForegroundColor Yellow
        $neverExpire | ForEach-Object { Write-Host "      $($_.SamAccountName) - Password set: $($_.PasswordLastSet)" -ForegroundColor Yellow }
    } else {
        Write-Host "    No enabled accounts with PasswordNeverExpires" -ForegroundColor Green
    }
    $audit.SecurityChecks.PasswordNeverExpires = @($neverExpire | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null } }
    })
    Write-Host "  [OK] PasswordNeverExpires checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "PasswordNeverExpires"; Error = $_.Exception.Message }
}

# --- Stale Passwords (not changed in 90+ days) ---
Write-Host "  Running: Stale passwords check - enabled accounts, 90+ days"
try {
    $staleDate = (Get-Date).AddDays(-90)
    $stalePasswords = Get-ADUser -Filter { Enabled -eq $true -and PasswordLastSet -lt $staleDate } -Properties PasswordLastSet |
        Select-Object Name, SamAccountName, PasswordLastSet | Sort-Object PasswordLastSet
    if ($stalePasswords) {
        Write-Host "    [WARN] $($stalePasswords.Count) enabled accounts have passwords older than 90 days:" -ForegroundColor Yellow
        $stalePasswords | Select-Object -First 20 | ForEach-Object {
            Write-Host "      $($_.SamAccountName) - Last set: $($_.PasswordLastSet)" -ForegroundColor Yellow
        }
        if ($stalePasswords.Count -gt 20) { Write-Host "      ... and $($stalePasswords.Count - 20) more" -ForegroundColor Yellow }
    } else {
        Write-Host "    All enabled accounts have passwords changed within 90 days" -ForegroundColor Green
    }
    $audit.SecurityChecks.StalePasswords = @($stalePasswords | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null } }
    })
    Write-Host "  [OK] Stale passwords checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "StalePasswords"; Error = $_.Exception.Message }
}

# --- Inactive Accounts (not logged in 90+ days) ---
Write-Host "  Running: Inactive accounts check - enabled, no logon 90+ days"
try {
    $inactiveDate = (Get-Date).AddDays(-90)
    $inactive = Get-ADUser -Filter { Enabled -eq $true -and LastLogonDate -lt $inactiveDate } -Properties LastLogonDate |
        Select-Object Name, SamAccountName, LastLogonDate | Sort-Object LastLogonDate
    if ($inactive) {
        Write-Host "    [WARN] $($inactive.Count) enabled accounts have not logged in for 90+ days:" -ForegroundColor Yellow
        $inactive | Select-Object -First 20 | ForEach-Object {
            Write-Host "      $($_.SamAccountName) - Last logon: $($_.LastLogonDate)" -ForegroundColor Yellow
        }
        if ($inactive.Count -gt 20) { Write-Host "      ... and $($inactive.Count - 20) more" -ForegroundColor Yellow }
    } else {
        Write-Host "    All enabled accounts have logged in within 90 days" -ForegroundColor Green
    }
    $audit.SecurityChecks.InactiveAccounts = @($inactive | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; LastLogon = if ($_.LastLogonDate) { $_.LastLogonDate.ToString("yyyy-MM-dd") } else { $null } }
    })
    Write-Host "  [OK] Inactive accounts checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "InactiveAccounts"; Error = $_.Exception.Message }
}

# --- Locked Accounts ---
Write-Host "  Running: Locked accounts check"
try {
    $locked = Search-ADAccount -LockedOut | Where-Object { $_.Enabled } | Select-Object Name, SamAccountName, LastLogonDate
    if ($locked) {
        Write-Host "    [WARN] $($locked.Count) accounts are currently LOCKED OUT:" -ForegroundColor Yellow
        $locked | ForEach-Object { Write-Host "      $($_.SamAccountName)" -ForegroundColor Yellow }
    } else {
        Write-Host "    No locked accounts" -ForegroundColor Green
    }
    $audit.SecurityChecks.LockedAccounts = @($locked | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName }
    })
    Write-Host "  [OK] Locked accounts checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "LockedAccounts"; Error = $_.Exception.Message }
}

# --- AdminSDHolder / Protected Users ---
Write-Host "  Running: Protected Users group check"
try {
    $protectedMembers = Get-ADGroupMember "Protected Users" -ErrorAction Stop | Select-Object Name, SamAccountName
    if ($protectedMembers) {
        Write-Host "    Protected Users members:" -ForegroundColor Green
        $protectedMembers | ForEach-Object { Write-Host "      $($_.SamAccountName)" }
    } else {
        Write-Host "    [WARN] Protected Users group is EMPTY - admin accounts should be added" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.ProtectedUsers = @($protectedMembers | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName }
    })
    Write-Host "  [OK] Protected Users checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ProtectedUsers"; Error = $_.Exception.Message }
}

# --- AdminSDHolder protected accounts ---
Write-Host "  Running: AdminSDHolder check"
try {
    $adminSDHolder = Get-ADUser -Filter { AdminCount -eq 1 -and Enabled -eq $true } -Properties AdminCount, MemberOf |
        Select-Object Name, SamAccountName
    if ($adminSDHolder) {
        Write-Host "    Accounts with AdminCount=1 - $($adminSDHolder.Count) accounts:" -ForegroundColor Yellow
        $adminSDHolder | ForEach-Object { Write-Host "      $($_.SamAccountName)" }
    }
    $audit.SecurityChecks.AdminSDHolder = @($adminSDHolder | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName }
    })
    Write-Host "  [OK] AdminSDHolder checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "AdminSDHolder"; Error = $_.Exception.Message }
}

# --- Kerberoastable Accounts (SPNs on user accounts) ---
Write-Host "  Running: Kerberoastable accounts check"
try {
    $spnUsers = Get-ADUser -Filter { ServicePrincipalName -like "*" -and Enabled -eq $true } -Properties ServicePrincipalName, PasswordLastSet |
        Where-Object { $_.SamAccountName -ne 'krbtgt' } |
        Select-Object Name, SamAccountName, PasswordLastSet, ServicePrincipalName
    if ($spnUsers) {
        Write-Host "    [WARN] $($spnUsers.Count) enabled user accounts have SPNs - kerberoastable:" -ForegroundColor Yellow
        $spnUsers | ForEach-Object {
            Write-Host "      $($_.SamAccountName) - Password set: $($_.PasswordLastSet) - SPN: $($_.ServicePrincipalName[0])" -ForegroundColor Yellow
        }
    } else {
        Write-Host "    No kerberoastable user accounts found" -ForegroundColor Green
    }
    $audit.SecurityChecks.Kerberoastable = @($spnUsers | ForEach-Object {
        [ordered]@{
            Name = $_.Name; SAM = $_.SamAccountName
            PasswordLastSet = if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString("yyyy-MM-dd") } else { $null }
            SPNs = @($_.ServicePrincipalName)
        }
    })
    Write-Host "  [OK] Kerberoastable checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "Kerberoastable"; Error = $_.Exception.Message }
}

# --- ASREPRoastable Accounts (PreAuth disabled) ---
Write-Host "  Running: ASREPRoastable accounts check"
try {
    $asrep = Get-ADUser -Filter { DoesNotRequirePreAuth -eq $true -and Enabled -eq $true } -Properties DoesNotRequirePreAuth |
        Select-Object Name, SamAccountName
    if ($asrep) {
        Write-Host "    [WARN] $($asrep.Count) accounts do NOT require Kerberos pre-auth - ASREPRoastable:" -ForegroundColor Red
        $asrep | ForEach-Object { Write-Host "      $($_.SamAccountName)" -ForegroundColor Red }
    } else {
        Write-Host "    No ASREPRoastable accounts found" -ForegroundColor Green
    }
    $audit.SecurityChecks.ASREPRoastable = @($asrep | ForEach-Object {
        [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName }
    })
    Write-Host "  [OK] ASREPRoastable checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "ASREPRoastable"; Error = $_.Exception.Message }
}

# --- NULL Sessions ---
Write-Host "  Running: NULL session check"
try {
    $lsaReg = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' -ErrorAction Stop
    $restrictAnon = $lsaReg.RestrictAnonymous
    $restrictAnonSAM = $lsaReg.RestrictAnonymousSAM
    $everyoneAnon = $lsaReg.EveryoneIncludesAnonymous

    $nullSession = [ordered]@{
        RestrictAnonymous           = $restrictAnon
        RestrictAnonymousSAM        = $restrictAnonSAM
        EveryoneIncludesAnonymous   = $everyoneAnon
    }
    $nullSession.GetEnumerator() | ForEach-Object { Write-Host "    $($_.Key): $($_.Value)" }

    if ($everyoneAnon -eq 1) {
        Write-Host "    [WARN] EveryoneIncludesAnonymous is ENABLED - anonymous users have Everyone access" -ForegroundColor Red
    }
    if ($restrictAnon -eq 0) {
        Write-Host "    [WARN] RestrictAnonymous is 0 - anonymous enumeration of shares/accounts possible" -ForegroundColor Yellow
    }

    $audit.SecurityChecks.NullSessions = $nullSession
    Write-Host "  [OK] NULL sessions checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "NullSessions"; Error = $_.Exception.Message }
}

# --- Insecure DNS Zones ---
Write-Host "  Running: Insecure DNS zones check"
try {
    Import-Module DnsServer -ErrorAction Stop
    $insecureZones = Get-DnsServerZone | Where-Object { $_.DynamicUpdate -eq 'NonsecureAndSecure' -and -not $_.IsAutoCreated }
    if ($insecureZones) {
        Write-Host "    [WARN] DNS zones allowing nonsecure dynamic updates:" -ForegroundColor Yellow
        $insecureZones | ForEach-Object { Write-Host "      $($_.ZoneName)" -ForegroundColor Yellow }
    } else {
        Write-Host "    All DNS zones use secure-only dynamic updates" -ForegroundColor Green
    }
    $audit.SecurityChecks.InsecureDNSZones = @($insecureZones | ForEach-Object { $_.ZoneName })
    Write-Host "  [OK] DNS zone security checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "InsecureDNSZones"; Error = $_.Exception.Message }
}

# --- SYSVOL Password Search (GPP cpassword) ---
Write-Host "  Running: SYSVOL GPP password search"
try {
    $dnsDomain = if ($env:USERDNSDOMAIN) { $env:USERDNSDOMAIN } elseif ($script:domain) { $script:domain.DNSRoot } else { (Get-ADDomain).DNSRoot }
    $sysvolPath = "\\$dnsDomain\SYSVOL\$dnsDomain"
    $gppFiles = Get-ChildItem -Path $sysvolPath -Recurse -Include "*.xml" -ErrorAction Stop
    $cpasswordFiles = @()
    foreach ($file in $gppFiles) {
        $content = Get-Content $file.FullName -ErrorAction SilentlyContinue
        if ($content -match 'cpassword') {
            $cpasswordFiles += $file.FullName
            Write-Host "    [CRITICAL] GPP password found in: $($file.FullName)" -ForegroundColor Red
        }
    }
    if ($cpasswordFiles.Count -eq 0) {
        Write-Host "    No GPP passwords found in SYSVOL" -ForegroundColor Green
    }
    $audit.SecurityChecks.GPPPasswords = $cpasswordFiles
    Write-Host "  [OK] SYSVOL password search complete" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "GPPPasswords"; Error = $_.Exception.Message }
}

# --- Old/EOL Operating Systems ---
Write-Host "  Running: End-of-life OS check"
try {
    $eolPatterns = @('Windows XP*', 'Windows Vista*', 'Windows 7*', 'Windows 8 *', 'Windows 8.1*',
        'Windows Server 2003*', 'Windows Server 2008*', 'Windows Server 2012 *', 'Windows Server 2012 R2*')
    $eolComputers = @()
    foreach ($pattern in $eolPatterns) {
        $found = Get-ADComputer -Filter "OperatingSystem -like '$pattern' -and Enabled -eq 'True'" -Properties OperatingSystem, LastLogonDate -ErrorAction SilentlyContinue
        if ($found) { $eolComputers += $found }
    }
    if ($eolComputers) {
        Write-Host "    [WARN] $($eolComputers.Count) enabled computers running END OF LIFE operating systems:" -ForegroundColor Red
        $eolComputers | ForEach-Object { Write-Host "      $($_.Name) - $($_.OperatingSystem) - Last logon: $($_.LastLogonDate)" -ForegroundColor Red }
    } else {
        Write-Host "    No end-of-life operating systems detected" -ForegroundColor Green
    }
    $audit.SecurityChecks.EOLComputers = @($eolComputers | ForEach-Object {
        [ordered]@{ Name = $_.Name; OS = $_.OperatingSystem; LastLogon = if ($_.LastLogonDate) { $_.LastLogonDate.ToString("yyyy-MM-dd") } else { $null } }
    })
    Write-Host "  [OK] EOL OS check complete" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "EOLComputers"; Error = $_.Exception.Message }
}

# --- Functional Level ---
Write-Host "  Running: Functional level assessment"
try {
    $domainFL = if ($script:domain) { $script:domain.DomainMode } else { (Get-ADDomain).DomainMode }
    $forestFL = if ($script:forest) { $script:forest.ForestMode } else { (Get-ADForest).ForestMode }
    Write-Host "    Domain Functional Level: $domainFL"
    Write-Host "    Forest Functional Level: $forestFL"

    $outdatedLevels = @('Windows2003Domain', 'Windows2008Domain', 'Windows2008R2Domain', 'Windows2012Domain', 'Windows2012R2Domain',
        'Windows2003Forest', 'Windows2008Forest', 'Windows2008R2Forest', 'Windows2012Forest', 'Windows2012R2Forest')
    if ($domainFL -in $outdatedLevels -or $forestFL -in $outdatedLevels) {
        Write-Host "    [WARN] Functional level is outdated - limits security features like Protected Users, auth silos" -ForegroundColor Yellow
    }

    $audit.SecurityChecks.FunctionalLevel = [ordered]@{ Domain = "$domainFL"; Forest = "$forestFL" }
    Write-Host "  [OK] Functional level checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "FunctionalLevel"; Error = $_.Exception.Message }
}

# --- Replication Type (FRS vs DFSR) ---
Write-Host "  Running: SYSVOL replication type check"
try {
    $dn = if ($script:domain) { $script:domain.DistinguishedName } else { (Get-ADDomain).DistinguishedName }
    $replType = (Get-ADObject "CN=DFSR-GlobalSettings,CN=System,$dn" -ErrorAction Stop)
    Write-Host "    SYSVOL Replication: DFSR" -ForegroundColor Green
    $audit.SecurityChecks.SYSVOLReplication = "DFSR"
}
catch {
    Write-Host "    SYSVOL Replication: FRS (legacy - should migrate to DFSR)" -ForegroundColor Yellow
    $audit.SecurityChecks.SYSVOLReplication = "FRS"
}
Write-Host "  [OK] Replication type checked" -ForegroundColor Green

# --- LDAP Signing + Channel Binding ---
Write-Host "  Running: LDAP signing and channel binding check"
try {
    $ntdsReg = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction Stop

    $ldapSigning = $ntdsReg.LDAPServerIntegrity
    $ldapSigningText = switch ($ldapSigning) {
        0 { "None (not required)" } 1 { "Require signing" } 2 { "Require signing" } default { "Unknown ($ldapSigning)" }
    }
    Write-Host "    LDAP Server Signing: $ldapSigningText"
    if ($ldapSigning -eq 0 -or $null -eq $ldapSigning) {
        Write-Host "    [WARN] LDAP signing is NOT required - MITM risk" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.LDAPSigning = $ldapSigningText

    $ldapCB = $ntdsReg.LdapEnforceChannelBinding
    $ldapCBText = switch ($ldapCB) {
        0 { "Never" } 1 { "When supported" } 2 { "Always" } default { "Not configured (0)" }
    }
    Write-Host "    LDAP Channel Binding: $ldapCBText"
    if ($ldapCB -ne 2) {
        Write-Host "    [WARN] LDAP channel binding is not set to Always - credential relay risk" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.LDAPChannelBinding = $ldapCBText
    Write-Host "  [OK] LDAP settings checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "LDAPSettings"; Error = $_.Exception.Message }
}

# --- DCs Not Owned by Domain Admins ---
Write-Host "  Running: DC ownership check"
try {
    $dcsNotOwned = Get-ADComputer -Filter { PrimaryGroupID -eq 516 } -Properties nTSecurityDescriptor -ErrorAction Stop | ForEach-Object {
        $owner = $_.nTSecurityDescriptor.Owner
        if ($owner -notmatch 'Domain Admins') {
            [PSCustomObject]@{ Name = $_.Name; Owner = $owner }
        }
    }
    if ($dcsNotOwned) {
        Write-Host "    [WARN] DCs not owned by Domain Admins:" -ForegroundColor Yellow
        $dcsNotOwned | ForEach-Object { Write-Host "      $($_.Name) owned by $($_.Owner)" -ForegroundColor Yellow }
    } else {
        Write-Host "    All DCs owned by Domain Admins" -ForegroundColor Green
    }
    $audit.SecurityChecks.DCsNotOwnedByDA = @($dcsNotOwned | ForEach-Object {
        [ordered]@{ Name = $_.Name; Owner = $_.Owner }
    })
    Write-Host "  [OK] DC ownership checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "DCOwnership"; Error = $_.Exception.Message }
}

# --- Recent Changes (last 30 days) ---
Write-Host "  Running: Recent AD changes - last 30 days"
try {
    $recentDate = (Get-Date).AddDays(-30)
    $newUsers = Get-ADUser -Filter { WhenCreated -gt $recentDate } -Properties WhenCreated |
        Select-Object Name, SamAccountName, WhenCreated
    $newGroups = Get-ADGroup -Filter { WhenCreated -gt $recentDate } -Properties WhenCreated |
        Select-Object Name, WhenCreated
    $newComputers = Get-ADComputer -Filter { WhenCreated -gt $recentDate } -Properties WhenCreated |
        Select-Object Name, WhenCreated

    if ($newUsers) {
        Write-Host "    New users in last 30 days:" -ForegroundColor Yellow
        $newUsers | ForEach-Object { Write-Host "      $($_.SamAccountName) - Created: $($_.WhenCreated)" }
    }
    if ($newGroups) {
        Write-Host "    New groups in last 30 days:" -ForegroundColor Yellow
        $newGroups | ForEach-Object { Write-Host "      $($_.Name) - Created: $($_.WhenCreated)" }
    }
    if ($newComputers) {
        Write-Host "    New computers in last 30 days:" -ForegroundColor Yellow
        $newComputers | ForEach-Object { Write-Host "      $($_.Name) - Created: $($_.WhenCreated)" }
    }
    if (-not $newUsers -and -not $newGroups -and -not $newComputers) {
        Write-Host "    No new AD objects in the last 30 days" -ForegroundColor Green
    }

    $audit.SecurityChecks.RecentChanges = [ordered]@{
        NewUsers = @($newUsers | ForEach-Object { [ordered]@{ Name = $_.Name; SAM = $_.SamAccountName; Created = $_.WhenCreated.ToString("yyyy-MM-dd") } })
        NewGroups = @($newGroups | ForEach-Object { [ordered]@{ Name = $_.Name; Created = $_.WhenCreated.ToString("yyyy-MM-dd") } })
        NewComputers = @($newComputers | ForEach-Object { [ordered]@{ Name = $_.Name; Created = $_.WhenCreated.ToString("yyyy-MM-dd") } })
    }
    Write-Host "  [OK] Recent changes checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "RecentChanges"; Error = $_.Exception.Message }
}

# --- krbtgt Password Age ---
Write-Host "  Running: krbtgt password age check"
try {
    $krbtgt = Get-ADUser 'krbtgt' -Properties PasswordLastSet -ErrorAction Stop
    $krbtgtAge = ((Get-Date) - $krbtgt.PasswordLastSet).Days
    Write-Host "    krbtgt password last set: $($krbtgt.PasswordLastSet) - $krbtgtAge days ago"
    if ($krbtgtAge -gt 180) {
        Write-Host "    [WARN] krbtgt password is $krbtgtAge days old - should be rotated at least every 180 days" -ForegroundColor Yellow
    }
    $audit.SecurityChecks.KrbtgtPasswordAge = [ordered]@{
        LastSet = $krbtgt.PasswordLastSet.ToString("yyyy-MM-dd")
        AgeDays = $krbtgtAge
    }
    Write-Host "  [OK] krbtgt checked" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "KrbtgtAge"; Error = $_.Exception.Message }
}

# --- Audit Policy (are logons being audited?) ---
Write-Host "  Running: Audit policy check"
try {
    $auditPolicy = auditpol /get /category:* 2>&1
    $auditPolicy | ForEach-Object { Write-Host "    $_" }

    # Check critical audit settings
    $audit.SecurityChecks.AuditPolicyRaw = @($auditPolicy | ForEach-Object { "$_".Trim() } | Where-Object { $_ })
    Write-Host "  [OK] Audit policy collected" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "AuditPolicy"; Error = $_.Exception.Message }
}

# --- Stopped Auto-Start Services ---
Write-Host "  Running: Auto-start services that are stopped"
try {
    $stoppedAuto = Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Status -ne 'Running' } |
        Select-Object Name, DisplayName, Status, StartType
    if ($stoppedAuto) {
        Write-Host "    [WARN] $($stoppedAuto.Count) auto-start services are NOT running:" -ForegroundColor Yellow
        $stoppedAuto | Format-Table -AutoSize
    } else {
        Write-Host "    All auto-start services are running" -ForegroundColor Green
    }
    $audit.SecurityChecks.StoppedAutoServices = @($stoppedAuto | ForEach-Object {
        [ordered]@{ Name = $_.Name; DisplayName = $_.DisplayName; Status = "$($_.Status)" }
    })
    Write-Host "  [OK] Service state check complete" -ForegroundColor Green
}
catch {
    Write-Host "  [FAIL] $($_.Exception.Message)" -ForegroundColor Red
    $audit._errors += @{ Section = "StoppedAutoServices"; Error = $_.Exception.Message }
}

Write-Host ""
Write-Host "  === Security Summary ===" -ForegroundColor Cyan
$totalWarnings = 0
if ($audit.SecurityChecks.PasswordPolicy.Issues) { $totalWarnings += $audit.SecurityChecks.PasswordPolicy.Issues.Count }
if ($audit.SecurityChecks.SMBv1Enabled) { $totalWarnings++ }
if ($audit.SecurityChecks.MachineAccountQuota -gt 0) { $totalWarnings++ }
if (-not $audit.SecurityChecks.RecycleBinEnabled) { $totalWarnings++ }
if ($audit.SecurityChecks.PasswordNeverExpires.Count -gt 0) { $totalWarnings++ }
if ($audit.SecurityChecks.Kerberoastable.Count -gt 0) { $totalWarnings++ }
if ($audit.SecurityChecks.ASREPRoastable.Count -gt 0) { $totalWarnings++ }
if ($audit.SecurityChecks.GPPPasswords.Count -gt 0) { $totalWarnings++ }
if ($audit.SecurityChecks.EOLComputers.Count -gt 0) { $totalWarnings++ }
if (-not $audit.SecurityChecks.LAPS.LegacyLAPS -and -not $audit.SecurityChecks.LAPS.WindowsLAPS) { $totalWarnings++ }

if ($totalWarnings -gt 0) {
    Write-Host "  $totalWarnings security findings detected - review warnings above" -ForegroundColor Yellow
} else {
    Write-Host "  No critical security findings" -ForegroundColor Green
}

# =====================================================
# DONE - SAVE JSON
# =====================================================
Write-Host ""
Write-Host "======================================="
Write-Host "      AUDIT COMPLETE"
Write-Host "======================================="

$errorCount = $audit._errors.Count
if ($errorCount -gt 0) {
    Write-Host "Completed with $errorCount section errors (see _errors in JSON)" -ForegroundColor Yellow
} else {
    Write-Host "All sections completed successfully" -ForegroundColor Green
}

Write-Host "JSON data: $JsonFile"
Write-Host "======================================="

# Save JSON
$audit | ConvertTo-Json -Depth 10 | Out-File $JsonFile -Encoding UTF8
