MAD-Console-End.ps1

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8

# ============================================================================
# SYNTHÈSE FINALE - Affichage des statistiques principales
# ============================================================================

Write-Host ""
Write-SectionHeader "📊 SYNTHÈSE DU PARC INFORMATIQUE"

# === ORDINATEURS ===
Write-Host ""
Write-Host " 💻 " -ForegroundColor Cyan -NoNewline
Write-Host "ORDINATEURS" -ForegroundColor White
Write-Counter "Total des ordinateurs" $totalcomputers "Cyan"
Write-Counter "Postes clients" $ClientList.Count "White"

# Calculer le nombre total de serveurs (hors clients)
# BUG22 FIX : calcul base sur les listes reelles au lieu de (total - clients)
# qui surestimait en incluant Divers et Unknown
$totalServers = $ServerList.Count
$serversWithOS = $ServerList.Count
$serversWithoutOS = 0

Write-Counter "Serveurs" $totalServers "White"

# === END OF LIFE ===
Write-Host ""
Write-Host " ⚠️ " -ForegroundColor Yellow -NoNewline
Write-Host "STATUT END OF LIFE" -ForegroundColor White

# Clients EoL
$clientSupported = @($ClientList | Where-Object { $_.EoL_Status -eq 'Supported' }).Count
$clientWarning = @($ClientList | Where-Object { $_.EoL_Status -eq $WarningStatusName }).Count
$clientEOL = @($ClientList | Where-Object { $_.EoL_Status -eq 'EOL' }).Count
$clientUnknown = @($ClientList | Where-Object { $_.EoL_Status -eq 'Unknown' }).Count

Write-Host " Clients:" -ForegroundColor Gray
Write-Counter " ✓ Supportés" $clientSupported "Green"
Write-Counter " ⚠ $WarningStatusName" $clientWarning "DarkYellow"
Write-Counter " ✗ EOL (fin de vie)" $clientEOL "Red"
Write-Counter " ? Inconnus" $clientUnknown "DarkGray"

# Serveurs EoL
$serverSupported = @($ServerList | Where-Object { $_.EoL_Status -eq 'Supported' }).Count
$serverWarning = @($ServerList | Where-Object { $_.EoL_Status -eq $WarningStatusName }).Count
$serverEOL = @($ServerList | Where-Object { $_.EoL_Status -eq 'EOL' }).Count
$serverUnknown = @($ServerList | Where-Object { $_.EoL_Status -eq 'Unknown' }).Count

Write-Host " Serveurs:" -ForegroundColor Gray
Write-Counter " ✓ Supportés" $serverSupported "Green"
Write-Counter " ⚠ $WarningStatusName" $serverWarning "DarkYellow"
Write-Counter " ✗ EOL (fin de vie)" $serverEOL "Red"
Write-Counter " ? Inconnus" $serverUnknown "DarkGray"

# === SYSTÈMES D'EXPLOITATION ===
Write-Host ""
Write-Host " 🖥️ " -ForegroundColor Cyan -NoNewline
Write-Host "TOP 5 SYSTÈMES D'EXPLOITATION" -ForegroundColor White

if ($ClientOSStats -and $ClientOSStats.Count -gt 0) {
    Write-Host " Clients:" -ForegroundColor Gray
    $ClientOSStats | Select-Object -First 5 | ForEach-Object {
        $percentage = [math]::Round(($_.Count / $ClientList.Count) * 100, 1)
        Write-Counter " $($_.Name) ($percentage%)" $_.Count "White"
    }
}

if ($ServerOSStats -and $ServerOSStats.Count -gt 0) {
    Write-Host " Serveurs:" -ForegroundColor Gray
    $ServerOSStats | Select-Object -First 5 | ForEach-Object {
        $percentage = [math]::Round(($_.Count / $ServerList.Count) * 100, 1)
        Write-Counter " $($_.Name) ($percentage%)" $_.Count "White"
    }
}

# === UTILISATEURS ===
Write-Host ""
Write-Host " 👥 " -ForegroundColor Cyan -NoNewline
Write-Host "UTILISATEURS" -ForegroundColor White
Write-Counter "Total des utilisateurs" $UserTable.Count "Cyan"

