I encountered a scenario at work where I had to monitor any changes performed on an account’s active directory membership groups on a daily/weekly basis. I believe there may be some out-of-box-experience solutions for this; however, I was recently moved into a new environment and I felt the quickest way to achieve this was through Powershell.
The script’s logic works around two text files, the first text file has a dump of all the group memberships at a certain time period, let’s say at the beginning of the month. The second file is a dump of all group memberships at the time of the script’s execution – the script works by comparing the newly created dump file of group memberships with the older dump file and displaying the differences if any. Since the second file is generated each time the script is executed, the script will also delete it upon exit, allowing for a clean slate for the next execution of the script. Lastly, the script will also email the results to you via SMTP protocol for a close to real-time notification.
I then set this script to run within a Windows Scheduled Task on a daily basis so that each day I would receive an email of any changes to the domain account.
Also, please keep in mind that in order to use the Get-ADUser cmdlet, you need to do Import-Module ActiveDirectory. If you try to Import-Module ActiveDirectory and it fails then you may need to enable the “Active Directory Module for Windows PowerShell” under Remote Server Administration Tools in Add/Remove Windows Features within your Control Panel. There are a handful of internet blogs that explain this step.
Here are some snippets of the script’s execution:
The script found no differences between the two files.
The script found differences in the two files, note that if there are differences, they are only shown in the email sent.
Here the first file was not found and the script prompted the user to create it. After it was created, the script went on to check the differences if any.
#fileName1 gathers your current domain group memberships and puts it into a file. It is ideal to keep this file #as a starting point to which you can compare against in the event that you group membership changes. $fileName1 = "currentAD.txt" #fileName2 gathers your current domain group memberships each time you execute the script and compares it #against your fileName1 file. Any changes between the two files will be compared and displayed. $fileName2 = "currentADtemp.txt" #This is a global variable to keep track of the contents of $checkDifference variable #which contains the difference between $fileName1 and $fileName2 $Global:checkDifference = $checkDifference function emailComparison ($checkDifference) { #Emails the results of the comparison via SMTP $From = "youruser@yourdomain.com" $To = "youruser@yourdomain.com" $Subject = "Domain Group Membership Change Report" $Body = $checkDifference $SMTPServer = "yourmailserver.yourdomain.com" $SMTPPort = "25" #This is the default port, if yours is different, you'll need to change it here.# Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer Write-Host "Email sent successfully..." -Foregroundcolor Green #Delete the temp file after email is sent. #This file is generated each time the script is executed. rm H:\Code\currentADtemp.txt } function comparingFiles ($fileName1, $fileName2) { #Performs the comparison between the two files $checkDifference = Compare-Object (gc $fileName1) (gc $fileName2) #Adds text to output to show which file has the difference $checkDifference | foreach { if ($_.sideindicator -eq '<=') {$_.sideindicator = $fileName1} if ($_.sideindicator -eq '=>') {$_.sideindicator = $fileName2} } #If content is $checkDifference is not empty or $NULL if ($checkDifference) { #Formats the content of $checkDifference into list format #Out-String changes the content from object to string $checkDifference = ( $checkDifference | Format-List | Out-String ) #Output to console Write-Host "Comparison of files completed successfully..." -Foreground Green #Function call to emailComparison which handles the actual generation of the email message emailComparison $checkDifference } else { #If there are no differences, you will still get emailed but with the below text $output = "There are no differences between $fileName1 and $fileName2." emailComparison $output Write-Host $output } } function mainFunction () { #This is the main part of the script, writing it as a function, allows me to start the script again within the script #Checks if $fileName1 file exists if (Test-Path $fileName1) { #Takes content of currentAD.txt file into $file1Content variable $file1Content = gc .\currentAD.txt #Checks if $fileName2 file exists if (Test-Path $fileName2) { #Takes content of currentADtemp.txt file into $file2Content variable $file2Content = gc .\currentADtemp.txt #Function call passing $fileName1 and $fileName2 variables as arguments comparingFiles $fileName1 $fileName2 } else { #Condition to create $fileName2 if it does not exist. Write-host "Creating $fileName2..." (Get-ADUser -Identity "domainuser" -Properties MemberOf | Select-Object MemberOf).MemberOf > $fileName2 $file2Content = gc .\currentADtemp.txt if (Test-Path $fileName2) { #Test condition to check if $fileName2 exists Write-Host "$fileName2 was created successfully!" -ForegroundColor Green #Function call passing $fileName1 and $fileName2 variables as arguments comparingFiles $fileName1 $fileName2 } else { #If creation of $fileName2 failes for whatever reason Write-Host "$fileName2 creation failed!" -ForegroundColor Red } } } else { #Condition to handle if $fileName1 does not exist #Provides user with option to create the file during script execution Write-Host "$fileName1 not found!" -ForegroundColor Red $currentADtext = Read-Host "Do you want to create a new currentAD.txt? (Y/N)" #Condition to allow script to decide if file creation should occur or not if ($currentADtext -eq 'Y' -or $currentADtext -eq 'y') { (Get-ADUser -Identity "domainuser" -Properties MemberOf | Select-Object MemberOf).MemberOf > $fileName1 Write-Host "Creating a new currentAD.txt file..." Write-Host "Restarting script to consume newly created currentAD.txt file...`n" #Once $fielName1 is created, by calling the script's main function again, #it prevents user from having to re-run the script themselves mainFunction } #Condition if file creation should not occur elseif ($currentADtext -eq 'N' -or $currentADtext -eq 'n') { Write-Host "File creation refused by user...`n" -ForegroundColor Yellow } #Condition to handle invalid input else { Write-Host "Invalid input received for currentAD.txt creation prompt!`n" -ForegroundColor Red } } } #Script starts here!# #I am calling the mainFunction here, this actually starts the program upon execution# mainFunction