The Security Imperative
In a Windows domain environment, Active Directory Security Groups are the primary mechanism for granting access to sensitive data. If an employee is added to the “HR-Confidential” group, they instantly gain access to the payroll file share. If a user is added to the “Domain Admins” group, they effectively own the entire network.
Therefore, tracking exactly who added a user to a highly privileged group, and when they did it, is a critical requirement for cybersecurity audits and compliance frameworks like SOX or HIPAA. While you can search through the massive Security Event Log in the Event Viewer, it is incredibly inefficient. PowerShell allows you to query the event logs across all Domain Controllers and instantly generate a clean audit report.
Step 1: Enabling Audit Policies
Before PowerShell can find the events, Windows must actually record them. By default, Active Directory does not log every single group membership change.
You must enable this via Group Policy (GPO) on the Default Domain Controllers Policy.
- Open the Group Policy Management Console (gpmc.msc).
- Edit the Default Domain Controllers Policy.
- Navigate to Computer Configuration > Policies > Windows Settings > Security Settings > Advanced Audit Policy Configuration > Audit Policies > Account Management.
- Enable Audit Security Group Management for both Success and Failure.
Once enabled, the Domain Controller will begin writing Event ID 4728 (Member Added) and Event ID 4729 (Member Removed) to the Security Log.
Step 2: Querying the Logs with PowerShell
To extract this data, open PowerShell with Administrator privileges. You will use the Get-WinEvent cmdlet to query the Security log for Event ID 4728.
$Events = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4728} -MaxEvents 50
This command grabs the 50 most recent “User Added to Group” events. However, the raw event object is difficult to read. The critical information (Who was added? Which group? Who did it?) is buried inside the XML properties of the event message.
Step 3: Parsing the Event Data
To create a readable report, you must iterate through the events and extract the specific property arrays.
$Report = foreach ($Event in $Events) {
# Extract the XML Properties
$EventXML = [xml]$Event.ToXml()
$Properties = $EventXML.Event.EventData.Data
# Map the properties to variables
$TargetUser = ($Properties | Where-Object Name -eq 'MemberName').'#text'
$GroupName = ($Properties | Where-Object Name -eq 'TargetUserName').'#text'
$AdminUser = ($Properties | Where-Object Name -eq 'SubjectUserName').'#text'
# Create a custom PowerShell object for the report
[PSCustomObject]@{
TimeAdded = $Event.TimeCreated
GroupName = $GroupName
AddedUser = $TargetUser
AdminName = $AdminUser
}
}
Step 4: Outputting the Audit Report
You now have a clean array of custom objects stored in the $Report variable. You can format it into a table directly in the console:
$Report | Format-Table -AutoSize
The output will clearly show the exact timestamp, the name of the Security Group (e.g., Domain Admins), the distinguished name of the user who was granted access, and the username of the Administrator who authorized the change. For compliance audits, you can easily pipe this output directly to a CSV file:
$Report | Export-Csv -Path "C:\Audit\GroupChanges.csv" -NoTypeInformation