# Calculer les comptes activés/désactivés
$EnabledUsers = @($UserTable | Where-Object { $_.Enabled -eq $true }).Count
$DisabledUsers = @($UserTable | Where-Object { $_.Enabled -eq $false }).Count

if ($EnabledUsers -gt 0 -or $DisabledUsers -gt 0) {
    Write-Counter "Comptes activés" $EnabledUsers "Green"
    Write-Counter "Comptes désactivés" $DisabledUsers "DarkGray"
}

# === GROUPES ===
Write-Host ""
Write-Host " 👨‍👩‍👧‍👦 " -ForegroundColor Cyan -NoNewline
Write-Host "GROUPES" -ForegroundColor White
Write-Counter "Total des groupes" $totalgroups "Cyan"
Write-Counter "Groupes de sécurité" $SecurityCount "White"
Write-Counter "Groupes de distribution" $DistroCount "White"

# === GPO (GROUP POLICY) ===
if (!$nogpomod -and $GPOs) {
    Write-Host ""
    Write-Host " 📋 " -ForegroundColor Cyan -NoNewline
    Write-Host "GROUP POLICY OBJECTS (GPO)" -ForegroundColor White
    Write-Counter "Total des GPO" $GPOs.Count "Cyan"
    if ($GPO_Linked) {
        Write-Counter "GPO liées (actives)" $GPO_Linked.Count "Green"
    }
    if ($GPO_NotLinked -and $GPO_NotLinked.Count -gt 0) {
        Write-Counter "GPO non-liées (orphelines)" $GPO_NotLinked.Count "Yellow"
    }
    
    # Détails supplémentaires UNIQUEMENT avec -ShowSensitiveObjects
    if ((-not $LimitedView -or $ShowSensitiveObjects.IsPresent) -and $GPO_Details -and $GPO_Details.Count -gt 0) {
        Write-Host ""
        Write-Host " 🔓 " -ForegroundColor Yellow -NoNewline
        Write-Host "DÉTAILS GPO (mode ShowSensitiveObjects)" -ForegroundColor Yellow
        
        # Top 3 GPO les plus utilisées
        $topGPOs = $GPO_Details | Sort-Object 'Link Count' -Descending | Select-Object -First 3
        if ($topGPOs) {
            Write-Host " Top 3 GPO les plus appliquées:" -ForegroundColor Gray
            foreach ($gpo in $topGPOs) {
                Write-Host " • $($gpo.'GPO Name') " -ForegroundColor White -NoNewline
                Write-Host "($($gpo.'Link Count') OU/Sites)" -ForegroundColor DarkGray
            }
        }
    } elseif ($LimitedView -and -not $ShowSensitiveObjects.IsPresent) {
        Write-Host " ℹ️ Utilisez " -ForegroundColor DarkGray -NoNewline
        Write-Host "-ShowSensitiveObjects" -ForegroundColor Cyan -NoNewline
        Write-Host " pour voir les détails d'application des GPO" -ForegroundColor DarkGray
    }
}

# === TRUSTS (DOMAIN TRUSTS) ===
if ($TrustsStats.Total -gt 0) {
    Write-Host ""
    Write-Host " 🔗 " -ForegroundColor Cyan -NoNewline
    Write-Host "DOMAIN TRUSTS" -ForegroundColor White
    Write-Counter "Total des trusts" $TrustsStats.Total "Cyan"
    
    if ($TrustsStats.IntraForest -gt 0) {
        Write-Counter "Trusts internes (intra-forest)" $TrustsStats.IntraForest "Green"
    }
    
    if ($TrustsStats.External -gt 0) {
        Write-Counter "Trusts externes" $TrustsStats.External "Yellow"
        
        # Alertes de sécurité sur les trusts externes
        $unsecuredExternal = $TrustsStats.External - $TrustsStats.WithSIDFiltering
        if ($unsecuredExternal -gt 0) {
            Write-Host " ⚠️ " -ForegroundColor Red -NoNewline
            Write-Host "$unsecuredExternal trust(s) externe(s) sans SID Filtering" -ForegroundColor Red
        }
    }
    
    # Répartition par direction
    if ($TrustsStats.Bidirectional -gt 0 -or $TrustsStats.Inbound -gt 0 -or $TrustsStats.Outbound -gt 0) {
        if ($TrustsStats.Bidirectional -gt 0) {
            Write-Counter " ↔ Bidirectionnels" $TrustsStats.Bidirectional "White"
        }
        if ($TrustsStats.Inbound -gt 0) {
            Write-Counter " ← Entrants" $TrustsStats.Inbound "White"
        }
        if ($TrustsStats.Outbound -gt 0) {
            Write-Counter " → Sortants" $TrustsStats.Outbound "White"
        }
    }
}

