Get Group Membership Changes

Scenario:

Get Group Membership Changes

There are sometimes that we would like to know when a member has been removed or added in groups in Active Directory. The script that we will discuss below, is checking groups to find out if any member has been added or removed from groups based on the last time we run it. The script can be used to run it manually when ever you want or you can schedule it to run automatically based on a time interval that you like. After the script performs all changes, it will inform you who has been added or removed from the groups if there was any change. Let’s see below in more detail what the script does.

Collecting the information

When we run the script without any parameter, the script will run against the entire domain. The script will collect all groups and then it go through each group that has collected to perform checks for the changes on it membership.

OrganizationalUnit Parameter

You are also able to select a specific Organizational Unit in Active Directory to run the checks. You will just need to use -OrganizationalUnit and the you will provide the DistinguishedName of the Organizational Unit. This will limit the search for groups only to the specific location.

Code:

param ( [string]$OrganizationalUnit = "" )

if ($OrganizationalUnit -eq ""){ $Groups = (Get-ADGroup -Filter *).Name } else{ try{$Groups = (Get-ADGroup -Filter * -SearchBase "$OrganizationalUnit").Name} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails Exit} }

[adinserter name=”In Article”]

Performing the checks

In order to keep track of the changes, the script is using csv files that are created by the script itself. If it is the first time that you run the script for the specific groups, then you will have no csv files to import the data from. So when you run the script for first time it will assume that all members of the group are new. Every time that you run the script, the csv files are replaced with the current members of each group. Each csv file represents a group. If the csv file exists, it will import the data from the respective file and keep it in memory in order to perform the comparison between the members. Based on the result, it will add the result in an email and a log file.

Using a switch statement we check for three conditions. The first one is that old members exist and we have a value for new members. When this is true then we perform the comparison mentioned above. The second case is when $oldmembers variable is empty. In this case, it means that we do not have a csv file to import previous member in memory and we assume that all new members have been added to the group. This usually happens when you run the script against a group for a first time. This third condition is when we have $newmembers variable empty. Then all new members will be considered that they have been removed. This case happens mostly when we have cleared all members from a group and the group has been left in the system.

Code:

Foreach ($Group in $Groups){ $oldmembers = $null $newmembers = $null $file = "$Path" + "$Group" + ".csv" try{$oldmembers = (Import-Csv $file).SamAccountName} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails} try{Get-ADGroupMember -Identity $Group | Select-Object SamAccountName | Export-Csv $file -NoTypeInformation} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails} $newmembers = (Import-Csv $file).SamAccountName switch -Regex ($oldmembers){ {($oldmembers -ne $null) -and ($newmembers -ne $null)}{ $Difference = Compare-Object $oldmembers $newmembers if ($Difference -ne ""){ Foreach ($DifferenceValue in $Difference){ $DifferenceValueIndicator = $DifferenceValue.SideIndicator switch -Regex ($DifferenceValueIndicator){ {$_ -eq "<="}{ $GroupMember = $DifferenceValue.InputObject $Action = "Removed" $EmailTemp = @" <tr> <td class="colorm">$GroupMember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $GroupMember has been $Action from $Group" Write-Log $LogDetails } {$_ -eq "=>"}{ $GroupMember = $DifferenceValue.InputObject $Action = "Added" $EmailTemp = @" <tr> <td class="colorm">$GroupMember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $GroupMember has been $Action to $Group" Write-Log $LogDetails } } } } Break } {($oldmembers -eq $null) -and ($newmembers -ne $null)}{ Foreach ($newmember in $newmembers){ $Action = "Added" $EmailTemp = @" <tr> <td class="colorm">$newmember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $newmember has been $Action to $Group" Write-Log $LogDetails } Break } {($oldmembers -ne $null) -and ($newmembers -eq $null)}{ Foreach ($oldmember in $oldmembers){ $Action = "Removed" $EmailTemp = @" <tr> <td class="colorm">$oldmember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $oldmember has been $Action from $Group" Write-Log $LogDetails } Break } } } $Email = $EmailUp + $EmailResult + $EmailDown

