2

I'm optimizing some code we use to query Active Directory. One of the methods fetches all of the AD users who have changed since a particular update, determined by the uSNCreated property of the Directory Entry. Essentially it's doing the C# equivalent of:

select * from PrincipalSearcher where uSNCreated > somevalue

The code is (more or less):

public IEnumerable<UserPrincipal> GetUpdatedUsers(string samAccountName, long lastUsnChanged)
{
    using (var context = new PrincipalContext(ContextType.Domain))
    using (var userSearcher = new PrincipalSearcher(new UserPrincipal(context)))
    {
        var items = userSearcher.FindAll().Cast<UserPrincipal>();
        return items.Where(x => GetUsnChanged(x) > lastUsnChanged).ToArray();
    }
} 

private static long GetUsnChanged(Principal item)
{
    var de = item.GetUnderlyingObject() as DirectoryEntry;
    if (de == null)
        return 0;

    if (!de.Properties.Contains("uSNCreated"))
        return 0;

    var usn = de.Properties["uSNCreated"].Value;
    var t = usn.GetType();

    var highPart = (int)t.InvokeMember("HighPart", BindingFlags.GetProperty, null, usn, null);
    var lowPart = (int)t.InvokeMember("LowPart", BindingFlags.GetProperty, null, usn, null);

    return highPart * ((long)uint.MaxValue + 1) + lowPart;
}

Now this code DOES work, but the repeated calls to InvokeMember() are SLOW. What I'd like to do is get a reference to the HighPart and LowPart properties so that I can call them over and over without the overhead of needing to "rediscover" them every time when calling InvokeMember().

I'd though that I could do something along the lines of

static PropertyInfo highProp = highProp
    ?? t.GetProperty("HighPart", BindingFlags.GetProperty);
highPart = (int)highProp.GetValue(usn);

Unfortnately t.GetProperty() always returns null. Looking at the results returned by GetProperties(), GetMethods() and GetMembers(), there doesn't seem to be a visible "HighPart" or "LowPart" that I can get to, even when using BindingFlags.NonPublic - the __ComObject simply doesn't seem to expose them (even though I can call the using InvokeMember())

Is there a way to solve this, or is it time to admit defeat?

Pete
  • 1,807
  • 1
  • 16
  • 32
  • 1
    You can copy/paste the [interface declaration](https://referencesource.microsoft.com/#System.Web/Security/ADMembershipProvider.cs,96419c0a980e9d0e). Or add a reference to c:\windows\system32\activeds.tlb. Cast the return value of the Value property to this interface. The odds that this is actually faster are not so high, you are surely just seeing the cost of the network roundtrip to the domain controller. – Hans Passant Oct 15 '17 at 14:54
  • You're right in that this doesn't seem to do anything for performance, but it certainly makes the code more readable. That's worth a +1 in my book - thanks! – Pete Oct 16 '17 at 09:12

1 Answers1

2

Classes from the System.DirectoryServices.AccountManagement namespace are designed for use in simple cases, e. g. you need to find a user or group. These classes have known perfomance issues. I'd recommend using DirectorySearcher or LdapConnection/SearchRequest. In this case you can filter objects on the server, not on the client which will significantly increase performance and reduce data sent over network. Here is an example of using DirectorySearcher to find all users: Get all users from AD domain In your case the filter will look like (&(objectClass=user)(uSNCreated>=x+1)) where x is your last usn. Be aware that you if you track objects with usnCreated attribute you will be getting only users that were created since last usn. To track changes use usnChanged attribute

oldovets
  • 695
  • 4
  • 9
  • Good call on the uSNCreated vs uSNChanged. I've tried using a DirectorySeacher but keep on getting an ArgumentException thrown complaining that "The (&(&(objectClass=user)(uSNChanged > 132440887))) search filter is invalid.". I'll keep plugging on with this though - I much prefer the idea of server-side filtering of the results – Pete Oct 16 '17 at 09:19
  • Replace > with >= in filter and increase last usn by one – oldovets Oct 16 '17 at 14:44
  • I'm absolutely gobsmacked! DirectorySearcher can't handle a > operator ??? That's amazing, but you're right. This works like a charm - thanks! – Pete Oct 16 '17 at 16:00