# === ALERTES CRITIQUES ===
$totalWarnings = $clientWarning + $serverWarning
$totalEOL = $clientEOL + $serverEOL

# Calcul des alertes de sécurité
$alerts = @()

# 1. ALERTE EOL - Machines en fin de vie
if ($totalEOL -gt 0) {
    $alerts += [PSCustomObject]@{
        Level = "CRITIQUE"
        Type = "End-of-Life"
        Count = $totalEOL
        Message = "$totalEOL machine(s) en fin de vie (EOL) - Remplacement urgent requis!"
        Recommendation = "Planifier le remplacement ou la mise à niveau immédiate"
        ANSSI = "R14 - Maintenir les systèmes à jour"
    }
}

# 2. ALERTE EOL - Machines proche fin de support
if ($totalWarnings -gt 0) {
    $alerts += [PSCustomObject]@{
        Level = "ELEVE"
        Type = "Fin de support proche"
        Count = $totalWarnings
        Message = "$totalWarnings machine(s) arrivent en fin de support dans moins de $DaysBeforeEoL jours"
        Recommendation = "Prévoir la migration sous $DaysBeforeEoL jours"
        ANSSI = "R14 - Maintenir les systèmes à jour"
    }
}

# 3. ALERTE - Comptes administrateurs inactifs
if ($UserTable) {
    # BUG23 FIX : -match "Admin" matchait n'importe quel groupe contenant "Admin" (ex: "Administrateurs locaux bureau")
    # Désormais on filtre sur AdminCount=1 (attribut AD fiable) OU appartenance à un groupe admin standard
    $adminAccounts = @($UserTable | Where-Object {
        $_.AdminCount -eq 1 -or
        ($_.memberof -and ($_.memberof -match '(^|,)CN=(Domain Admins|Enterprise Admins|Schema Admins|Administrators|Admins du domaine|Administrateurs),'))
    })
    if ($adminAccounts.Count -gt 0) {
        $inactiveAdmins = @($adminAccounts | Where-Object { 
            $_.LastLogonDate -and ((Get-Date) - $_.LastLogonDate).Days -gt $AdminInactivityThreshold 
        }).Count
        
        $inactiveAdmins365 = @($adminAccounts | Where-Object {
            $_.LastLogonDate -and ((Get-Date) - $_.LastLogonDate).Days -gt 365
        }).Count
        if ($inactiveAdmins365 -gt 0) {
            $alerts += [PSCustomObject]@{
                Level = "CRITIQUE"
                Type = "Admins inactifs +365j"
                Count = $inactiveAdmins365
                Message = "$inactiveAdmins365 compte(s) admin inactif(s) depuis plus de 365 jours - risque eleve"
                Recommendation = "Desactiver immediatement ces comptes privilegies"
                ANSSI = "R12 - Limiter les comptes a privileges"
            }
        }
        if ($inactiveAdmins -gt 0) {
            $alerts += [PSCustomObject]@{
                Level = "ELEVE"
                Type = "Admins inactifs +$($AdminInactivityThreshold)j"
                Count = $inactiveAdmins
                Message = "$inactiveAdmins compte(s) administrateur(s) inactif(s) depuis plus de $AdminInactivityThreshold jours"
                Recommendation = "Desactiver ou supprimer les comptes admin inutilises"
                ANSSI = "R12 - Limiter les comptes a privileges"
            }
        }
    }
}

