Script Repository


Changes in group membership (including changes made by 3rd party tools)

August 11, 2021
2499

With the help of the script, you can get a report on changes in membership of an AD group, no matter whether the changes were made using Adaxes or any 3rd party tools, such as ADUC or Exchange.

To be able to identify which members were added to or removed from a group, the script saves GUIDs of the current members in a certain binary attribute of the group. The saved GUIDs are used to compare the list of current members of the group with members on the previous run.

We suggest using an Adaxes custom attribute for storing member GUIDs, for example,
adm-CustomAttributeBinary1. Such attributes are not stored in AD, but can be used the same as any other attribute of AD objects.

To use the script with Adaxes, create a scheduled task for Group objects that runs the script using the Run a program or PowerShell script action.

Parameters:

  • $savedMembersAttribute - Specifies the LDAP display name of the attribute that will be used to store group member GUIDs.
  • $to - Specifies the recipient of the email notification.
  • $subject - Specifies the email message subject.
  • $reportHeader - Specifies the report header.
  • $reportFooter - Specifies the report footer.
  • $headerAddedMembers - Specifies a header for the section with added members.
  • $headerRemovedMembers - Specifies a header for the section with removed members.
Edit Remove
PowerShell
$savedMembersAttribute = "adm-CustomAttributeBinary1" # TODO: modify me

# E-mail message settings
$to = "recipient@domain.com" # TODO: modify me
$subject = "Changes in group membership for group '%name%'" # TODO: modify me
$reportHeader = "<h2><b>Changes in group membership for group '%name%'</b></h2><br/>" # TODO: modify me
$reportFooter = "<hr /><p><i>Please do not reply to this e-mail, it has been sent to you for notification purposes only.</i></p>" # TODO: modify me
$headerAddedMembers = "<b>Members added to the group</b><br />" # TODO: modify me
$headerRemovedMembers = "<b>Members removed from the group</b><br />" # TODO: modify me

function SaveCurrentMembers($guidsBytes, $savedMembersAttribute)
{
    if ($guidsBytes.Count -eq 0)
    {
        # All members were removed from the group
        $Context.TargetObject.Put($savedMembersAttribute, [Guid]::Empty.ToByteArray())
    }
    else
    {
        $totalBytes = $guidsBytes.Count * 16
        $result = New-Object 'System.Collections.Generic.List[System.Byte]' $totalBytes
        
        foreach ($guidBytes in $guidsBytes)
        {
            $result.AddRange($guidBytes)
        }
        
        $Context.TargetObject.Put($savedMembersAttribute, $result.ToArray())
    }
    
    # Save changes
    $Context.TargetObject.SetInfo()
}

# Get GUIDs of direct members of the group
try
{
    $currentMemberGuidsBytes = $Context.TargetObject.GetEx("adm-DirectMembersGuid")
}
catch
{
    $currentMemberGuidsBytes = @()
}

$addedMemberGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
foreach ($guidBytes in $currentMemberGuidsBytes)
{
    $guid = [Guid]$guidBytes
    $addedMemberGuids.Add($guid)
}

# Get saved member GUIDs
try
{
    $savedMemberGuidsBytes = $Context.TargetObject.Get($savedMembersAttribute)
}
catch
{
    if ($addedMemberGuids.Count -eq 0)
    {
        return # No current or saved members
    }
    
    # Save current members GUIDs and exit
    SaveCurrentMembers $currentMemberGuidsBytes $savedMembersAttribute
    return
}

if (($savedMemberGuidsBytes.Length -eq 16) -and ([Guid]$savedMemberGuidsBytes -eq [Guid]::Empty))
{
    $savedMemberGuidsBytes = @() # All users were removed from the group previous time
}

$savedMemberGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
if ($savedMemberGuidsBytes.Length -ne 0)
{
    # Calculate the number of GUIDs
    $totalBytes = $savedMemberGuidsBytes.Length
    
    # Make sure that the total number of  bytes is a divisible of 16
    $remainder = 0
    [System.Math]::DivRem($totalBytes, 16, [ref]$remainder)
    if ($remainder -ne 0)
    {
        $Context.Cancel("Unexpected data length! Exiting.")
        return
    }
    
    for ($i = 0; $i -lt ($totalBytes / 16); $i++)
    {
        $bytes = [System.Guid]::Empty.ToByteArray()
        [System.Array]::Copy($savedMemberGuidsBytes, $i * 16, $bytes, 0, 16)
        $guid = [Guid]$bytes
        [void]$savedMemberGuids.Add($guid)
    }
}

# Find members that were removed from the group
$removedMemberGuids = New-Object "System.Collections.Generic.HashSet[System.Guid]"
foreach ($guid in $savedMemberGuids)
{
    if ($addedMemberGuids.Remove($guid))
    {
        continue
    }
    
    $removedMemberGuids.Add($guid)
}

if (($removedMemberGuids.Count -eq 0) -and ($addedMemberGuids.Count -eq 0))
{
    return # No changes
}

# Get the default Web Interface address
$webInterfaceAddress = "%adm-WebInterfaceUrl%"
if ([System.String]::IsNullOrEmpty($webInterfaceAddress))
{
    $Context.LogMessage("Default web interface address not set for Adaxes service. For details, see http://www.adaxes.com/help/?HowDoI.ManageService.RegisterWebInterface.html", "Warning")
}

if ($addedMemberGuids.Count -ne 0)
{
    # Add new members to the report
    foreach ($newMemberGuid in $addedMemberGuids)
    {
        # Bind to the member
        $path = "Adaxes://<GUID=$newMemberGuid>"
        
        # Get member name
        $memberName = [Softerra.Adaxes.Utils.ObjectNameHelper]::GetObjectName($path, 'IncludeParentPath')
        $memberName = [System.Web.HttpUtility]::HtmlEncode($memberName)
        
        # Add to the report
        $addedMembersReport += "<li><a href='$webInterfaceAddress`ViewObject.aspx?guid=$newMemberGuid'>$memberName</a></li>"
    }
    $addedMembersReport += "</ol><br/>"
    
    # Add to the report
    $reportHeader += $headerAddedMembers
    $reportHeader += $addedMembersReport
}

if ($removedMemberGuids.Count -ne 0)
{
    # Iterate through removed members
    foreach ($removedMemberGuid in $removedMemberGuids)
    {
        # Bind to the member
        $path = "Adaxes://<GUID=$removedMemberGuid>"
        
        # Get member name
        $memberName = [Softerra.Adaxes.Utils.ObjectNameHelper]::GetObjectName($path, 'IncludeParentPath')
        $memberName = [System.Web.HttpUtility]::HtmlEncode($memberName)
        
        # Add to report
        $removedMembersReport += "<li><a href='$webInterfaceAddress`ViewObject.aspx?guid=$removedMemberGuid'>$memberName</a></li>"
    }
    $removedMembersReport += "</ol>"
    
    # Add to the report
    $reportHeader += $headerRemovedMembers
    $reportHeader += $removedMembersReport
}

# Send mail
$report = $reportHeader + $reportFooter
$Context.SendMail($to, $subject, $NULL, $report)
    
# Save current member GUIDs to custom attribute
SaveCurrentMembers $currentMemberGuidsBytes $savedMembersAttribute

Comments ( 0 )
No results found.
Leave a comment

Related Scripts