• Skip to primary navigation
  • Skip to main content
  • Skip to footer

Stephanos Constantinou Blog

PowerShell Scripting

  • Home
  • Blogs
    • PowerShell Tutorials
    • PowerShell Scripts
    • PowerShell Modules
      • Modules Cmdlets
    • Software Reviews
  • About
  • Contact
You are here: Home / PowerShell Scripts / Get Group Membership Changes

Get Group Membership Changes

13/08/2018 by Stephanos 6 Comments

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:

  • PowerShell Scripts
  • PowerShell Tutorials
  • Import-Module – Microsoft Docs
  • Get-Content – Microsoft Docs
  • ConvertTo-SecureString – Microsoft Docs
  • New-Object – Microsoft Docs
  • Get-Date – Microsoft Docs
  • Add-Content – Microsoft Docs
  • about_Functions | Microsoft Docs
  • about_Switch | Microsoft Docs
  • Get-ADGroup – Microsoft Docs
  • Import-Csv – Microsoft Docs
  • Get-ADGroupMember – Microsoft Docs
  • Select-Object – Microsoft Docs
  • Export-Csv – Microsoft Docs
  • Compare-Object – Microsoft Docs
  • Send-MailMessage – Microsoft Docs
  • about_If | Microsoft Docs

[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”]

Summary
Get Group Membership Changes
Article Name
Get Group Membership Changes
Description
Get Group Membership Changes. Here you will find information about Get Group Membership Changes script and its use. Stephanos Constantinou Blog
Author
Stephanos
Publisher Name
Stephanos Constantinou Blog
Publisher Logo
Stephanos Constantinou Blog

Filed Under: PowerShell Scripts Tagged With: Add-Content, Arithmetic Operators, Compare-Object, Comparison Operators, ConvertTo-SecureString, Export-Csv, Functions, Get-ADGroup, Get-ADGroupMember, Get-Content, Get-Date, Import-Csv, Import-Module, Logical Operators, New-Object, PowerShell If, PowerShell Switch, Select-Object, Send-MailMessage

Reader Interactions

Comments

  1. Stephen Roberts says

    14/08/2018 at 13:24

    One note. You know you can export-clixml a credential object and import it back in with import-clixml file name and it is alrady incripted and you don’t have to make a key and create the credential.

    Reply
  2. Stephanos says

    14/08/2018 at 15:06

    Hello Stephen,

    Thanks for the note. I know about Export-Clixml and it is really helpful on credentials. The problem is that you are not able to run the script with a different user and on a different computer/server using that method.

    I use this method in order to allow me to run it with different user and on a different computer.

    Thanks
    Stephanos

    Reply
  3. Paul Konen says

    19/10/2019 at 20:44

    Great script. I can use it on a daily base with scheduled task so once per day I get an overview. This is better then set triggers on event id’s and get constant messages. Is it possible to add who made the change?
    Thanks in advance. Regards, Paul Konen

    Reply
  4. Stephanos says

    21/10/2019 at 09:29

    Hi Paul,

    On the current state of the script we are not able to get the account that made the change. In order to get this information we need to check the events and a different approach is needed

    Reply
  5. Marc says

    15/05/2020 at 21:43

    Hi Stephanos,

    Big fan. Thank you very much.

    Possible to include a timestamp when a user was added/removed in the email report?

    Thank you

    Reply
  6. Salvatore says

    26/10/2020 at 11:35

    Hi Stephanos,
    thanks for the script.
    In Italy, at the change of time, it happened that the e-mails arrived in which the total removal was reported and in reintegration of all the users contained in the groups under monitoring.
    I’m afraid the script doesn’t take the change into account now and detects changes that haven’t actually happened.
    Thanks in advance.
    Regards
    Salvatore

    Reply

Leave a Reply Cancel reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Footer

Recent Posts

  • ICS Cube Product Review 26/04/2019
  • PowerShell Module SysInfo v1.2.0 15/03/2019
  • PowerShell Module SysInfo v1.1.2 13/11/2018
  • PowerShell Module SysInfo 24/10/2018
  • Get-VoltageProbe 24/10/2018
  • Get-VideoController 24/10/2018
  • Get-USBController 24/10/2018
  • Get-TrackPoint 24/10/2018
  • Get-TrackBall 24/10/2018
  • Get-TouchScreen 24/10/2018
Planet PowerShell

Categories

  • Modules Cmdlets (57)
  • PowerShell Modules (5)
  • PowerShell Scripts (38)
  • PowerShell Tutorials (35)
  • Software Reviews (2)

Archives

  • April 2019 (1)
  • March 2019 (1)
  • November 2018 (1)
  • October 2018 (56)
  • September 2018 (13)
  • August 2018 (9)
  • July 2018 (6)
  • June 2018 (8)
  • May 2018 (7)
  • April 2018 (9)
  • March 2018 (4)
  • February 2018 (6)
  • January 2018 (12)
  • December 2017 (4)
Top 10 PowerShell 2018

Blogroll

  • Planet PowerShell
  • Reddit – PowerShell
  • PowerShell Magazine
  • PowerShell.org
  • PowerShell Team Blog
  • Hey, Scripting Guy! Blog
  • Mike F Robbins
  • PowerShell Explained with Kevin Marquette
  • Mike Kanakos – Network Admin
  • The Lonely Administrator
  • AskME4Tech
PowerShell Blogs Sysadmin Blogs Banners for Top 20 Programming Blogs

© 2021 · Stephanos Constantinou Blog

  • Home
  • Blogs
  • About
  • Contact