# 4. ALERTE - Comptes utilisateurs inactifs (seuils 180j/365j)
if ($UserTable) {
    $inactUsers365 = @($UserTable | Where-Object {
        $_.Enabled -eq $true -and $_.LastLogonDate -and
        ((Get-Date) - $_.LastLogonDate).Days -gt 365
    }).Count
    $inactUsers180 = @($UserTable | Where-Object {
        $_.Enabled -eq $true -and $_.LastLogonDate -and
        ((Get-Date) - $_.LastLogonDate).Days -gt 180 -and
        ((Get-Date) - $_.LastLogonDate).Days -le 365
    }).Count
    # BUG1 FIX : borne superieure dynamique — utilise $UserInactivityThreshold
    # au lieu de 180 hardcode. Couvre la plage ]$UserInactivityThreshold ; 180[
    # uniquement si $UserInactivityThreshold < 180 ; sinon la plage est vide
    # (ce qui est le comportement attendu : inactUsers180 prend le relais).
    $inactUsersBase = @($UserTable | Where-Object {
        $_.Enabled -eq $true -and $_.LastLogonDate -and
        ((Get-Date) - $_.LastLogonDate).Days -gt $UserInactivityThreshold -and
        ((Get-Date) - $_.LastLogonDate).Days -le $UserInactivityThreshold * 2
    }).Count
    if ($inactUsers365 -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "CRITIQUE"
            Type = "Utilisateurs inactifs +365j"
            Count = $inactUsers365
            Message = "$inactUsers365 compte(s) actif(s) sans connexion depuis plus de 365 jours"
            Recommendation = "Desactiver ou supprimer ces comptes immediatement"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
    if ($inactUsers180 -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "ELEVE"
            Type = "Utilisateurs inactifs +180j"
            Count = $inactUsers180
            Message = "$inactUsers180 compte(s) actif(s) sans connexion depuis 180 a 365 jours"
            Recommendation = "Verifier et desactiver les comptes inutilises"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
    if ($inactUsersBase -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "MOYEN"
            Type = "Utilisateurs inactifs +$($UserInactivityThreshold)j"
            Count = $inactUsersBase
            Message = "$inactUsersBase compte(s) actif(s) inactif(s) depuis $UserInactivityThreshold a 180 jours"
            Recommendation = "Surveiller et planifier la desactivation"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
}

# 5. ALERTE - Trop de comptes désactivés (pollution AD)
if ($UserTable -and $UserTable.Count -gt 0) {
    $disabledPercent = [math]::Round(($DisabledUsers / $UserTable.Count) * 100, 1)
    
    if ($disabledPercent -gt $DisabledAccountsThreshold) {
        $alerts += [PSCustomObject]@{
            Level = "INFO"
            Type = "Comptes désactivés"
            Count = $DisabledUsers
            Message = "$disabledPercent% de comptes désactivés ($DisabledUsers/$($UserTable.Count)) - Seuil: $DisabledAccountsThreshold%"
            Recommendation = "Nettoyer l'AD en supprimant les comptes désactivés obsolètes"
            ANSSI = "R11 - Gérer le cycle de vie des comptes"
        }
    }
}

# 6. ALERTE - Mots de passe n'expirant jamais
if ($UserTable) {
    $pwdNeverExpires = @($UserTable | Where-Object { $_.PasswordNeverExpires -eq $true -and $_.Enabled -eq $true }).Count
    
    if ($pwdNeverExpires -gt 0 -and $UserTable.Count -gt 0) {
        $pwdNeverExpiresPercent = [math]::Round(($pwdNeverExpires / $UserTable.Count) * 100, 1)
        
        if ($pwdNeverExpiresPercent -gt $PasswordNeverExpiresThreshold) {
            $alerts += [PSCustomObject]@{
                Level = "ELEVE"
                Type = "Mots de passe permanents"
                Count = $pwdNeverExpires
                Message = "$pwdNeverExpires compte(s) avec mot de passe n'expirant jamais ($pwdNeverExpiresPercent%) - Seuil: $PasswordNeverExpiresThreshold%"
                Recommendation = "Configurer une expiration de mot de passe (90-365 jours selon ANSSI)"
                ANSSI = "R27 - Politique de mots de passe robuste"
            }
        }
    }
}

# 7. ALERTE - Ordinateurs inactifs (seuils 180j/365j)
if ($ComputersTable) {
    $inactPC365 = @($ComputersTable | Where-Object {
        $_.LastLogonDate -and ((Get-Date) - $_.LastLogonDate).Days -gt 365
    }).Count
    $inactPC180 = @($ComputersTable | Where-Object {
        $_.LastLogonDate -and ((Get-Date) - $_.LastLogonDate).Days -gt 180 -and
        ((Get-Date) - $_.LastLogonDate).Days -le 365
    }).Count
    # BUG1 FIX (meme pattern) : borne superieure dynamique basee sur $ComputerInactivityThreshold
    $inactPCBase = @($ComputersTable | Where-Object {
        $_.LastLogonDate -and ((Get-Date) - $_.LastLogonDate).Days -gt $ComputerInactivityThreshold -and
        ((Get-Date) - $_.LastLogonDate).Days -le $ComputerInactivityThreshold * 2
    }).Count
    if ($inactPC365 -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "CRITIQUE"
            Type = "Ordinateurs inactifs +365j"
            Count = $inactPC365
            Message = "$inactPC365 ordinateur(s) sans connexion depuis plus de 365 jours"
            Recommendation = "Desactiver ou supprimer ces comptes machine immediatement"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
    if ($inactPC180 -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "ELEVE"
            Type = "Ordinateurs inactifs +180j"
            Count = $inactPC180
            Message = "$inactPC180 ordinateur(s) sans connexion depuis 180 a 365 jours"
            Recommendation = "Verifier et nettoyer les comptes ordinateur obsoletes"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
    if ($inactPCBase -gt 0) {
        $alerts += [PSCustomObject]@{
            Level = "MOYEN"
            Type = "Ordinateurs inactifs +$($ComputerInactivityThreshold)j"
            Count = $inactPCBase
            Message = "$inactPCBase ordinateur(s) inactif(s) depuis $ComputerInactivityThreshold a 180 jours"
            Recommendation = "Surveiller et planifier le nettoyage"
            ANSSI = "R11 - Gerer le cycle de vie des comptes"
        }
    }
}

# 8. ALERTE - Groupes vides (pollution)
if ($totalgroups -gt 0) {
    $emptyGroupsPercent = [math]::Round(($Groupswithnomembership / $totalgroups) * 100, 1)
    
    if ($emptyGroupsPercent -gt $EmptyGroupsThreshold) {
        $alerts += [PSCustomObject]@{
            Level = "INFO"
            Type = "Groupes vides"
            Count = $Groupswithnomembership
            Message = "$emptyGroupsPercent% de groupes vides ($Groupswithnomembership/$totalgroups) - Seuil: $EmptyGroupsThreshold%"
            Recommendation = "Supprimer les groupes vides pour simplifier la gestion"
            ANSSI = "R13 - Minimiser les privilèges"
        }
    }
}

# Affichage des alertes
if ($alerts.Count -gt 0) {
    Write-SectionHeader "🔐 ALERTES DE SECURITE — ANSSI Hygiene informatique"
    Write-Host ""
    
    # Trier par niveau de criticité
    $alertOrder = @{ "CRITIQUE" = 1; "ELEVE" = 2; "MOYEN" = 3; "INFO" = 4 }
    $sortedAlerts = $alerts | Sort-Object { $alertOrder[$_.Level] }
    
    foreach ($alert in $sortedAlerts) {
        # Couleur selon le niveau
        $color = switch ($alert.Level) {
            "CRITIQUE" { "Red" }
            "ELEVE"    { "Yellow" }
            "MOYEN"    { "DarkYellow" }
            "INFO"     { "Cyan" }
        }
        
        # Icône selon le niveau
        $prefix = switch ($alert.Level) {
            "CRITIQUE" { "[CRITIQUE]" }
            "ELEVE"    { "[ELEVE] " }
            "MOYEN"    { "[MOYEN] " }
            "INFO"     { "[INFO] " }
        }
        
        Write-Host " $prefix $($alert.Type)" -ForegroundColor $color
        Write-Host " $($alert.Message)" -ForegroundColor Gray
        Write-Host " → " -ForegroundColor DarkGray -NoNewline
        Write-Host "$($alert.Recommendation)" -ForegroundColor DarkCyan
        Write-Host " 📋 ANSSI: $($alert.ANSSI)" -ForegroundColor DarkGray
        Write-Host ""
    }
    
    Write-Host ""
    
} else {
    Write-SectionHeader "✅ HYGIENE INFORMATIQUE — Aucune alerte detectee"
    Write-Host " Toutes les verifications de securite sont conformes" -ForegroundColor DarkGreen
}

# === GÉNÉRATION DU RAPPORT ===
Write-Host ""
Write-SectionHeader "📄 GÉNÉRATION DU RAPPORT HTML"