Audit et conformite des GPO¶
Ce que vous allez pouvoir faire
- Generer un rapport HTML ou XML exhaustif de toutes les GPO du domaine en une commande, exploitable par un auditeur externe
- Detecter toute modification sur une GPO grace aux evenements Windows 5136 et 4719, sans deployer AGPM ni outil tiers
- Comparer une GPO de production avec une baseline de securite documentee et produire un rapport de conformite pret a etre joint a un audit ISO 27001 ou SOC2
- Centraliser les evenements GPO vers un SIEM via Windows Event Collector avec un filtre XML pret a l'emploi
- Identifier les GPO orphelines, sans description et non modifiees depuis plus d'un an — les trois signaux d'une hygiene GPO degradee
Si vous ne retenez qu'une chose
L'evenement 5136 dans le journal Security enregistre chaque modification sur un objet Active Directory — y compris les GPO (groupPolicyContainer). Combine avec l'evenement 4719 (politique d'audit modifiee), ces deux ID vous donnent une piste d'audit complete des changements GPO sans AGPM. Mais ils n'existent que si "Audit Directory Service Changes" est active sur les controleurs de domaine. Verifiez ce point avant tout.
Pourquoi auditer les GPO¶
Les GPO comme surface d'attaque¶
Les GPO sont les vecteurs de configuration les plus puissants de votre Active Directory. Une GPO malveillante ou mal configuree peut desactiver le pare-feu, affaiblir la politique de mots de passe, ou deployer un script de demarrage sur tous les postes du domaine.
Pour un attaquant ayant obtenu les droits suffisants, modifier une GPO est une methode discrete et persistante d'installation d'une backdoor. Sans audit actif, cette modification peut rester invisible pendant des semaines.
Les exigences reglementaires¶
Les referentiels ISO 27001, SOC2 Type II et HDS exigent tous de pouvoir demontrer que les configurations de securite sont maintenues dans le temps et que toute modification est tracee et justifiee.
Lors d'un audit externe, la question "qui a modifie cette GPO de securite, et quand ?" doit recevoir une reponse factuelle et documentee. "On ne sait pas" ou "on n'avait pas active l'audit" constituent des non-conformites majeures.
Ce que l'audit GPO doit couvrir¶
Un dispositif d'audit GPO complet repond a quatre questions.
Qui a modifie ? — Nom du compte ayant effectue la modification (via Event 5136).
Quoi a change ? — Attribut LDAP modifie sur l'objet groupPolicyContainer et, via LGPO.exe, la valeur avant/apres.
Quand ? — Horodatage exact de la modification.
La GPO est-elle conforme a la baseline attendue ? — Comparaison entre les parametres actuels et les valeurs requises par votre politique de securite.
En resume
Sans audit GPO, une investigation d'incident de securite ne peut pas determiner si un attaquant a modifie une GPO pour affaiblir les defenses. L'activation des evenements 5136 et 4719 est le prerequis zero de tout dispositif de securite serieux sur Active Directory.
Audit natif : Get-GPOReport¶
Rapport HTML de toutes les GPO¶
La commande la plus directe pour produire une vue complete de toutes les GPO du domaine. Le rapport HTML est lisible dans n'importe quel navigateur et peut etre joint a un dossier d'audit.
# Generate a single HTML report for all GPOs, sorted alphabetically
$reportPath = "C:\Temp\GPO-Report-$(Get-Date -Format 'yyyyMMdd').html"
Get-GPO -All | Sort-Object DisplayName | ForEach-Object {
Get-GPOReport -Guid $_.Id -ReportType Html | Out-File $reportPath -Append
}
Write-Host "Report saved to $reportPath"
Le fichier genere concatene le rapport de chaque GPO. Chaque section commence par le nom, la date de modification, et le statut de la GPO avant de detailler les parametres configures.
Rapport HTML concatene non structure
La concatenation de rapports HTML individuels produit un fichier valide mais sans en-tete HTML global. Pour un rapport structure avec navigation, privilegiez le format XML (voir section suivante) et transformez-le via XSLT ou parsez-le en PowerShell.
Extraction de parametres via le format XML¶
Le format XML de Get-GPOReport est la base de tous les scripts de conformite. Il permet de cibler precisement un parametre dans une ou plusieurs GPO sans lire l'integralite du rapport.
Cet exemple extrait les parametres de politique de securite (SecurityOptions) de toutes les GPO du domaine pour les consolider dans un tableau de conformite.
# Extract security settings from all GPOs for a compliance report
$ns = @{ q1 = "http://www.microsoft.com/GroupPolicy/Settings/Security" }
$complianceData = Get-GPO -All | ForEach-Object {
$gpo = $_
$xml = [xml](Get-GPOReport -Guid $gpo.Id -ReportType Xml)
$secSettings = $xml | Select-Xml -XPath "//q1:SecurityOptions/q1:Display" -Namespace $ns
if ($secSettings) {
$secSettings | ForEach-Object {
[PSCustomObject]@{
GPO = $gpo.DisplayName
Setting = $_.Node.Name
Value = $_.Node.DisplayString
}
}
}
}
$complianceData | Sort-Object GPO, Setting | Format-Table -AutoSize
GPO Setting Value
--- ------- -----
SEC-Postes-Baseline Minimum password length 12
SEC-Postes-Baseline Password must meet complexity req. Enabled
SEC-Postes-Baseline Account lockout threshold 5
SEC-Serveurs-Production Minimum password length 16
Export CSV pour Excel ou SIEM¶
Pour un audit formel, exportez les donnees en CSV plutot que de les afficher a l'ecran.
# Export all GPO settings to CSV for audit documentation
$reportPath = "C:\Temp\GPO-Compliance-$(Get-Date -Format 'yyyyMMdd').csv"
Get-GPO -All | ForEach-Object {
$gpo = $_
[PSCustomObject]@{
Name = $gpo.DisplayName
GUID = $gpo.Id
Status = $gpo.GpoStatus
CreatedDate = $gpo.CreationTime.ToString("yyyy-MM-dd")
LastModifiedDate = $gpo.ModificationTime.ToString("yyyy-MM-dd")
Description = $gpo.Description
WMIFilter = if ($gpo.WmiFilter) { $gpo.WmiFilter.Name } else { "" }
}
} | Export-Csv $reportPath -NoTypeInformation
Write-Host "Exported $((Get-GPO -All).Count) GPOs to $reportPath"
En resume
Get-GPOReport -ReportType Xml est le point d'entree de tout script de conformite. Le XML expose la totalite des parametres d'une GPO de maniere parseable. La combinaison XPath + namespace q1 est le pattern a retenir pour cibler n'importe quelle section du rapport.
Surveillance des modifications GPO : Event ID 5136¶
Comment Windows trace les modifications AD¶
Chaque objet Active Directory — y compris les GPO, representees dans LDAP comme des objets groupPolicyContainer — genere un evenement 5136 dans le journal Security du controleur de domaine chaque fois qu'un de ses attributs est modifie.
Cet evenement contient : le nom du compte auteur de la modification, le nom LDAP de l'attribut modifie, la nouvelle valeur, et l'horodatage exact.
Prerequis : activer "Audit Directory Service Changes"¶
L'evenement 5136 n'est pas genere par defaut. Il necessite l'activation de la sous-categorie d'audit "Audit Directory Service Changes" via la politique d'audit avancee.
Chemin dans la GPO : Computer Configuration → Policies → Windows Settings → Security Settings → Advanced Audit Policy Configuration → DS Access → Audit Directory Service Changes
Configurez cette sous-categorie sur Success (au minimum) ou Success and Failure sur tous les controleurs de domaine. La politique doit etre liee au niveau de la DC OU.
# Verify that Audit Directory Service Changes is enabled on the PDC
$dcPDC = (Get-ADDomain).PDCEmulator
Invoke-Command -ComputerName $dcPDC -ScriptBlock {
auditpol /get /subcategory:"Directory Service Changes"
}
System audit policy
Category/Subcategory Setting
DS Access
Directory Service Changes Success and Failure
5136 non active par defaut
L'evenement 5136 n'est pas genere si "Audit Directory Service Changes" n'est pas active. Sans cette configuration, toutes les modifications GPO sont silencieuses. Verifiez systematiquement ce point sur chaque nouveau controleur de domaine promu — la politique d'audit avancee ne se propage pas toujours correctement.
Requete PowerShell : modifications GPO des 7 derniers jours¶
# Find all GPO modifications in the last 7 days from the PDC Security log
$startDate = (Get-Date).AddDays(-7)
$dcPDC = (Get-ADDomain).PDCEmulator
Get-WinEvent -ComputerName $dcPDC -FilterHashtable @{
LogName = 'Security'
Id = 5136
StartTime = $startDate
} | Where-Object {
$_.Message -match "groupPolicyContainer"
} | Select-Object TimeCreated,
@{N="Object"; E={ ($_.Message -split "`n" | Where-Object { $_ -match "Object Name:" }) -replace ".*Object Name:\s*","" }},
@{N="Attribute"; E={ ($_.Message -split "`n" | Where-Object { $_ -match "LDAP Display Name:" }) -replace ".*LDAP Display Name:\s*","" }},
@{N="ModifiedBy"; E={ ($_.Message -split "`n" | Where-Object { $_ -match "Account Name:" })[0] -replace ".*Account Name:\s*","" }} |
Format-Table -AutoSize -Wrap
TimeCreated Object Attribute ModifiedBy
----------- ------ --------- ----------
2026-04-04 14:32:11 CN={GUID},CN=Policies,CN=System,DC=... versionNumber CONTOSO\jdupont
2026-04-03 09:17:44 CN={GUID},CN=Policies,CN=System,DC=... gPCFileSysPath CONTOSO\adminsvc
Decoder le GUID vers un nom de GPO¶
Le champ Object contient le GUID de la GPO au format LDAP. Pour obtenir le nom lisible :
# Resolve a GPO GUID to its display name
$guid = "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" # replace with GUID from event 5136
Get-GPO -Guid $guid.Trim("{}") | Select-Object DisplayName, ModificationTime, GpoStatus
DisplayName ModificationTime GpoStatus
----------- ---------------- ---------
SEC-Postes-Baseline 2026-04-04 14:32:11 AllSettingsEnabled
Les attributs LDAP importants a surveiller¶
| Attribut LDAP | Signification | Niveau d'alerte |
|---|---|---|
versionNumber | Numero de version de la GPO incremente a chaque modification | Haute |
gPCFileSysPath | Chemin SYSVOL de la GPO — ne doit pas changer | Critique |
nTSecurityDescriptor | Permissions sur l'objet GPO modifiees | Critique |
gPCMachineExtensionNames | Extensions CSE utilisees cote ordinateur | Haute |
gPCUserExtensionNames | Extensions CSE utilisees cote utilisateur | Haute |
displayName | Nom de la GPO renomme | Moyenne |
En resume
L'evenement 5136 filtres sur groupPolicyContainer est votre principale source de verite pour les modifications GPO. Le GUID dans le champ Object se resout en nom lisible avec Get-GPO -Guid. Ciblez toujours le PDC Emulator pour les requetes d'audit — c'est lui qui recoit les ecritures AD en priorite.
Event ID 4719 : changement de politique d'audit¶
Pourquoi cet evenement est critique¶
L'evenement 4719 est declenche lorsque la politique d'audit systeme est modifiee. C'est un signal d'alerte fort : si quelqu'un desactive la sous-categorie "Directory Service Changes", les evenements 5136 cessent d'etre generes — et les modifications GPO deviennent silencieuses.
Un attaquant suffisamment avance commencera souvent par desactiver les logs d'audit avant d'agir. L'evenement 4719 est donc la premiere ligne de defense de votre dispositif d'audit.
Interroger les evenements 4719¶
# Review the 50 most recent audit policy changes on the PDC
$dcPDC = (Get-ADDomain).PDCEmulator
Get-WinEvent -ComputerName $dcPDC -FilterHashtable @{
LogName = 'Security'
Id = 4719
} -MaxEvents 50 | Select-Object TimeCreated, Message | Format-Table -AutoSize -Wrap
TimeCreated Message
----------- -------
2026-04-01 08:00:03 System audit policy was changed. ...
Creer une alerte sur 4719¶
En production, l'evenement 4719 doit declencher une alerte immmediate. Si vous n'avez pas encore de SIEM, un script de surveillance simple peut envoyer un mail via Send-MailMessage.
# Monitor for unexpected audit policy changes and alert by email
$dcPDC = (Get-ADDomain).PDCEmulator
$cutoff = (Get-Date).AddMinutes(-60) # check the last hour
$events = Get-WinEvent -ComputerName $dcPDC -FilterHashtable @{
LogName = 'Security'
Id = 4719
StartTime = $cutoff
} -ErrorAction SilentlyContinue
if ($events) {
$body = $events | Select-Object TimeCreated, Message |
Out-String
Send-MailMessage `
-To "soc@contoso.local" `
-From "monitor@contoso.local" `
-Subject "[ALERTE] Politique d'audit modifiee sur $dcPDC" `
-Body $body `
-SmtpServer "smtp.contoso.local"
}
4719 sans explication = incident de securite
Un evenement 4719 inattendu — en dehors des fenetres de maintenance planifiees — doit etre traite comme un incident de securite actif jusqu'a preuve du contraire. La modification de la politique d'audit est une etape classique d'un mouvement lateral avance.
En resume
Monitorez 4719 avec au moins autant de rigueur que 5136. Si 4719 survient sans cause documentee, supposez une compromission et lancez une investigation. En SIEM, correlez 4719 avec les connexions RDP et les elevations de privileges dans la meme fenetre temporelle.
Script de conformite baseline¶
Principe du controle de conformite¶
Un script de conformite compare les parametres actuels d'une GPO a une liste de valeurs requises — la "baseline". Il produit un rapport indiquant si chaque parametre est conforme, non conforme ou absent.
Ce type de rapport est exactement ce qu'un auditeur ISO 27001 ou SOC2 demande pour verifier que les controles de securite sont en place.
Fonction Test-GPOCompliance¶
# Compliance check: verify a named GPO contains required registry-based settings
function Test-GPOCompliance {
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[hashtable]$RequiredSettings
)
$xml = [xml](Get-GPOReport -Name $GPOName -ReportType Xml)
$results = @()
foreach ($setting in $RequiredSettings.GetEnumerator()) {
# Search for the registry key path in GPO XML
$found = $xml | Select-Xml -XPath "//RegistrySetting/KeyPath[contains(text(),'$($setting.Key)')]"
$status = if ($found) { "COMPLIANT" } else { "MISSING" }
$results += [PSCustomObject]@{
Setting = $setting.Key
Expected = $setting.Value
Status = $status
}
}
return $results
}
Utilisation du script¶
# Define required security baseline settings and run the check
$baseline = @{
"EnableFirewall" = "1"
"DisableAutorun" = "1"
"MinPasswordLength" = "12"
"NTLMv2Only" = "1"
"DisableWinRM" = "0" # WinRM doit rester actif pour la gestion
}
$report = Test-GPOCompliance -GPOName "SEC-Postes-Baseline" -RequiredSettings $baseline
$report | Format-Table -AutoSize
# Summary
$compliant = ($report | Where-Object Status -eq "COMPLIANT").Count
$missing = ($report | Where-Object Status -eq "MISSING").Count
Write-Host "Conformite : $compliant/$($report.Count) parametres OK — $missing manquants"
Setting Expected Status
------- -------- ------
EnableFirewall 1 COMPLIANT
DisableAutorun 1 COMPLIANT
MinPasswordLength 12 MISSING
NTLMv2Only 1 COMPLIANT
DisableWinRM 0 COMPLIANT
Conformite : 4/5 parametres OK — 1 manquants
Export du rapport de conformite¶
# Export compliance report to CSV for audit documentation
$reportPath = "C:\Temp\Compliance-Report-$(Get-Date -Format 'yyyyMMdd').csv"
$report | Export-Csv $reportPath -NoTypeInformation
Write-Host "Compliance report saved to $reportPath"
Limites de Test-GPOCompliance
Cette fonction recherche les cles dans les RegistrySetting de la GPO. Les parametres deployes via les Security Settings (GptTmpl.inf) comme la politique de mots de passe, les droits utilisateurs ou le pare-feu Windows Defender sont dans un namespace XML different. Pour ces parametres, utilisez le namespace q1 comme dans les exemples de la section Get-GPOReport.
En resume
La fonction Test-GPOCompliance est un point de depart. En production, enrichissez-la avec la verification des Security Settings via le namespace XML adequat, et exportez le rapport en CSV pour l'archiver dans votre GED ou votre outil de ticketing.
Intégration SIEM : Windows Event Collector¶
Architecture WEC pour les evenements GPO¶
Windows Event Collector (WEC) permet de centraliser les evenements des controleurs de domaine vers un collecteur unique, sans installer d'agent tiers. Le SIEM interroge ensuite uniquement le collecteur.
Cette architecture reduit la charge sur les DC et garantit que les evenements sont preserves meme si le journal Security d'un DC est efface.
Filtre WEF pour les evenements GPO¶
Ce fichier de souscription WEF (Windows Event Forwarding) collecte uniquement les evenements liés aux GPO, en filtrant sur la classe d'objet groupPolicyContainer.
<!-- WEF Subscription filter for GPO audit events -->
<!-- Deploy on the WEC server via wecutil cs subscription.xml -->
<Subscription>
<SubscriptionType>SourceInitiated</SubscriptionType>
<Description>GPO audit events from all Domain Controllers</Description>
<SubscriptionId>GPO-Audit</SubscriptionId>
<Enabled>true</Enabled>
<Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri>
<ConfigurationMode>Custom</ConfigurationMode>
<Delivery Mode="Push">
<Batching>
<MaxLatencyTime>900000</MaxLatencyTime>
</Batching>
<PushSettings>
<Heartbeat Interval="1800000"/>
</PushSettings>
</Delivery>
<Query>
<![CDATA[
<QueryList>
<Query Id="0">
<Select Path="Security">
*[System[(EventID=5136 or EventID=5137 or EventID=5141 or EventID=4719 or EventID=4670)]]
and
*[EventData[Data[@Name='ObjectClass']='groupPolicyContainer']]
</Select>
</Query>
</QueryList>
]]>
</Query>
<ReadExistingEvents>false</ReadExistingEvents>
<TransportSecurity>HTTPS</TransportSecurity>
<ContentFormat>RenderedText</ContentFormat>
<Locale Language="fr-FR"/>
</Subscription>
Tableau des Event IDs GPO a transmettre au SIEM¶
| Event ID | Journal | Signification | Priorite SIEM |
|---|---|---|---|
| 5136 | Security | Objet AD modifie — attribut GPO change | Haute |
| 5137 | Security | Objet AD cree — nouvelle GPO creee | Haute |
| 5141 | Security | Objet AD supprime — GPO supprimee | Critique |
| 4719 | Security | Politique d'audit systeme modifiee | Critique |
| 4670 | Security | Permissions d'un objet AD modifiees | Haute |
Deployer la souscription WEF¶
# Deploy the WEF subscription on the WEC server
# Run on the WEC server as Domain Admin
wecutil cs "C:\WEF\GPO-Audit-Subscription.xml"
# Verify the subscription is active
wecutil gs GPO-Audit
# List all active subscriptions
wecutil es
Journal Security plein
Activer toutes les sous-categories d'audit sur un grand domaine peut remplir le journal Security d'un DC en quelques heures. Configurez une retention minimale de 128 Mo sur les DC — la recommandation pour un environnement enterprise est 4 Go. Utilisez WEC pour centraliser les evenements et alleger la pression sur les journaux locaux des DC.
En resume
WEC + un filtre XPath sur groupPolicyContainer est la solution la plus simple pour centraliser les evenements GPO sans agent. Les cinq Event IDs du tableau couvrent l'ensemble du cycle de vie d'une GPO : creation, modification, suppression, changement de permissions, changement de politique d'audit.
Audit avec LGPO.exe¶
Ce que LGPO.exe apporte¶
LGPO.exe est un outil Microsoft de la Security Compliance Toolkit. Il peut exporter les parametres d'une GPO au format texte parseable, ce qui permet de faire des comparaisons diff entre deux etats de la meme GPO.
C'est l'approche la plus fiable pour repondre a la question "qu'est-ce qui a change exactement dans cette GPO ?" quand l'evenement 5136 indique seulement que versionNumber a ete incremente.
Telecharger LGPO.exe¶
LGPO.exe est disponible sur le site officiel de Microsoft dans le package Security Compliance Toolkit. Placez l'executable dans C:\Tools\LGPO\ ou ajoutez son dossier au PATH systeme.
# Verify LGPO.exe is accessible
Get-Command lgpo.exe -ErrorAction SilentlyContinue |
Select-Object Name, Source, Version
Creer un snapshot des GPO au format texte¶
# Export all GPO Registry.pol files to parseable text — GPO snapshot
$snapshotPath = "C:\Temp\GPO-Snapshot-$(Get-Date -Format 'yyyyMMdd')"
New-Item -ItemType Directory -Path $snapshotPath -Force | Out-Null
$domain = $env:USERDNSDOMAIN
Get-GPO -All | ForEach-Object {
$guid = $_.Id.ToString()
$gptPath = "\\$domain\SYSVOL\$domain\Policies\{$guid}\Machine\Registry.pol"
if (Test-Path $gptPath) {
# Sanitize GPO name for use as filename
$safeName = $_.DisplayName -replace '[\\/:*?"<>|]', '_'
$outFile = "$snapshotPath\$safeName.txt"
lgpo.exe /parse /m $gptPath > $outFile
Write-Host "Exported: $($_.DisplayName)"
}
}
Write-Host "Snapshot saved to $snapshotPath"
Exported: Default Domain Policy
Exported: SEC-Postes-Baseline
Exported: SEC-Serveurs-Production
Exported: GPO-WSUS-Configuration
...
Snapshot saved to C:\Temp\GPO-Snapshot-20260405
Comparer deux snapshots¶
Apres un evenement 5136 signalant une modification, comparez le snapshot actuel avec le snapshot de reference pour identifier precisement la valeur modifiee.
# Compare two snapshots to identify changed settings
$snap1 = "C:\Temp\GPO-Snapshot-20260401"
$snap2 = "C:\Temp\GPO-Snapshot-20260405"
# Compare a specific GPO file
$file1 = "$snap1\SEC-Postes-Baseline.txt"
$file2 = "$snap2\SEC-Postes-Baseline.txt"
if ((Test-Path $file1) -and (Test-Path $file2)) {
$diff = Compare-Object (Get-Content $file1) (Get-Content $file2)
if ($diff) {
$diff | Format-Table -AutoSize
} else {
Write-Host "No changes detected in SEC-Postes-Baseline"
}
}
InputObject SideIndicator
----------- -------------
MACHINE\Software\Policies\Microsoft\...\Value=0 <=
MACHINE\Software\Policies\Microsoft\...\Value=1 =>
Les lignes <= representent les valeurs dans le snapshot de reference. Les lignes => representent les valeurs actuelles. Une difference sur une cle de securite doit declencher une investigation.
Versionner les snapshots avec Git
Versionnez vos snapshots dans un depot Git prive. git diff produit une sortie diff beaucoup plus lisible que Compare-Object et conserve l'historique complet des modifications. Ajoutez la creation du snapshot a votre pipeline de sauvegarde hebdomadaire GPO.
En resume
LGPO.exe + snapshots Git = visibilite complete sur "ce qui a change" dans les GPO, au niveau du parametre individuel. L'evenement 5136 vous dit qu'une modification a eu lieu, le diff LGPO vous dit laquelle.
Rapport de GPO orphelines et non documentees¶
Les trois signaux d'hygiene GPO degradee¶
Un parc GPO mal maintenu accumule trois types de problemes qui affectent a la fois les performances (traitement GPO plus long) et la securite (configurations imprevisibles).
GPO non liee — La GPO existe dans AD mais n'est liee a aucune OU. Elle consomme de l'espace SYSVOL et de la bande passante de replication sans s'appliquer nulle part. Risque : une GPO orpheline peut etre deliberement reliee par un attaquant.
GPO sans description — Impossible de savoir a quoi elle sert, qui l'a creee, ou si elle est encore utile. Risque : personne ne la supprime par peur de casser quelque chose.
GPO non modifiee depuis plus d'un an — Peut indiquer qu'elle est obsolete, ou au contraire qu'elle est critique et stable. Dans les deux cas, elle doit etre documentee et validee.
Script d'identification¶
# Identify GPOs with hygiene issues: unlinked, no description, or stale
$cutoffDate = (Get-Date).AddDays(-365)
$issues = Get-GPO -All | ForEach-Object {
$gpo = $_
$xml = [xml](Get-GPOReport -Guid $gpo.Id -ReportType Xml)
# LinksTo is present in the XML only if the GPO has at least one link
$isLinked = $null -ne $xml.GPO.LinksTo
[PSCustomObject]@{
Name = $gpo.DisplayName
GUID = $gpo.Id
Unlinked = -not $isLinked
NoDesc = [string]::IsNullOrWhiteSpace($gpo.Description)
Stale = $gpo.ModificationTime -lt $cutoffDate
LastModified = $gpo.ModificationTime.ToString("yyyy-MM-dd")
Status = $gpo.GpoStatus
}
} | Where-Object { $_.Unlinked -or $_.NoDesc -or $_.Stale }
# Display results
$issues | Format-Table Name, Unlinked, NoDesc, Stale, LastModified, Status -AutoSize
# Summary
Write-Host ""
Write-Host "GPOs avec problemes d'hygiene : $($issues.Count)"
Write-Host " Non liees : $(($issues | Where-Object Unlinked).Count)"
Write-Host " Sans desc. : $(($issues | Where-Object NoDesc).Count)"
Write-Host " Non maj +1an: $(($issues | Where-Object Stale).Count)"
Name Unlinked NoDesc Stale LastModified Status
---- -------- ------ ----- ------------ ------
OLD-IE-Settings True True True 2019-03-14 AllSettingsEnabled
TEST-GPO-NePasSupprimer True True False 2025-11-02 AllSettingsEnabled
SEC-Postes-Baseline False False False 2026-04-04 AllSettingsEnabled
GPOs avec problemes d'hygiene : 2
Non liees : 2
Sans desc. : 2
Non maj +1an: 1
Exporter pour le rapport d'audit¶
# Export hygiene report to CSV for audit
$reportPath = "C:\Temp\GPO-Hygiene-$(Get-Date -Format 'yyyyMMdd').csv"
$issues | Export-Csv $reportPath -NoTypeInformation
Write-Host "Hygiene report saved to $reportPath — $($issues.Count) items to review"
Ne supprimez pas sans validation
Avant de supprimer une GPO identifiee comme "non liee" ou "obsolete", verifiez qu'elle n'est pas referencee dans des scripts ou des outils de monitoring par son GUID. Consultez egalement l'equipe applicative responsable du perimetre concerne. La suppression d'une GPO est irreversible sans backup.
En resume
Planifiez ce script en tache mensuelle et exportez le CSV vers votre outil de ticketing. Chaque item genere un ticket de validation. C'est la methode la plus rapide pour maintenir un parc GPO propre et auditables sans revue manuelle fastidieuse.
Pieges en production¶
Pieges courants et comment les eviter¶
Journal Security plein apres activation de l'audit. L'activation de toutes les sous-categories d'audit sur un domaine de taille significative peut saturer le journal Security d'un DC en quelques heures, provoquant l'arret de la journalisation des nouveaux evenements. Augmentez la taille maximale du journal (4 Go recommande) et deployez WEC avant d'activer les sous-categories.
Interroger tous les DC au lieu du PDC. Les requetes d'evenements 5136 doivent cibler en priorite le PDC Emulator — c'est lui qui recoit les ecritures Active Directory. Interroger tous les DC produit des doublons et ralentit les scripts. Utilisez (Get-ADDomain).PDCEmulator pour cibler automatiquement le bon DC.
Confondre la date de modification de la GPO avec la date de modification des parametres. Get-GPO retourne ModificationTime qui correspond au dernier incrément de versionNumber. Un simple changement de description ou de commentaire incrementé la version. Pour identifier ce qui a vraiment change, croisez avec l'evenement 5136 et les snapshots LGPO.
Namespace XML incorrect dans Get-GPOReport. Les parametres de securite (password policy, audit policy, user rights) sont dans le namespace http://www.microsoft.com/GroupPolicy/Settings/Security. Les parametres de registre sont dans un namespace different. Un XPath sans namespace produit toujours zero resultat — sans message d'erreur.
Journal Security plein
Configurez une taille de journal minimum de 128 Mo sur tous les DC, et 4 Go sur les DC de production. Verifiez cette configuration apres chaque promotion DC — la politique de taille du journal ne s'applique pas toujours automatiquement. Utilisez WEC pour centraliser les evenements et reduire la pression sur les journaux locaux.
5136 non active par defaut
L'evenement 5136 necessite l'activation explicite de "Audit Directory Service Changes" dans la politique d'audit avancee. Sans cette activation, les modifications GPO sont entierement silencieuses. Verifiez que cette sous-categorie est configuree sur tous les DC, y compris les DC nouvellement promus, et incluez cette verification dans votre checklist de promotion DC.
En resume
Les deux pieges les plus couteux en production : journal Security plein qui stoppe la journalisation silencieusement, et 5136 non active qui rend les modifications GPO invisibles. Verifiez ces deux points avant de presenter votre dispositif d'audit a un auditeur externe.
Cross-references¶
| Sujet | Reference |
|---|---|
| Gouvernance et delegation des droits GPO | Ch. 02 — Gouvernance |
| Cmdlets PowerShell GroupPolicy (Get-GPOReport, Get-GPO) | Ch. 03 — PowerShell GroupPolicy module |
| Sauvegarde et restauration des GPO | Ch. 05 — Sauvegarde et restauration |
| Baselines de securite Microsoft (SCT) | La Bible GPO — Ch. 22 — Baselines |
| Audit avancé et strategies d'audit subcategories | La Bible GPO — Ch. 13 — Securite avancee |
| Automatisation CI/CD — audit en pipeline | Ch. 23 — Automatisation CI/CD |
En résumé
- À relire : Gouvernance et delegation des droits GPO → Ch. 02 — Gouvernance.
- À relire : Cmdlets PowerShell GroupPolicy (Get-GPOReport, Get-GPO) → Ch. 03 — PowerShell GroupPolicy module.
- À relire : Sauvegarde et restauration des GPO → Ch. 05 — Sauvegarde et restauration.
- À relire : Baselines de securite Microsoft (SCT) → La Bible GPO — Ch. 22 — Baselines.
- Ces renvois prolongent le chapitre avec des mécanismes complémentaires ou des cas d’usage voisins.