I recently did a post on this before which you can view here about monitoring Group Policy objects, however its always a good idea to improve on your work as you go as this is version 2 of that script, that is more technical but is very helpful in pinpointing events.
Note : This will take events directly from the Domain Controllers which means a valid entry is present on the Domain Controller for this report to exist, which means this is only as valid as the "scope " of your security log before it is overridden.
The previous script did have no cavets and I am sure this one will with time, but in testing all has been well and the results are very informative, this is how the plan looks and what the script does:
- Define the XML for the Event Log query
- Get a list of GPO GUID's
- Add the DisplayName of the GPO to a field in the CSV so its human readable
- Cycle though all the Domain Controllers and look for the Event ID
- Map the relevant fields to the variables names in the script
- Output this to a CSV file
First we need to get an example alert which will come from the Security log and we are looking for the Event ID of 5136 which translates as "A directory service object was modified" however there are hundreds of these but we are interested in a certain type of alert, which is driven by the XML:
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=5136) and TimeCreated[timediff(@SystemTime) <= 86400000]]]
and
*[EventData[Data[@Name='ObjectClass'] and (Data='groupPolicyContainer')]]
and
*[EventData[Data[@Name='AttributeLDAPDisplayName'] and (Data='versionNumber')]]
and
*[EventData[Data[@Name='OperationType'] and (Data='%%14674')]]
</Select>
</Query>
</QueryList>
This will give you events like this that are only relevant to the task at hand, we then need to extract data from this event which will be the script variables these are in bold below:
A directory service object was modified.
Subject:
Security ID: bear.local\GPO.Editor
Account Name: gpo.editor
Account Domain: BEAR
Logon ID: 0x8EAA37C51
Directory Service:
Name: bear.local
Type: Active Directory Domain Services
Object:
DN: CN={3E84CDDE-5EEA2-4381-B566-C36AAE9BF012},CN=POLICIES,CN=SYSTEM,DC=BEAR,DC=LOCAL
GUID: CN={3E84CDDE-5EEA2-4381-B566-C36AAE9BF012},CN=Policies,CN=System,DC=bear,DC=local
Class: groupPolicyContainer
Attribute:
LDAP Display Name: versionNumber
Syntax (OID): 2.5.5.9
Value: 262146
Operation:
Type: Value Added
Correlation ID: {44ecdea7-cb38-4eb7-886d-881721cf9c98}
Application Correlation ID: -
This has obviously given us the GUID of "3E84CDDE-5EEA2-4381-B566-C36AAE9BF012" which is not very helpful for humans to read, however we can use this command which will then give us the details:
Get-GPO -Guid CN=3E84CDDE-5EEA2-4381-B566-C36AAE9BF012
This will then tell us the following information from which we require the DisplayName attribute:
DisplayName : Disable Edge (Chrome is better)
DomainName : bear.local
Owner : BEAR\gpo.manager
Id : 3e84cdde-5dd4-4385-b565-c36aae9bf012
GpoStatus : AllSettingsEnabled
Description : He who shall not be named!
CreationTime : 04/09/2024 17:15:06
ModificationTime : 17/09/2024 14:11:54
UserVersion : AD Version: 4, SysVol Version: 4
ComputerVersion : AD Version: 3, SysVol Version: 3
We now have all the information to get this CSV file correct, so lets get some code to get all that done with one script which will track not only what has been changed but how many times it has been changed with the version increments.
Script : GPO-XMLInterrogate.ps1
# Import required modules
Import-Module ActiveDirectory
Import-Module GroupPolicy
# XML query with time limitation (adjust as needed)
$xmlQuery = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=5136) and TimeCreated[timediff(@SystemTime) <= 86400000]]]
and
*[EventData[Data[@Name='ObjectClass'] and (Data='groupPolicyContainer')]]
and
*[EventData[Data[@Name='AttributeLDAPDisplayName'] and (Data='versionNumber')]]
and
*[EventData[Data[@Name='OperationType'] and (Data='%%14674')]]
</Select>
</Query>
</QueryList>
"@
# Function to resolve GUID to GPO display name
function Get-GPODisplayName {
param($guidString)
if ([string]::IsNullOrWhiteSpace($guidString)) {
Write-Warning "Empty GUID string provided"
return "Unknown GPO (Empty GUID)"
}
try {
$gpo = Get-GPO -Guid $guidString -ErrorAction Stop
return $gpo.DisplayName
} catch {
$errorMessage = $_.Exception.Message
Write-Warning "Error retrieving GPO for GUID $guidString : $errorMessage"
return $null
}
}
# Get all domain controllers
$domainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty Hostname
# Array to store all events
$allEvents = @()
# Query each domain controller
foreach ($dc in $domainControllers) {
Write-Host "Querying $dc..."
$events = Get-WinEvent -FilterXml $xmlQuery -ComputerName $dc -ErrorAction SilentlyContinue
if ($events) {
$allEvents += $events | ForEach-Object {
$eventXML = [xml]$_.ToXml()
$objectDN = $eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'ObjectDN' } | Select-Object -ExpandProperty '#text'
$guidString = $objectDN -replace 'CN=\{(.*?)\},.*', '$1'
$subjectUserName = $eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'SubjectUserName' } | Select-Object -ExpandProperty '#text'
$subjectDomainName = $eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'SubjectDomainName' } | Select-Object -ExpandProperty '#text'
$versionNumber = $eventXML.Event.EventData.Data | Where-Object { $_.Name -eq 'AttributeValue' } | Select-Object -ExpandProperty '#text'
$eventData = @{
TimeCreated = $_.TimeCreated
DC = $dc
SubjectUserName = "$subjectDomainName\$subjectUserName"
GUID = $guidString
DisplayName = (Get-GPODisplayName $guidString)
VersionNumber = $versionNumber
}
New-Object PSObject -Property $eventData
}
}
}
# Export to CSV
$csvPath = "GPOChanges.csv"
$allEvents | Select-Object TimeCreated, DC, SubjectUserName, GUID, DisplayName, VersionNumber | Export-Csv -Path $csvPath -NoTypeInformation
Write-Host "Report has been exported to $csvPath"
This will the save a CSV file that will be formatted in a very easy to read format for technical people to understand which will then as you can see track all the version updates and relevant times.