Audit Active Directory group membership changes with PowerShell
Active Directory group membership affects file shares, VPN access, administrative roles and application permissions. A user added to a privileged group can gain access within minutes, while a removed account may lose access during a critical support task. A PowerShell script to audit Active Directory group membership changes gives administrators a repeatable way to identify who made each change, which account was modified and when the event occurred.
The method relies on Windows Security event logs collected from domain controllers. It can support a daily review, an incident investigation or a scheduled export for a security information and event management platform. The same approach works across a small Brisbane office, a multi-site organisation in Sydney and Melbourne, or a hybrid environment with infrastructure hosted in AWS and on premises.
Australian organisations also need to consider local time zones and retention requirements. A report generated in Perth should be interpreted differently from one created in AEDT in Canberra or Melbourne, and regulated environments may need documented access controls around audit data. The script below provides a practical baseline that can be adapted to those operational requirements.
Enable the required audit policy
The relevant audit events are generated only when the appropriate advanced audit policy is enabled. In Group Policy Management, open the policy applied to domain controllers and navigate to Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > Audit Policies > Account Management.
Enable Audit Security Group Management for Success events. The main event IDs are 4728 and 4729 for global groups, 4732 and 4733 for local groups, and 4756 and 4757 for universal groups. The first event in each pair records an addition, while the second records a removal.
Apply the policy to all domain controllers and allow replication to complete. A common operational mistake is enabling auditing on only one server. Group membership changes are recorded by the domain controller that processes the write, so querying a single server can produce an incomplete history.
Collect events from every domain controller
The script uses the ActiveDirectory module to discover domain controllers and Get-WinEvent to query each Security log. It extracts the event data by field name rather than relying on a fixed property position, which makes the output easier to understand and less fragile across Windows Server versions.
Import-Module ActiveDirectory
param(
[int]$Days = 1,
[string]$OutputPath = ".\ad-group-membership-audit.csv"
)
$eventIds = 4728, 4729, 4732, 4733, 4756, 4757
$startTime = (Get-Date).AddDays(-$Days)
$results = foreach ($dc in Get-ADDomainController -Filter * | Sort-Object HostName) {
Write-Verbose "Querying $($dc.HostName)"
$events = Get-WinEvent -ComputerName $dc.HostName `
-FilterHashtable @{
LogName = 'Security'
Id = $eventIds
StartTime = $startTime
} -ErrorAction SilentlyContinue
foreach ($event in $events) {
$xml = [xml]$event.ToXml()
$data = @{}
foreach ($item in $xml.Event.EventData.Data) {
$data[$item.Name] = [string]$item.'#text'
}
[pscustomobject]@{
TimeCreated = $event.TimeCreated
DomainController = $dc.HostName
EventId = $event.Id
Action = if ($event.Id -in 4728,4732,4756) { 'Added' } else { 'Removed' }
GroupName = $data['TargetUserName']
GroupDomain = $data['TargetDomainName']
MemberName = $data['MemberName']
MemberId = $data['MemberId']
ChangedBy = "$($data['SubjectDomainName'])\$($data['SubjectUserName'])"
SubjectLogonId = $data['SubjectLogonId']
}
}
}
$results | Sort-Object TimeCreated |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Run the script from a management workstation or domain controller using an account with permission to read the remote Security logs. Windows Firewall, log forwarding settings and network segmentation can prevent remote queries. In tightly controlled environments, Windows Event Forwarding may be preferable to direct polling.
Interpret the audit fields correctly
The GroupName field identifies the target group, while MemberName identifies the account or group that was added or removed. ChangedBy is the actor recorded in the event. These fields can expose changes made by a service account, help desk operator, delegated administrator or compromised credential.
The event ID shows the group scope and action. For example, 4728 means a member was added to a global security-enabled group, while 4757 means a member was removed from a universal group. Keeping the original Event ID in the CSV is useful when correlating activity with other Windows events.
The TimeCreated value normally reflects the domain controller’s system time. Standardise domain controller time with reliable NTP and report the time zone explicitly. This is especially important for organisations operating across Sydney, Perth and Darwin, where local business hours do not align. Converting reports to UTC for central storage while displaying Australian local time for analysts can prevent misleading timelines.
Focus reviews on privileged groups
Not every group change has the same risk. Membership changes involving Domain Admins, Enterprise Admins, Administrators, Account Operators, Backup Operators, server administration groups and application-specific access groups deserve faster review. A useful next step is to filter the exported data against a maintained list of sensitive groups.
$sensitiveGroups = @(
'Domain Admins',
'Enterprise Admins',
'Administrators',
'Backup Operators',
'Account Operators'
)
Import-Csv .\ad-group-membership-audit.csv |
Where-Object { $_.GroupName -in $sensitiveGroups } |
Sort-Object TimeCreated -Descending |
Format-Table TimeCreated, GroupName, MemberName, ChangedBy, DomainController
Organisations using Microsoft 365, Entra ID or cloud-managed applications should keep this report focused on on-premises AD changes while correlating it with cloud audit logs. A hybrid identity incident may involve a local group update followed by a token refresh or a change to a cloud role.
Schedule the report and protect its output
A scheduled task can run the audit at a fixed interval and write the CSV to a restricted folder. Daily execution is often sufficient for routine governance, while smaller intervals may be appropriate for privileged access monitoring. Use a dedicated service account or gMSA where possible, and grant it only the permissions required to read event logs and query domain controllers.
Do not leave audit exports in a broadly accessible share. Apply NTFS permissions, configure a sensible retention period and consider forwarding the records to a central platform. A company with sites in Adelaide and Canberra may need retention and evidence procedures that align with contractual obligations, the Privacy Act 1988 and internal security policies. The Essential Eight also makes sound account and privilege monitoring a practical control, even where a specific reporting format is not mandated.
For hybrid estates, the same operational discipline should extend to cloud logs and network controls. Teams documenting AWS networking alongside domain services can correlate a group change with access to a VPC, bastion host or management subnet.
Troubleshoot missing or incomplete results
If the CSV is empty, confirm that the audit policy is applied with auditpol /get /subcategory:"Security Group Management" and verify that the Security log contains one of the six event IDs. Check that the query period is long enough and that the remote account can read each domain controller’s log.
If only some changes appear, inspect all domain controllers and confirm replication health. A change may also have been performed through an application or identity workflow that uses a service account, so the recorded actor may differ from the human who initiated the request. Compare the SubjectLogonId with logon events when an investigation requires stronger attribution.
Finally, test the script with a controlled membership change in a non-production group. Confirm the event ID, member name, actor, domain controller and timestamp before relying on the report for incident response or audit evidence. A repeatable test helps detect policy scope errors before a real privileged access change is missed.