[adinserter name=”In Article”]

Logs

The script will also create few logs in a log file every time that we run it. Logs will be written when we will have:

A member added to a group

A member removed from a group

Error

Code:

Function Write-Log{ Param ([string]$LogDetails) Add-content $Logfile -value $LogDetails }

Every time the script is running, it will create a new log file. The name of the file will be in the format of log-“current date/time”.log. The script will create the file only if there is something to be added in it. If there are no changes or errors, then no file will be created. The path that the log file will be created is the same with the csv files of the groups.

[adinserter name=”In Article”]

Error Control

In the script there are three error controls. The first one is when we will try to retrieve the groups from Active Directory. If the -OrganizationalUnit parameter is in wrong format it will not be able to retrieve groups. A record will be added in the log file. The second one is when we are trying to import old members from the csv file. If the file does not exists, it will throw an error. The error is added in the log file. The third check is when we are trying export the csv for a group. In case of someone having the specific csv file open or another process is using it, the script will not be able to export the new file and replace the existing one.

You can download the script here or copy it from below. (Note that code within the script might not be copied correctly due to syntax highlighting.)

Hope you like it.

You feedback is appreciated.

If you have any questions or anything else please let me know in the comments below.

[adinserter name=”In Article”]

Related Links:

[adinserter name=”In Article”]

Solution / Script:

