🔐 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
}


