Yes, the FilterBuilder class is a helper class in Adaxes that helps to build LDAP filters requiring a special format. A good example of such filters are filters that search by a certain binary property, for example, GUID.
If you are going to build LDAP filters in your scripts, we think the following article by Microsoft will help you to better understand the LDAP filter format: Active Directory: LDAP Syntax Filters.
If you mean the foreach loop that starts with foreach ($requestID in $requests), we actually wanted to make sure that we handle all the approval requests that match the search criteria. Now, after a second thought, we understand that if there are multiple Approval Requests generated by the Scheduled Task for the same user, it is probably some error / undesired behavior. We've changed the script a bit so that it handles situations when there is 1 Approval Request and when there are several Approval Requests differently. We've also left a TODO for you there so that you can decide for yourself, what the script should do if there is more than one Approval Request.
The first method that you tried is missing a line. It should be something like:
$approvalGuidInByte = $request.Get("objectGUID")
$approvalGuid = New-Object "System.Guid" (,$approvalGuidInByte)
$approvalGuidInByteString = $approvalGuid.ToString("D")
However, it involves binding to the AD object that represents the Approval Request. As we've already mentioned, it can sometimes be an expensive operation and requires extra calls.
The second method that you tried is a good alternative, however, you should always remember that when using the method you should fetch the required properties in the search. That is, you need to set the properties as the properties that should be loaded using the SetPropertiesToLoad method of the searcher object. Otherwise, any attempt to get the properties will fail as the properties are not fetched and thus not present in the property cache.
We've modified the script to use the second method that you tried (as it is much faster) and have set the objectGUID property as a property that should be fetched when searching for Approval Requests.
Also, we've found one more place where we can optimize the script a bit. The thing is that in the previous version of the script the necessary Scheduled Task was specified by the Task name. With this approach, the script execution always started with searching for the DN of the Scheduled Task to be able to bind to it and get the Task GUID. However, if the DN of the Scheduled Task is specified directly in the script, we can skip searching for the Task DN and thus save one call to the AD. This will help to make the script faster a bit.
So, to summarize, the following changes have been made to the task:
- Introduced different behavior when there is more than one Approval Request for a certain Subordinate and left a TODO for you so that you can decide what the script will do when there is more than one Approval Request.
- The Approval Request GUID is now returned by the search.
- The Approval Request GUID returned by the search can now be converted to a string so that you can use it in a URL.
- The Scheduled Task is now specified not by the Task name, but by the Task Relative Distinguished Name (RDN) in order to construct the task DN and avoid searching for the Task each time the script runs.
Here's the text of the script:
$scheduledTaskRdn = "CN=Account Review - Users" # TODO: modify me
$subordinateDNs = $Context.TargetObject.GetEx("directReports") | sort-object
$subordinateDNs = $NULL
if ($subordinateDNs -ne $NULL)
# Get Scheduled Task 'Account Review - Users' GUID
$scheduledTasksContainerPath = $Context.GetWellKnownContainerPath("ScheduledTasks")
$scheduledTasksContainerPathObj = New-Object "Softerra.Adaxes.Adsi.AdsPath" $scheduledTasksContainerPath
$scheduledTaskPath = $scheduledTasksContainerPathObj.CreateChildPath($scheduledTaskRdn)
$scheduledTask = $Context.BindToObject($scheduledTaskPath)
$scheduledTaskGuidInByte = $scheduledTask.Get("objectGUID")
$scheduledTaskGuid = [Softerra.Adaxes.Ldap.FilterBuilder]::Create("adm-ApprovalRequestorGuid", $scheduledTaskGuidInByte)
foreach ($subordinateDN in $subordinateDNs)
if ($subordinateDN -like "*CN=Users,DC=root,DC=net") # This is a fudge fix to stop references to the AD root domain causing errors
$subordinateDN = "'root' DN trapped and excluded"
$subordinate = $Context.BindToObjectByDN($subordinateDN)
$reviewDate = $subordinate.Get("adm-CustomAttributeDate1").ToString("dd/MM/yyy")
$reviewStatus = $subordinate.Get("adm-CustomAttributeText4")
if ($reviewStatus.startsWith("Review In Progress"))
$searchFilter = "(&(objectCategory=adm-ApprovalRequest)(adm-ApprovalState=0)$scheduledTaskGuid"
$objectGuidInByte = $subordinate.Get("objectGUID")
$objectGuid = [Softerra.Adaxes.Ldap.FilterBuilder]::Create("adm-TargetObjectGuid", $objectGuidInByte)
$searchFilter += "$objectGuid)"
$containerPath = $Context.GetWellKnownContainerPath("ApprovalRequests")
$searcher = New-Object "Softerra.Adaxes.Adsi.Search.DirectorySearcher" $NULL, $False
$searcher.SearchParameters.BaseObjectPath = $containerPath
$searcher.SearchParameters.PageSize = 500
$searcher.SearchParameters.SearchScope = "ADS_SCOPE_SUBTREE"
$searcher.SearchParameters.Filter = $searchFilter
$searcher.SearchParameters.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
$searcherResult = $searcher.ExecuteSearch()
$requests = $searcherResult.FetchAll()
if ($requests.Count -eq 1)
$requestGuidInByte = $requests.Properties["objectGUID"].Value
$requestGuid = New-Object "System.Guid" (, $requestGuidInByte)
# TOOD: Some code to handle situations when there is more than one Approval Request for the current subordinate
In the script, $scheduledTaskRdn specifies the Scheduled Task Distinguished Name relative to the Scheduled Tasks container. For example, if your Scheduled Task is called Account Review - Users, and it is located directly in the Scheduled Tasks container, you should specify CN=Account Review - Users.