<# .SYNOPSIS Name: Get-GroupMembershipChanges.ps1 The purpose of this script is to monitor and inform you for membership changes on groups .DESCRIPTION This is a simple script that checks for changes of the members of groups. The script can run as one off or it can be configured to run a scheduled basis to monitor and inform you for group membership changes. .RELATED LINKS Home .PARAMETER OrganizationalUnit This is the only parameter for the script. It is used to define the Organizational Unit in Active Directory that you want to run the script. You need to provide the DistinguishedName of the Organizational Unit. The default value is "". .NOTES Release Date: 10-08-2018 Author: Stephanos Constantinou .EXAMPLE Run the Get-GroupMembershipChanges.ps1 script without any parameter to run it for the entire domain. Get-GroupMembershipChanges.ps1 .EXAMPLE Run the Get-GroupMembershipChanges.ps1 script with Organizational Unit parameter to run it on specific Organizational Unit in Active Directory. Get-GroupMembershipChanges.ps1 -OrganizationalUnit "OU=Groups,DC=Domain,DC=com" #> param ( [string]$OrganizationalUnit = "" ) Import-Module ActiveDirectory $PasswordFile = "C:\Scripts\Password.txt" $Key = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32) $EmailUser = "[email protected]" $Password = Get-Content $PasswordFile | ConvertTo-SecureString -Key $Key $EmailCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $EmailUser,$Password $To = '[email protected]','[email protected]' $From = '[email protected]' $Path = "C:\Scripts\Files\" $Date = Get-Date -format dd-MM-yyyy-HH-mm-ss $LogFile = "C:\Scripts\Files\log-$Date .log" $EmailResult = "" $EmailUp = @" <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0D%0A%0D%0Abody%20%7B%20font-family%3ASegoe%2C%20%22Segoe%20UI%22%2C%20%22DejaVu%20Sans%22%2C%20%22Trebuchet%20MS%22%2C%20Verdana%2C%20sans-serif%20!important%3B%20color%3A%23434242%3B%7D%0D%0ATABLE%20%7B%20font-family%3ASegoe%2C%20%22Segoe%20UI%22%2C%20%22DejaVu%20Sans%22%2C%20%22Trebuchet%20MS%22%2C%20Verdana%2C%20sans-serif%20!important%3B%20border-width%3A%201px%3Bborder-style%3A%20solid%3Bborder-color%3A%20black%3Bborder-collapse%3A%20collapse%3B%7D%0D%0ATR%20%7Bborder-width%3A%201px%3Bpadding%3A%2010px%3Bborder-style%3A%20solid%3Bborder-color%3A%20white%3B%20%7D%0D%0ATD%20%7Bfont-family%3ASegoe%2C%20%22Segoe%20UI%22%2C%20%22DejaVu%20Sans%22%2C%20%22Trebuchet%20MS%22%2C%20Verdana%2C%20sans-serif%20!important%3B%20border-width%3A%201px%3Bpadding%3A%2010px%3Bborder-style%3A%20solid%3Bborder-color%3A%20white%3B%20background-color%3A%23C3DDDB%3B%7D%0D%0A.colorm%20%7Bbackground-color%3A%2358A09E%3B%20color%3Awhite%3B%7D%0D%0A.colort%7Bbackground-color%3A%2358A09E%3B%20padding%3A20px%3B%20color%3Awhite%3B%20font-weight%3Abold%3B%7D%0D%0A.colorn%7Bbackground-color%3Atransparent%3B%7D%0D%0A%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" /> <body> <h3>Script has been completed successfully</h3> <h4>Changes applied:</h4> <table> <tr> <td class="colort">User</td> <td class="colort">Action</td> <td class="colort">Group</td> </tr> "@ $EmailDown = @" </table> </body> "@ Function Write-Log{ Param ([string]$LogDetails) Add-content $Logfile -value $LogDetails } if ($OrganizationalUnit -eq ""){ $Groups = (Get-ADGroup -Filter *).Name } else{ try{$Groups = (Get-ADGroup -Filter * -SearchBase "$OrganizationalUnit").Name} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails Exit} } Foreach ($Group in $Groups){ $oldmembers = $null $newmembers = $null $file = "$Path" + "$Group" + ".csv" try{$oldmembers = (Import-Csv $file).SamAccountName} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails} try{Get-ADGroupMember -Identity $Group | Select-Object SamAccountName | Export-Csv $file -NoTypeInformation} catch{ $TimeStamp = Get-Date $LogDetails = "$TimeStamp" + " " + "$_" Write-Log $LogDetails} $newmembers = (Import-Csv $file).SamAccountName switch -Regex ($oldmembers){ {($oldmembers -ne $null) -and ($newmembers -ne $null)}{ $Difference = Compare-Object $oldmembers $newmembers if ($Difference -ne ""){ Foreach ($DifferenceValue in $Difference){ $DifferenceValueIndicator = $DifferenceValue.SideIndicator switch -Regex ($DifferenceValueIndicator){ {$_ -eq "<="}{ $GroupMember = $DifferenceValue.InputObject $Action = "Removed" $EmailTemp = @" <tr> <td class="colorm">$GroupMember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $GroupMember has been $Action from $Group" Write-Log $LogDetails } {$_ -eq "=>"}{ $GroupMember = $DifferenceValue.InputObject $Action = "Added" $EmailTemp = @" <tr> <td class="colorm">$GroupMember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $GroupMember has been $Action to $Group" Write-Log $LogDetails } } } } Break } {($oldmembers -eq $null) -and ($newmembers -ne $null)}{ Foreach ($newmember in $newmembers){ $Action = "Added" $EmailTemp = @" <tr> <td class="colorm">$newmember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $newmember has been $Action to $Group" Write-Log $LogDetails } Break } {($oldmembers -ne $null) -and ($newmembers -eq $null)}{ Foreach ($oldmember in $oldmembers){ $Action = "Removed" $EmailTemp = @" <tr> <td class="colorm">$oldmember</td> <td>$Action</td> <td>$Group</td> </tr> "@ $EmailResult = $EmailResult + $EmailTemp $TimeStamp = Get-Date $LogDetails = "$TimeStamp $oldmember has been $Action from $Group" Write-Log $LogDetails } Break } } } $Email = $EmailUp + $EmailResult + $EmailDown if ($EmailResult -ne ""){ $EmailParameters = @{ To = $To Subject = "Group Membership Changes Report $(Get-Date -format dd/MM/yyyy)" Body = $Email BodyAsHtml = $True Priority = "High" UseSsl = $True Port = "587" SmtpServer = "smtp.office365.com" Credential = $EmailCredentials From = $From} send-mailmessage @EmailParameters }

[adinserter name=”Matched-Content”]