| 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
break
}
}
}
} 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. |