Search code examples
c#active-directoryinvokecomobject

Get Invokable Method from __ComObject


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?


Solution

  • 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