In every enterprise environment, Active Directory (AD) group memberships play a pivotal role in access control and security. Unauthorized modifications to privileged groups like "Domain Admins" or "Enterprise Admins" can expose your organization to risk. Enter ADHawk: a simple but powerful PowerShell script that watches over your AD group memberships like a hawk—hence the name. ADHawk ADHawk is designed to run as a scheduled task every five minutes, automatically logging changes and sending email alerts for any detected modifications. Lightweight, modular, and highly customizable, this tool is perfect for security-conscious sysadmins who want real-time visibility without deploying heavyweight monitoring solutions. What ADHawk Monitors What ADHawk Monitors By default, ADHawk keeps a close eye on the following critical groups: "Domain Admins", "Exchange Admins", "Enterprise Admins", "Schema Admins", "Backup Operators", "Account Operators", "Server Operators" "Domain Admins", "Exchange Admins", "Enterprise Admins", "Schema Admins", "Backup Operators", "Account Operators", "Server Operators" You can easily modify this list to suit your organizational structure. How It Works How It Works Initialization: Checks for a designated state directory (e.g., C:\customapps\adgroupmembers). Creates it if it doesn't exist. Group Enumeration: Fetches the current members of each monitored group via Get-ADGroupMember. Stores each group’s state as a sorted list of SamAccountNames. Change Detection: Compares current membership against previously saved state. Uses Compare-Object to identify additions or removals. Notification & Logging: Constructs a detailed message with a timestamp and change summary. Sends the alert via email. Appends the event to a log file (change.log) for audit-ability. State Refresh: Updates the stored file with the current membership snapshot. Initialization: Checks for a designated state directory (e.g., C:\customapps\adgroupmembers). Creates it if it doesn't exist. Initialization: Checks for a designated state directory (e.g., C:\customapps\adgroupmembers). Creates it if it doesn't exist. Checks for a designated state directory (e.g., C:\customapps\adgroupmembers). C:\customapps\adgroupmembers Creates it if it doesn't exist. Group Enumeration: Fetches the current members of each monitored group via Get-ADGroupMember. Stores each group’s state as a sorted list of SamAccountNames. Group Enumeration: Fetches the current members of each monitored group via Get-ADGroupMember. Stores each group’s state as a sorted list of SamAccountNames. Fetches the current members of each monitored group via Get-ADGroupMember. Get-ADGroupMember Stores each group’s state as a sorted list of SamAccountNames. SamAccountNames Change Detection: Compares current membership against previously saved state. Uses Compare-Object to identify additions or removals. Change Detection: Compares current membership against previously saved state. Uses Compare-Object to identify additions or removals. Compares current membership against previously saved state. Uses Compare-Object to identify additions or removals. Compare-Object Notification & Logging: Constructs a detailed message with a timestamp and change summary. Sends the alert via email. Appends the event to a log file (change.log) for audit-ability. Notification & Logging: Constructs a detailed message with a timestamp and change summary. Sends the alert via email. Appends the event to a log file (change.log) for audit-ability. Constructs a detailed message with a timestamp and change summary. Sends the alert via email. Appends the event to a log file (change.log) for audit-ability. change.log State Refresh: Updates the stored file with the current membership snapshot. State Refresh: Updates the stored file with the current membership snapshot. Updates the stored file with the current membership snapshot. Email Notification Setup (REQUIRED EDIT) Email Notification Setup (REQUIRED EDIT) Before using ADHawk in your environment, you must customize the following parameters: you must customize $EmailFrom = "ADHawk@YourCompanyHere.com" $EmailTo = "notifyParty1@YourCompanyHere.com,notifyParty2@YourCompanyHere.com" $SMTPServer = "Your SMTP server IP here" $EmailFrom = "ADHawk@YourCompanyHere.com" $EmailTo = "notifyParty1@YourCompanyHere.com,notifyParty2@YourCompanyHere.com" $SMTPServer = "Your SMTP server IP here" These fields define where your email alerts come from, who receives them, and which SMTP server relays them. Implementation Steps Implementation Steps Prerequisites: PowerShell with ActiveDirectory module installed SMTP server configured and accessible Deployment: Save the full script to a .ps1 file, e.g., ADHawk.ps1 Modify the group list or email fields as needed Create the directory C:\customapps\adgroupmembers Scheduling the Task: Open Task Scheduler Create a new task: Trigger: Every 5 minutes Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Testing: Manually run the script and simulate changes by adding/removing users Verify emails are sent and logs are updated Prerequisites: PowerShell with ActiveDirectory module installed SMTP server configured and accessible Prerequisites: PowerShell with ActiveDirectory module installed SMTP server configured and accessible PowerShell with ActiveDirectory module installed ActiveDirectory SMTP server configured and accessible Deployment: Save the full script to a .ps1 file, e.g., ADHawk.ps1 Modify the group list or email fields as needed Create the directory C:\customapps\adgroupmembers Deployment: Save the full script to a .ps1 file, e.g., ADHawk.ps1 Modify the group list or email fields as needed Create the directory C:\customapps\adgroupmembers Save the full script to a .ps1 file, e.g., ADHawk.ps1 .ps1 ADHawk.ps1 Modify the group list or email fields as needed Create the directory C:\customapps\adgroupmembers C:\customapps\adgroupmembers Scheduling the Task: Open Task Scheduler Create a new task: Trigger: Every 5 minutes Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Scheduling the Task: Open Task Scheduler Create a new task: Trigger: Every 5 minutes Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Open Task Scheduler Create a new task: Trigger: Every 5 minutes Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Trigger: Every 5 minutes Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Trigger: Every 5 minutes Trigger: Every 5 minutes Trigger: Action: Run powershell.exe with argument: -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Action: Run powershell.exe with argument: Action: powershell.exe -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" -ExecutionPolicy Bypass -File "C:\Path\To\ADHawk.ps1" Run with highest privileges Run with highest privileges Run with highest privileges Testing: Manually run the script and simulate changes by adding/removing users Verify emails are sent and logs are updated Testing: Manually run the script and simulate changes by adding/removing users Verify emails are sent and logs are updated Manually run the script and simulate changes by adding/removing users Verify emails are sent and logs are updated Why ADHawk? Why ADHawk? Minimal Footprint: No agents or external dependencies Real-Time Monitoring: 5-minute check intervals provide near-instant detection Customizable: Choose which groups to watch and who gets notified Audit Trail: Keeps a log of every change for forensic or compliance reviews Minimal Footprint: No agents or external dependencies Minimal Footprint: Real-Time Monitoring: 5-minute check intervals provide near-instant detection Real-Time Monitoring: Customizable: Choose which groups to watch and who gets notified Customizable: Audit Trail: Keeps a log of every change for forensic or compliance reviews Audit Trail: Final Thoughts Final Thoughts ADHawk embodies what PowerShell does best—simple, direct, and effective automation. Whether you're a solo sysadmin or part of a larger security team, this script gives you a lightweight, proactive way to harden your AD infrastructure. Try it out, customize it to your needs, and let ADHawk keep a vigilant eye on your domain. #Another /\_[]_/\ # fine |] _||_ [| # ___ \/ || \/ # /___\ || # (|0 0|) || # __/{\U/}\_ ___/vvv # / \ {~} / _|_P| # | /\ ~ /_/ [] # |_| (____) # \_]/______\ Barberion # _\_||_/_ Production # (_,_||_,_) # #Requires -Modules ActiveDirectory $groups = @("Domain Admins", "Exchange Admins", "Enterprise Admins", "Schema Admins", "Backup Operators", "Account Operators", "Server Operators") $stateFilePath = "C:\customapps\adgroupmembers" $logFilePath = "C:\customapps\adgroupmembers\change.log" # directory exists? if (-not (Test-Path $stateFilePath)) { New-Item -Path $stateFilePath -ItemType Directory } # get group members and return as simple string array function Get-GroupMembers { param ( [string]$GroupName ) Get-ADGroupMember -Identity $GroupName | Select-Object -ExpandProperty SamAccountName | Sort-Object } # send email notification function Send-Email { param ( [string]$Subject, [string]$Body ) $EmailFrom = "ADAccountHawk@YourCompanyHere.com" $EmailTo = "notifyParty1@YourCompanyHere.com,notifyParty2@YourCompanyHere.com" $SMTPServer = "Your SMTP server IP here" Send-MailMessage -From $EmailFrom -To $EmailTo -Subject $Subject -Body $Body -SmtpServer $SMTPServer # Append to change.log Add-Content -Path $logFilePath -Value $Body Add-Content -Path $logFilePath -Value "rn" # Add a blank line } foreach ($group in $groups) { $currentMembers = Get-GroupMembers -GroupName $group $filePath = Join-Path -Path $stateFilePath -ChildPath "$group.csv" if (Test-Path $filePath) { $previousMembers = Get-Content $filePath $differences = Compare-Object -ReferenceObject $previousMembers -DifferenceObject $currentMembers if ($differences) { $changes = $differences | ForEach-Object { if ($_.SideIndicator -eq "<=") { "$($_.InputObject) was removed from $group" } else { "$($_.InputObject) was added to $group" } } $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $body = "Changes detected at ${timestamp}:rn" + ($changes -join "rn") Send-Email -Subject "AD Group Altered" -Body $body } } # Always update the file with the current state for next comparison $currentMembers | Out-File $filePath } #Another /\_[]_/\ # fine |] _||_ [| # ___ \/ || \/ # /___\ || # (|0 0|) || # __/{\U/}\_ ___/vvv # / \ {~} / _|_P| # | /\ ~ /_/ [] # |_| (____) # \_]/______\ Barberion # _\_||_/_ Production # (_,_||_,_) # #Requires -Modules ActiveDirectory $groups = @("Domain Admins", "Exchange Admins", "Enterprise Admins", "Schema Admins", "Backup Operators", "Account Operators", "Server Operators") $stateFilePath = "C:\customapps\adgroupmembers" $logFilePath = "C:\customapps\adgroupmembers\change.log" # directory exists? if (-not (Test-Path $stateFilePath)) { New-Item -Path $stateFilePath -ItemType Directory } # get group members and return as simple string array function Get-GroupMembers { param ( [string]$GroupName ) Get-ADGroupMember -Identity $GroupName | Select-Object -ExpandProperty SamAccountName | Sort-Object } # send email notification function Send-Email { param ( [string]$Subject, [string]$Body ) $EmailFrom = "ADAccountHawk@YourCompanyHere.com" $EmailTo = "notifyParty1@YourCompanyHere.com,notifyParty2@YourCompanyHere.com" $SMTPServer = "Your SMTP server IP here" Send-MailMessage -From $EmailFrom -To $EmailTo -Subject $Subject -Body $Body -SmtpServer $SMTPServer # Append to change.log Add-Content -Path $logFilePath -Value $Body Add-Content -Path $logFilePath -Value "rn" # Add a blank line } foreach ($group in $groups) { $currentMembers = Get-GroupMembers -GroupName $group $filePath = Join-Path -Path $stateFilePath -ChildPath "$group.csv" if (Test-Path $filePath) { $previousMembers = Get-Content $filePath $differences = Compare-Object -ReferenceObject $previousMembers -DifferenceObject $currentMembers if ($differences) { $changes = $differences | ForEach-Object { if ($_.SideIndicator -eq "<=") { "$($_.InputObject) was removed from $group" } else { "$($_.InputObject) was added to $group" } } $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $body = "Changes detected at ${timestamp}:rn" + ($changes -join "rn") Send-Email -Subject "AD Group Altered" -Body $body } } # Always update the file with the current state for next comparison $currentMembers | Out-File $filePath }