Script Powershell pour auditer les comptes desactivés, expirés ou proche de l’expiration

par | Juin 22, 2026 | Active Directory, PowerShell, Script, Uncategorized | 0 commentaires

🔐 Mots de passe expirant prochainement : Anticipez les blocages des utilisateurs en identifiant qui doit changer son mot de passe sous $N$ jours.

⏱️ Comptes expirés : Repérez les comptes (souvent temporaires ou de prestataires) dont la date de fin de validité est dépassée.

🔒 Comptes désactivés : Listez les comptes inactifs afin de planifier leur suppression définitive.

Zéro bruit (Exclusions intelligentes) : Le script intègre un système de filtres automatiques pour ignorer les comptes de service (krbtgt, préfixes spécifiques SERV_, admapp_, etc.) et se concentrer uniquement sur les vrais comptes utilisateurs.

Rapport HTML : En plus de générer des fichiers CSV bruts pour chaque catégorie, le script compile les données dans un tableau HTML

Flexibilité : Vous pouvez ajuster les seuils de recherche directement via les paramètres (ex: chercher les mots de passe expirant dans 15 jours au lieu de 30).

#Requires -Modules ActiveDirectory
#Requires -Version 5.0

<#
.SYNOPSIS
    Génère un rapport consolidé des anomalies Active Directory
    
.DESCRIPTION
    Script qui regroupe tous les audits AD:
    - Utilisateurs avec mot de passe expirant prochainement
    - Comptes expirés depuis N jours
    - Comptes désactivés depuis N jours
    
    Génère des fichiers CSV et un rapport HTML consolid.

.PARAMETER DaysAheadPassword
    Nombre de jours à l'avance pour les mots de passe expirant.
    Défaut: 30

.PARAMETER DaysBackExpired
    Nombre de jours à remonter pour les comptes expirés.
    Défaut: 60

.PARAMETER DaysBackDisabled
    Nombre de jours à remonter pour les comptes désactivés.
    Défaut: 60

.PARAMETER OutputPath
    Répertoire de sortie pour les rapports.
    Défaut: C:\Temp\AD_Reports

.PARAMETER ExcludedObjectSid
    SID de l'objet à exclure (ex: BUILTIN\Invité).

.PARAMETER ExcludedPrefixes
    Tableau des préfixes de compte à exclure.

.EXAMPLE
    .\AD_Accounts_Disab_Expir_Audit.ps1
    
.EXAMPLE
    .\AD_Accounts_Disab_Expir_Audit.ps1 -DaysAheadPassword 15 -DaysBackExpired 90 -OutputPath "C:\Reports"
#>

param(
    [int]$DaysAheadPassword = 30,
    [int]$DaysBackExpired = 60,
    [int]$DaysBackDisabled = 60,
    [string]$OutputPath = "C:\Temp\AD_Reports",
    [string]$ExcludedObjectSid = "S-1-5-21-1801674531-261903793-725345543-501",
    [string[]]$ExcludedPrefixes = @("krbtgt", "TsInternetUser", "X_", "T_", "V_", "U_", "P_", "E_", "S_", "R_", "SERV_", "admapp_", "admpr_", "admex_")
)

# ============================================================================
# FUNCTIONS
# ============================================================================

function Write-Header {
    param([string]$Text)
    Write-Host ""
    Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan
    Write-Host "  $Text" -ForegroundColor Cyan
    Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan
}

function Write-Section {
    param([string]$Text)
    Write-Host "➤ $Text" -ForegroundColor Yellow
}

function Write-Success {
    param([string]$Text)
    Write-Host "  ✓ $Text" -ForegroundColor Green
}

function Write-Error-Custom {
    param([string]$Text)
    Write-Host "  ✗ $Text" -ForegroundColor Red
}

function Build-ExclusionFilter {
    <#
    .SYNOPSIS
        Construit le filtre LDAP pour exclure les comptes de service
    #>
    $filterParts = @()
    foreach ($prefix in $ExcludedPrefixes) {
        $filterParts += "(!samaccountname=$prefix*)"
    }
    return ($filterParts -join "")
}

