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.
$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
There were some changes in Adaxes 2023 that might result in such a behaviour. Please, try the below updated script: