We use cookies to improve your experience.
By your continued use of this site you accept such use.
For more details please see our privacy policy and cookies policy.

Script Repository

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

November 22, 2023 Views: 3942

The script sends a report on changes of membership in a group, no matter whether the changes were made using Adaxes or any 3rd party tools, such as ADUC or Exchange.

To identify the members added or removed from a group, on each run the script preserves GUIDs of the current members in a binary attribute (e.g. adm-CustomAttributeBinary1) of the group. On each subsequent run, the saved GUIDs are used to compare the list of current members of the group with the members preserved in the binary attribute.

To execute the script, create a scheduled task configured for the Group object type.

Parameters:

  • $savedMembersAttribute - Specifies the LDAP name of the binary attribute used to preserve 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 = $Context.GetDisplayNameFromAdsPath($path, $True)
        $memberName = [System.Web.HttpUtility]::HtmlEncode($memberName)
        
        # Add to the report
        $addedMembersReport += "<li><a href='$webInterfaceAddress`#/Browse/$newMemberGuid'>$memberName</a></li>"
    }
    $addedMembersReport += "</ul>"
    
    # 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 = $Context.GetDisplayNameFromAdsPath($path, $True)
        $memberName = [System.Web.HttpUtility]::HtmlEncode($memberName)
        
        # Add to report
        $removedMembersReport += "<li><a href='$webInterfaceAddress`#/Browse/$removedMemberGuid'>$memberName</a></li>"
    }
    $removedMembersReport += "</ul>"
    
    # 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 2
avatar
Jeremy Altman Feb 15, 2023
This script used to work great, but after upgrading to Adaxes 2023 (3.15.20916.0), the report is generated and emailed but the "Members added to/removed from the group" section is blank. It only shows a bullet point, but the members are no longer listed.
avatar
Support Feb 15, 2023
Hello Jeremy,

There were some changes in Adaxes 2023 that might result in such a behaviour. Please, try the below updated script:

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 = $Context.GetDisplayNameFromAdsPath($path, $True)
        $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 = $Context.GetDisplayNameFromAdsPath($path, $True)
        $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
Leave a comment
Loading...

Got questions?

Support Questions & Answers