Unable to get Changes Using uSNChanged

737 views Asked by At

I want to get incremental changes from Active Directory using C#. And for that I am trying to build a solution as mentioned in the following article.

https://learn.microsoft.com/en-us/windows/win32/ad/polling-for-changes-using-usnchanged

However, I am facing following problems:

  1. There is no uSNChanged property available for user (although it is available for OU i.e. OrganizationalUnit). I can see only following properties for user.

enter image description here

  1. When I am moving users from OU1 to OU2, then highestcommittedusn get incremented but when I am querying for changes (search.Filter = "(uSNChanged>=13000)") using following code, I DO NOT get any updates.

  2. same case is for adding a user inside an OU.

Sample Code is mentioned below:

public static void GetUpdates()
{
    var myLdapConnection = createDirectoryEntry();
    var search = new DirectorySearcher(myLdapConnection);
    search.Filter = "(uSNChanged>=13000)";
    search.SearchScope = System.DirectoryServices.SearchScope.Subtree;
    var results = search.FindAll();
    Console.WriteLine(results.Count);
}

public static DirectoryEntry createDirectoryEntry()
{
    DirectoryEntry ldapConnection = new DirectoryEntry("LDAP://adfs.fed.abcd.com/DC=adfs,DC=fed,DC=abcd,DC=com");
    ldapConnection.Path = "adfs.fed.abcd.com";
    ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
            return ldapConnection;
}

 private static long GetHighestUsn()
 {
     using (LdapConnection connection = new LdapConnection(ldapPath))
     {
          var filter = "(&(objectClass=*))";
          var searchRequest = new SearchRequest(null, filter, System.DirectoryServices.Protocols.SearchScope.Base, "highestCommittedUSN");
          var response = connection.SendRequest(searchRequest) as SearchResponse;
          var usn = response.Entries[0].Attributes["highestcommittedusn"][0];
          return Convert.ToInt64(usn);
      }
      return 0;
}

Any help is highly appreciated.

Edit:

  1. I have only ONE domain controller.
2

There are 2 answers

2
Kushal Arora On BEST ANSWER

Answer: The code was correct.

I needed to provide "Read all user information" permission to the user (which was making the request) and after that I starting getting usnchanged property with every object including user.

Steps to enable permission is given in below article. https://social.technet.microsoft.com/Forums/en-US/b34f7295-4989-4440-93af-cebd6d66c711/cannot-read-the-usnchanged-attribute-for-some-users-why?forum=winserverDS

5
Hazrelle On

For GetHighestUsn I think the null from new SearchRequest(null ... should be replaced by an empty string to get RootDSE.

Secondly you mix System.DirectoryServices and System.DirectoryServices.Protocols: you should better stick to one. It is not clear what is the value for ldapPath in new LdapConnection(ldapPath)). If it is "adfs.fed.abcd.com" and you have more than one domain controller, you cannot know exactly which one will answer, this is related to last remark.

Last, USNChanged attribute is a non-replicated attribute, meaning that you should always request the same domain controller to get updates. Another domain controller will store a complete different value for the same object.

Normally every object should return USNChanged attribute unless you have specified a finite list of attributes to return in which case you must include it as well.

EDIT: Include sample code (Powershell)

  • System.DirectoryServices
    $entry = [System.DirectoryServices.DirectoryEntry]::new("LDAP://localhost:50005/O=MyAppInstance")
    $entry.psbase.AuthenticationType = [System.DirectoryServices.AuthenticationTypes]::Secure
    $searcher = [System.DirectoryServices.DirectorySearcher]::new($entry)
    $searcher.Filter = "(uSNChanged>=13004)"
    $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
    $results = $searcher.FindAll()
    $results.Count
  • System.DirectoryServices.Protocols
    $con = [System.DirectoryServices.Protocols.LdapConnection]::new("localhost:50005")
    $con.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
    $con.SessionOptions.ProtocolVersion = 3
    $con.Bind()

    $req = [System.DirectoryServices.Protocols.SearchRequest]::new()
    $req.DistinguishedName = "O=MyAppInstance"
    $req.Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
    $req.Filter = "(uSNChanged>=13004)"

    $res = [System.DirectoryServices.Protocols.SearchResponse]$con.SendRequest($req)
    $res.Entries.Count