function Build-SelectedProperties {
    <#
    .SYNOPSIS
        Construit les propriétés à afficher dans le rapport
    #>
    return @(
        @{Name="Nom Affiche";Expression={$_."DisplayName"}},
        @{Name="Nom";Expression={$_."sn"}},
        @{Name="Prenom";Expression={$_."GivenName"}},
        @{Name="Login";Expression={$_."SamAccountName"}},
        @{Name="Description";Expression={$_."description"}},
        @{Name="Adresse email";Expression={$_."Mail"}},
        @{Name="Direction/Entite";Expression={$_."Division"}},
        @{Name="Service";Expression={$_."department"}},
        @{Name="Fonction";Expression={$_."title"}},
        @{Name="Bureau";Expression={$_."PhysicalDeliveryOfficeName"}},
        @{Name="Telephone fixe";Expression={$_."TelephoneNumber"}},
        @{Name="Numero IP";Expression={$_."IPPhone"}},
        @{Name="Telephone mobile";Expression={$_."Mobile"}},
        @{Name="Code taxation societe";Expression={$_."extensionAttribute9"}},
        @{Name="Code taxation service";Expression={$_."extensionAttribute10"}},
        @{Name="Date modification";Expression={$_."whenChanged"}}
    )
}

function Get-AllADProperties {
    <#
    .SYNOPSIS
        Retourne la liste complète des propriétés à récupérer
    #>
    return @(
        'DisplayName', 'sn', 'GivenName', 'SamAccountName', 'description', 
        'Mail', 'Division', 'department', 'title', 'PhysicalDeliveryOfficeName',
        'TelephoneNumber', 'IPPhone', 'Mobile', 'extensionAttribute9', 
        'extensionAttribute10', 'whenChanged', 'msDS-UserPasswordExpiryTimeComputed',
        'PasswordNeverExpires', 'Enabled', 'accountExpires'
    )
}

function Get-PasswordExpiringUsers {
    <#
    .SYNOPSIS
        Récupère les utilisateurs avec mots de passe expirant dans N jours
    #>
    Write-Section "Recherche des mots de passe expirant dans $DaysAheadPassword jours..."
    
    try {
        $DateStart = Get-Date
        $DateEnd = $DateStart.AddDays($DaysAheadPassword)
        
        $properties = Get-AllADProperties
        $exclusionFilter = Build-ExclusionFilter
        
        # Filtre pour les mots de passe expirant
        $filter = "(&(objectCategory=person)(objectClass=user)(enabled=TRUE)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(PasswordNeverExpires=TRUE))(!(objectSid=$ExcludedObjectSid))$exclusionFilter)"
        
        $allUsers = Get-ADUser -Filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -Properties $properties
        
        $results = @()
        foreach ($user in $allUsers) {
            $pwdExpiry = $user."msDS-UserPasswordExpiryTimeComputed"
            if ($pwdExpiry) {
                try {
                    $expiryDate = [DateTime]::FromFileTime($pwdExpiry)
                    if ($expiryDate -gt $DateStart -and $expiryDate -le $DateEnd) {
                        $userobj = $user | Select-Object (Build-SelectedProperties)
                        $userobj | Add-Member -MemberType NoteProperty -Name "Date expiration MdP" -Value ($expiryDate.ToString('dd/MM/yyyy')) -Force
                        $results += $userobj
                    }
                }
                catch { }
            }
        }
        
        Write-Success "$($results.Count) utilisateur(s) avec mot de passe expirant"
        return $results | Sort-Object "Date expiration MdP"
    }
    catch {
        Write-Error-Custom "Erreur: $_"
        return @()
    }
}

function Get-ExpiredAccounts {
    <#
    .SYNOPSIS
        Récupère les comptes expirés depuis N jours
    #>
    Write-Section "Recherche des comptes expirés depuis $DaysBackExpired jours..."
    
    try {
        $Date = (Get-Date).AddDays(-$DaysBackExpired)
        $properties = Get-AllADProperties
        $exclusionFilter = Build-ExclusionFilter
        
        $filter = "(&(!useraccountcontrol:1.2.840.113556.1.4.803:=2)(objectCategory=person)(objectClass=user)(accountExpires<=$($Date.ToFileTime()))(!(|(accountExpires=9223372036854775807)(accountExpires=0)))(!objectSid=$ExcludedObjectSid)$exclusionFilter)"
        
        $results = Get-ADUser -LDAPFilter $filter -Properties $properties |
            Where-Object {
                $ae = $_.accountExpires
                $ae -gt 0 -and $ae -ne 9223372036854775807
            } |
            ForEach-Object {
                $user = $_
                $expiryDate = [DateTime]::FromFiletime([Int64]($user.accountExpires))
                $userobj = $user | Select-Object (Build-SelectedProperties)
                $userobj | Add-Member -MemberType NoteProperty -Name "Date expiration" -Value ($expiryDate.ToString('dd/MM/yyyy')) -Force
                $userobj
            } |
            Sort-Object "Date expiration"
        
        Write-Success "$($results.Count) compte(s) expiré(s)"
        return @($results)
    }
    catch {
        Write-Error-Custom "Erreur: $_"
        return @()
    }
}

