The DirSync control is powerful and efficient, but has two
significant limitations:
Only for highly-privileged applications: To use the DirSync
control, an application must run under an account that has the
SE_SYNC_AGENT_NAME privilege on the domain controller. Few accounts
are so highly privileged, so an application that uses the DirSync
control cannot be run by ordinary users.
No subtree scoping: The DirSync control returns all changes
that occur within a naming context. An application interested only
in changes that occur in a small subtree of a naming context must
wade through many irrelevant changes, which is inefficient both for
the application and for the domain controller.
Changes from Active Directory can also be obtained by querying
the uSNChanged attribute, which avoids the limitations of
the DirSync control. This alternative is not better than the
DirSync control in all respects because it involves transmitting
all attributes when any attribute changes and it requires more work
from the application developer to handle certain failure scenarios
correctly. It is, currently, the best way to write certain
change-tracking applications.
When a domain controller modifies an object it sets that
object's uSNChanged attribute to a value that is larger than
the previous value of the uSNChanged attribute for that
object, and larger than the current value of the uSNChanged
attribute for all other objects held on that domain controller. As
a consequence, an application can find the most recently changed
object on a domain controller by finding the object with the
largest uSNChanged value. The second most recently changed
object on a domain controller will have the second largest
uSNChanged value, and so on.
The uSNChanged attribute is not replicated, therefore
reading an object's uSNChanged attribute at two different
domain controllers will typically give different values.
For example, the uSNChanged attribute can be used to
track changes in a subtree S. First, perform a "full sync" of the
subtree S. Suppose the largest uSNChanged value for any
object in S is U. Periodically query for all objects in subtree S
whose uSNChanged value is greater than U. The query will
return all objects that have changed since the full sync. Set U to
the largest uSNChanged among these changed objects, and you
are ready to poll again.
The subtleties of implementing a uSNChanged
synchronization application include:
Use the highestCommittedUSN attribute of rootDSE to
bound your uSNChanged filters. That is, before starting a
full sync, read the highestCommittedUSN of your affiliated
domain controller. Then, perform a full synchronization query
(using paged results) to initialize the database. When this is
complete, store the highestCommittedUSN value read before
the full sync query; to use as the lower bounds of the
uSNChanged attribute for the next synchronization. Later, to
perform an incremental synchronization, reread the
highestCommittedUSN rootDSE attribute. Then query for
relevant objects, using paged results, whose uSNChanged is
greater than the lower bounds of the uSNChanged attribute
value saved from the previous synchronization. Update the database
using this information. When complete, update the lower bounds of
the uSNChanged attribute from the highestCommittedUSN
value read before the incremental synchronization query. Always
store the lower bounds of the uSNChanged attribute value in
the same storage that the application is synchronizing with the
domain controller content.
Following this procedure, rather than that based on
uSNChanged values on retrieved objects, avoids making the
server reexamine updated objects that fall outside the set that is
applicable to the application.
Because uSNChanged is a non-replicated attribute, the
application must bind to the same domain controller every time it
runs. If it cannot bind to that domain controller it must either
wait until it can do so, or affiliate with some new domain
controller and perform a full synchronization with that domain
controller. When the application affiliates with a domain
controller it records the DNS name of that domain controller in
stable storage, which is the same storage it is keeping consistent
with the domain controller content. Then it uses the stored DNS
name to bind to the same domain controller for subsequent
synchronizations.
The application must detect when the domain controller it is
currently affiliated with has been restored from backup, because
this can cause inconsistency. When the application affiliates with
a domain controller it caches the "invocation id" of that domain
controller in stable storage, that is, the same storage it is
keeping consistent with the domain controller content. The
"invocation id" of a domain controller is a GUID stored in the
invocationId attribute of the domain controller's service
object. To get the distinguished name of a domain controller's
service object, read the dsServiceName attribute of the
rootDSE.
Be aware that when the application's stable storage is restored
from backup there are no consistency problems because the domain
controller name, invocation id, and lower bounds of the
uSNChanged attribute value are stored with the data
synchronized with the domain controller content.
Use paging when querying the server, both full and incremental
synchronizations, to avoid the possibility of retrieving large
result sets simulateously. For more information, see Specifying Other Search
Options.
Perform index-based queries to avoid forcing the server to
store large intermediate results when using paged results. For more
information, see Indexed
Attributes.
In general, do not use server-side sorting of search results,
which can force the server to store and sort large intermediate
results. This applies to both full and incremental
synchronizations. For more information, see Specifying Other Search
Options.
Handle no parent conditions gracefully. The application may
recognize an object before it has recognized its parent. Depending
upon the application, this may or may not be a problem. The
application can always read the current state of the parent from
the directory.
To handle moved or deleted objects, store the objectGUID
attribute of each tracked object. An object's objectGUID
attribute remains unchanged regardless of where it is moved
throughout the forest.
To handle moved objects, either perform periodic full
synchronizations or increase the search scope and filter out
uninteresting changes at the client end.
To handle deleted objects, either perform periodic full
synchronizations or perform a separate search for deleted objects
when you perform an incremental synchronization. When you query for
deleted objects, retrieve the objectGUID of the deleted
objects to determine the objects to delete from your database. For
more information, see Retrieving Deleted
Objects.
Be aware that the search results include only the objects and
attributes that the caller has permission to read (based on the
security descriptors and DACLs on the various objects). For more
information, see Effects of Security on
Queries.