Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's English Pages


Engineering and troubleshooting by Directory Master!
MCM: Directory

Quick Launch

Ondrej Sevecek's English Pages > Posts > Example of using DirSync LDAP query modificator in PowerShell
February 24
Example of using DirSync LDAP query modificator in PowerShell

Just a quick PowerShell sample of how to use the Microsoft AD (Active Directory) DirSync LDAP control (OID 1.2.840.113556.1.4.841) which modifies LDAP query search behavior to return only objects and their attributes modified since the last query.

When the following code runs the first time, it downloads everything from AD. It must rerun the queries until there are no more data because of the default paged search setting (usually 1000 entries per an LDAP request). If you cycle it again, it downloads only the objects and only their attributes that were changed its last run. The cookie is used to remember the last run state.

$ssl = $false
$dc = 'dc1.gopas.virtual'
$rootDN = 'DC=gopas,DC=virtual' 
$filter = '(&(objectClass=*))'

# Note: if you want to download all non-empty (non-password and non-confidential) attributes
[string[]] $attributesToGet = $null

# Note: if you want to download only some attributes
#       also if you want to download confidential attribute values, you must name them explicitly
#       in the attribute list - for example, if you specify $null, the msfve-recoverypassword will show up
#       if modified, but would contain no data - on the contrary if you specify the parameter msfve-recoverypassword
#       to be downloaded explicitly, it will show up and also will contain its binary data
#[string[]] $attributesToGet = @('samaccountname', 'userPrincipalname', 'displayname', 'msfve-recoverypassword')

[void] ([System.Reflection.Assembly]::LoadWithPartialName('System.DirectoryServices.Protocols'))

if (($ssl) -and ($dc -notlike '*:636')) { $dc = '{0}:636' -f $dc }

[DirectoryServices.Protocols.LdapConnection] $ldapConn = New-Object DirectoryServices.Protocols.LdapConnection($dc)

if (-not $ssl) {

  $ldapConn.SessionOptions.Sealing = $true

} else {

  $ldapConn.SessionOptions.SecureSocketLayer = $true

$ldapConn.AuthType = [DirectoryServices.Protocols.AuthType]::Kerberos

[DirectoryServices.Protocols.SearchRequest] $ldapRequest = New-Object DirectoryServices.Protocols.SearchRequest($rootDN, $filter, 'SubTree', $attributesToGet)

[byte[]] $dirSyncCookie = $null

#[DirectoryServices.Protocols.DirSyncRequestControl] $dirSyncCtr = New-Object DirectoryServices.Protocols.DirSyncRequestControl($dirSyncCookie, [DirectoryServices.Protocols.DirectorySynchronizationOptions]::IncrementalValues, [Int32]::MaxValue)
[DirectoryServices.Protocols.DirSyncRequestControl] $dirSyncCtr = New-Object DirectoryServices.Protocols.DirSyncRequestControl($dirSyncCookie, [DirectoryServices.Protocols.DirectorySynchronizationOptions]::None, [Int32]::MaxValue)

[void] $ldapRequest.Controls.Add($dirSyncCtr)

[bool] $moreProcessingRequired = $false
[bool] $firstRun = $true

do { 

  do {

    [DirectoryServices.Protocols.SearchResponse] $ldapResponse = $null
    $ldapResponse = $ldapConn.SendRequest($ldapRequest)

    Write-Host ('One request: {0} | #{1}' -f $ldapResponse.ResultCode, $ldapResponse.Entries.Count)

    if (-not $firstRun) {

      foreach ($oneEntry in $ldapResponse.Entries) {

        Write-Host ('One result: {0} | attributes = #{1}' -f $oneEntry.DistinguishedName, $oneEntry.Attributes.Count)

    $moreProcessingRequired = $false

    if (-not ([object]::Equals($ldapResponse, $null))) {

      foreach ($oneLdapResponseControl in $ldapResponse.Controls) {

        if ($oneLdapResponseControl -is [DirectoryServices.Protocols.DirSyncResponseControl]) {

          [DirectoryServices.Protocols.DirSyncResponseControl] $dirSyncCtrResponse = [DirectoryServices.Protocols.DirSyncResponseControl] $oneLdapResponseControl
          $dirSyncCtr.Cookie = $dirSyncCtrResponse.Cookie
          $moreProcessingRequired = $dirSyncCtrResponse.MoreData


  } while ($moreProcessingRequired)

  $firstRun = $false
  [string] $restart = Read-Host 'Do you want to restart the search? (y/yes)'

} while (($restart -eq 'yes') -or ($restart -eq 'y'))

Note that the user account under which the script runs must be assigned LDAP permission of Replicate Directory Changes on the domain (or any particular naming context in question). Even if you granted the user account the Replicate Directory Changes All, you would not get values for system protected attributes such as unicodePwd, ntPwdHistory or dbcsPwd which contain password hashes or password hash history. Although you get the attribute names in the result collection if they were actually changed, they are always empty because the LDAP interface never ever reveals their values.

Even the data of less strictly protected confidential attributes such as the BitLocker recovery passwords (msFVE-RecoveryPassword) or the BitLocker key packages (msFVE-KeyPackage) are not downloaded by default if you do not specify their attribute names explicitly. Only the empty attribute names indicate they were changed. If you want to download the actual confidential attribute values, you must name them explicitly among the downloaded attributes.



 on 28/09/2019 12:06

Add Comment

Sorry comments are disable due to the constant load of spam *

This simple antispam field seems to work well. Just put here the number.


You do not need to provide any value this column. It will automatically fill with the name of the article itself.

Author *

Body *