function Get-DisabledAccounts {
    <#
    .SYNOPSIS
        Récupère les comptes désactivés depuis N jours
    #>
    Write-Section "Recherche des comptes désactivés depuis $DaysBackDisabled jours..."
    
    try {
        $Date = (Get-Date).AddDays(-$DaysBackDisabled)
        $properties = Get-AllADProperties
        $exclusionFilter = Build-ExclusionFilter
        
        $filterParts = @(
            "(&(useraccountcontrol:1.2.840.113556.1.4.803:=2)(objectclass=user)"
            "(!objectSid=$ExcludedObjectSid)"
        )
        
        $filter = $filterParts -join "" + "$exclusionFilter)"
        
        $results = Get-ADUser -LDAPFilter $filter -Properties $properties |
            Select-Object (Build-SelectedProperties) |
            Sort-Object "Nom Affiche"
        
        Write-Success "$($results.Count) compte(s) désactivé(s)"
        return @($results)
    }
    catch {
        Write-Error-Custom "Erreur: $_"
        return @()
    }
}

function Export-ToCSV {
    <#
    .SYNOPSIS
        Exporte les données en fichier CSV
    #>
    param(
        [array]$Data,
        [string]$FileName
    )
    
    $filePath = Join-Path $OutputPath $FileName
    
    if ($Data -and $Data.Count -gt 0) {
        $Data | Export-Csv -Encoding UTF8 -NoTypeInformation -Path $filePath -Force
        Write-Success "Exporté: $FileName"
    }
    else {
        "Aucune donnée à exporter." | Out-File -Encoding UTF8 -FilePath $filePath -Force
        Write-Host "  ⚠ Exporté (vide): $FileName" -ForegroundColor Gray
    }
    
    return $filePath
}

