Script repository
The script sends a report on changes of an AD group membership, no matter whether the changes were made using Adaxes or any third 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- the name of the binary attribute to preserve group member GUIDs in.$to- the address of the email notification recipient.$subject- the email notification subject.$reportHeader- the report header.$reportFooter- the report footer.$headerAddedMembers- the header for the section with added members.$headerRemovedMembers- the header for the section with removed members.
$savedMembersAttribute = "adm-CustomAttributeBinary1" # TODO: modify me
# Email 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 group members.
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 by 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 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 report
$reportHeader += $headerAddedMembers
$reportHeader += $addedMembersReport
}
if ($removedMemberGuids.Count -ne 0)
{
# Iterate through removed members.
foreach ($removedMemberGuid in $removedMemberGuids)
{
# Bind to 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 report
$reportHeader += $headerRemovedMembers
$reportHeader += $removedMembersReport
}
# Send mail
$report = $reportHeader + $reportFooter
$Context.SendMail($to, $subject, $NULL, $report)
# Save current member GUIDs.
SaveCurrentMembers $currentMemberGuidsBytes $savedMembersAttribute
Comments 6
You must be signed in to comment.
Jeremy Altman
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.
Support
Hello Jeremy,
There were some changes in Adaxes 2023 that might result in such a behaviour. Please, try the below updated script:
Charles Wilson
Could this script be modified to pull the AD email address field and company field also? Rather than list the information out in the email message body, can the information be placed in a pdf attachment and then emailed?
Thanks!
Support
Hello Charles,
Unfortunately, there is no easy way to do that and requires significantly updating the script.
Charles Wilson
I understand. Are you aware of another script in the repository to accomplish something similar?
Support
Hello Charles,
No, there is no such script.