function Generate-HTMLReport {
    <#
    .SYNOPSIS
        Génère un rapport HTML consolidé
    #>
    param(
        [array]$PasswordExpiringUsers,
        [array]$ExpiredAccounts,
        [array]$DisabledAccounts
    )
    
    Write-Section "Génération du rapport HTML..."
    
    $timestamp = Get-Date -Format "dd/MM/yyyy HH:mm:ss"
    $htmlPath = Join-Path $OutputPath "AD_Consolidated_Report.html"
    
    $totalIssues = ($PasswordExpiringUsers.Count) + ($ExpiredAccounts.Count) + ($DisabledAccounts.Count)
    
    $htmlContent = @"
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Audit de l'expiration et desactivation des comptes Active Directory</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 40px 20px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 32px;
            margin-bottom: 10px;
        }
        
        .header p {
            font-size: 14px;
            opacity: 0.9;
        }
        
        .content {
            padding: 30px 20px;
        }
        
        .summary-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 40px;
        }
        
        .summary-card {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            padding: 20px;
            border-radius: 8px;
            border-left: 4px solid #667eea;
            text-align: center;
        }
        
        .summary-card.warning {
            border-left-color: #ff6b6b;
            background: linear-gradient(135deg, #ffe5e5 0%, #ffcccc 100%);
        }
        
        .summary-card.danger {
            border-left-color: #ff4757;
            background: linear-gradient(135deg, #ffd6d6 0%, #ffb3b3 100%);
        }
        
        .summary-card.success {
            border-left-color: #2ed573;
            background: linear-gradient(135deg, #e5f9e5 0%, #ccf0cc 100%);
        }
        
        .summary-card h3 {
            font-size: 12px;
            text-transform: uppercase;
            color: #666;
            margin-bottom: 10px;
        }
        
        .summary-card .number {
            font-size: 28px;
            font-weight: bold;
            color: #333;
        }
        
        .section {
            margin-bottom: 40px;
        }
        
        .section h2 {
            border-bottom: 2px solid #667eea;
            padding-bottom: 10px;
            color: #333;
            margin-bottom: 20px;
            font-size: 20px;
        }
        
        table {
            width: 100%;
            border-collapse: collapse;
            background: white;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        
        th {
            background: #f8f9fa;
            color: #333;
            padding: 12px;
            text-align: left;
            font-weight: 600;
            border-bottom: 1px solid #ddd;
            font-size: 13px;
        }
        
        td {
            padding: 10px 12px;
            border-bottom: 1px solid #eee;
            font-size: 13px;
            color: #666;
        }
        
        tr:hover {
            background: #f8f9fa;
        }
        
        tr:last-child td {
            border-bottom: none;
        }
        
        .empty-message {
            text-align: center;
            padding: 30px;
            color: #999;
            background: #f8f9fa;
            border-radius: 8px;
        }
        
        .footer {
            background: #f8f9fa;
            padding: 20px;
            text-align: center;
            border-top: 1px solid #eee;
            color: #999;
            font-size: 12px;
        }
        
        .total-issues {
            font-size: 16px;
            font-weight: bold;
            color: #2c3e50;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📊 Audit de l'expiration et desactivation des comptes Active Directory</h1>
            <p>Généré le $timestamp</p>
        </div>
        
        <div class="content">
            <div class="total-issues">
                Nombre total de comptes détectés: <span style="color: #ff6b6b;">$totalIssues</span>
            </div>
            
            <div class="summary-grid">
                <div class="summary-card warning">
                    <h3>Mots de passe expirant</h3>
                    <div class="number">${($PasswordExpiringUsers.Count)}</div>
                </div>
                <div class="summary-card danger">
                    <h3>Comptes expirés</h3>
                    <div class="number">${($ExpiredAccounts.Count)}</div>
                </div>
                <div class="summary-card danger">
                    <h3>Comptes désactivés</h3>
                    <div class="number">${($DisabledAccounts.Count)}</div>
                </div>
            </div>
"@
    
    # Section Mots de passe expirant
    $htmlContent += "<div class='section'>"
    $htmlContent += "<h2>🔐 Mots de passe expirant prochainement (dans $DaysAheadPassword jours)</h2>"
    
    if ($PasswordExpiringUsers -and $PasswordExpiringUsers.Count -gt 0) {
        $htmlContent += "<table><thead><tr>"
        $htmlContent += "<th>Login</th><th>Nom Affiche</th><th>Email</th><th>Service</th><th>Date expiration MdP</th>"
        $htmlContent += "</tr></thead><tbody>"
        
        foreach ($user in $PasswordExpiringUsers) {
            $htmlContent += "<tr>"
            $htmlContent += "<td>$($user.'Login')</td>"
            $htmlContent += "<td>$($user.'Nom Affiche')</td>"
            $htmlContent += "<td>$($user.'Adresse email')</td>"
            $htmlContent += "<td>$($user.'Service')</td>"
            $htmlContent += "<td>$($user.'Date expiration MdP')</td>"
            $htmlContent += "</tr>"
        }
        
        $htmlContent += "</tbody></table>"
    }
    else {
        $htmlContent += "<div class='empty-message'>✓ Aucun mot de passe n'expire dans les prochains $DaysAheadPassword jours</div>"
    }
    
    $htmlContent += "</div>"
    
    # Section Comptes expirés
    $htmlContent += "<div class='section'>"
    $htmlContent += "<h2>⏱️ Comptes expirés (depuis $DaysBackExpired jours)</h2>"
    
    if ($ExpiredAccounts -and $ExpiredAccounts.Count -gt 0) {
        $htmlContent += "<table><thead><tr>"
        $htmlContent += "<th>Login</th><th>Nom Affiche</th><th>Email</th><th>Service</th><th>Date expiration</th>"
        $htmlContent += "</tr></thead><tbody>"
        
        foreach ($user in $ExpiredAccounts) {
            $htmlContent += "<tr>"
            $htmlContent += "<td>$($user.'Login')</td>"
            $htmlContent += "<td>$($user.'Nom Affiche')</td>"
            $htmlContent += "<td>$($user.'Adresse email')</td>"
            $htmlContent += "<td>$($user.'Service')</td>"
            $htmlContent += "<td>$($user.'Date expiration')</td>"
            $htmlContent += "</tr>"
        }
        
        $htmlContent += "</tbody></table>"
    }
    else {
        $htmlContent += "<div class='empty-message'>✓ Aucun compte expiré</div>"
    }
    
    $htmlContent += "</div>"
    
    # Section Comptes désactivés
    $htmlContent += "<div class='section'>"
    $htmlContent += "<h2>🔒 Comptes désactivés (depuis $DaysBackDisabled jours)</h2>"
    
    if ($DisabledAccounts -and $DisabledAccounts.Count -gt 0) {
        $htmlContent += "<table><thead><tr>"
        $htmlContent += "<th>Login</th><th>Nom Affiche</th><th>Email</th><th>Service</th><th>Date modification</th>"
        $htmlContent += "</tr></thead><tbody>"
        
        foreach ($user in $DisabledAccounts) {
            $htmlContent += "<tr>"
            $htmlContent += "<td>$($user.'Login')</td>"
            $htmlContent += "<td>$($user.'Nom Affiche')</td>"
            $htmlContent += "<td>$($user.'Adresse email')</td>"
            $htmlContent += "<td>$($user.'Service')</td>"
            $htmlContent += "<td>$($user.'Date modification')</td>"
            $htmlContent += "</tr>"
        }
        
        $htmlContent += "</tbody></table>"
    }
    else {
        $htmlContent += "<div class='empty-message'>✓ Aucun compte désactivé</div>"
    }
    
    $htmlContent += "</div>"
    
    $htmlContent += @"
        </div>
        
        <div class="footer">
            <p>Rapport généré automatiquement par AD_Accounts_Disab_Expir_Audit.ps1</p>
            <p>$timestamp</p>
        </div>
    </div>
</body>
</html>
"@
    
    $htmlContent | Out-File -Encoding UTF8 -FilePath $htmlPath -Force
    Write-Success "Rapport HTML généré: AD_Consolidated_Report.html"
    
    return $htmlPath
}

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

try {
    # Vérification du module ActiveDirectory
    Write-Header "Audit de l'expiration et desactivation des comptes Active Directory"
    
    if (!(Get-Module ActiveDirectory)) {
        Write-Section "Import du module ActiveDirectory..."
        Import-Module ActiveDirectory -ErrorAction Stop
        Write-Success "Module ActiveDirectory importé"
    }
    
    # Créer le répertoire de sortie s'il n'existe pas
    if (!(Test-Path $OutputPath)) {
        New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
        Write-Success "Répertoire créé: $OutputPath"
    }
    else {
        Write-Success "Répertoire de sortie: $OutputPath"
    }
    
    Write-Header "Lancement des audits"
    
    # Récupérer les données
    $passwordExpiring = Get-PasswordExpiringUsers
    $expiredAccounts = Get-ExpiredAccounts
    $disabledAccounts = Get-DisabledAccounts
    
    Write-Header "Exportation des données"
    
    # Exporter en CSV
    Export-ToCSV -Data $passwordExpiring -FileName "PasswordExpiring-$($DaysAheadPassword)Days.csv"
    Export-ToCSV -Data $expiredAccounts -FileName "AccountExpired-$($DaysBackExpired)Days.csv"
    Export-ToCSV -Data $disabledAccounts -FileName "AccountDisabled-$($DaysBackDisabled)Days.csv"
    
    # Générer le rapport HTML
    Write-Header "Génération du rapport"
    $reportPath = Generate-HTMLReport -PasswordExpiringUsers $passwordExpiring -ExpiredAccounts $expiredAccounts -DisabledAccounts $disabledAccounts
    
    # Résumé final
    Write-Host ""
    Write-Host "╔════════════════════════════════════════════════════════════╗" -ForegroundColor Green
    Write-Host "║                   ✓ AUDIT TERMINÉ AVEC SUCCÈS              ║" -ForegroundColor Green
    Write-Host "╚════════════════════════════════════════════════════════════╝" -ForegroundColor Green
    Write-Host ""
    Write-Host "Résumé:" -ForegroundColor Cyan
    Write-Host "  • Mots de passe expirant: $($passwordExpiring.Count)" -ForegroundColor Yellow
    Write-Host "  • Comptes expirés: $($expiredAccounts.Count)" -ForegroundColor Red
    Write-Host "  • Comptes désactivés: $($disabledAccounts.Count)" -ForegroundColor Red
    Write-Host "  • Total comptes: $(($passwordExpiring.Count) + ($expiredAccounts.Count) + ($disabledAccounts.Count))" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Fichiers générés dans: $OutputPath" -ForegroundColor Green
    Write-Host ""
    
    # Proposer d'ouvrir le rapport
    $response = Read-Host "Voulez-vous ouvrir le rapport HTML? (O/N)"
    if ($response -eq "O" -or $response -eq "o") {
        Start-Process $reportPath
    }
}
catch {
    Write-Host ""
    Write-Host "╔════════════════════════════════════════════════════════════╗" -ForegroundColor Red
    Write-Host "║                    ✗ ERREUR FATALE                        ║" -ForegroundColor Red
    Write-Host "╚════════════════════════════════════════════════════════════╝" -ForegroundColor Red
    Write-Host ""
    Write-Error $_
    exit 1
}

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *