SecThor
SecThor

Reputation: 69

Query user data from a specific AD DC

I'd like to query some user attribute in our AD, but on a specific DC, in C#. We've a few dozens of DCs and I suspect that there is some replication issue between them. I'd like to make a cleanup of unused accounts, to which I'd like to use the last logon time attribute and I'd like to query this on all DCs one bye one (I know this is a bit like brute forcing, however I don't intended to do such a thing too often) so I can see if the most recent value is up-to date or not. I had the code to query all the DCs:

            Domain TestDomain = Domain.GetCurrentDomain();
            Console.WriteLine("Number of found DCs in the domain {0}", TestDomain.DomainControllers.Count);
            foreach (DomainController dc in TestDomain.DomainControllers)
            {
                Console.WriteLine("Name: " + dc.Name);
                ///DO STUFF
            }

And I also found help to construct the code that can query a user from AD:

    PrincipalContext context = new PrincipalContext(ContextType.Domain, "test.domain.com");
            string userName = "testusername";
            UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
            Console.WriteLine(user.LastLogon.Value.ToString());                
            Console.ReadKey();

And here I stuck. Now I'd like to get the user's last logon timestamp from all DCs. In the past I already deleted accidentaly account that seemed to be unused for a long time (check on only one DC), than it turned out the user use it every day so the info came from the DC was not synced. I'm aware that the most reasonable action would be to review what cause this incorrect sync phenomena, however in my current status that would take ages and probably ended without any finding... Thanks in advance for any constructive response/comment!

Upvotes: 0

Views: 2685

Answers (2)

fuglede
fuglede

Reputation: 18201

While the accepted answer was a good kick in the right direction, I found it to ultimately not produce the expected result: As hinted in the answer, "lastLogon" is not replicated between controllers, whereas the attribute "lastLogonTimeStamp" is replicated (but it is, on the other hand, not guaranteed to be more than 14 days wrong, cf. this answer).

Now, rather confusingly, UserPrincipal.LastLogon refers not to the unreplicated but precise "lastLogon" but to the replicated but imprecise "lastLogonTimeStamp", and I found that by running the code in the accepted answer, all produced DateTimes were equal and wrong.

Instead, inspired by this answer, I found that in order to find the most recent logon date for a user with a given sAMAccountName (which you can easily extend to search for all users), I would have to do something like the following:

public DateTime? FindLatestLogonDate(string username)
{
    var logons = new List<DateTime>();
    DomainControllerCollection domains = Domain.GetCurrentDomain().DomainControllers;
    foreach (DomainController controller in domains)
    {
        using (var directoryEntry = new DirectoryEntry($"LDAP://{controller.Name}"))
        {
            using (var searcher = new DirectorySearcher(directoryEntry))
            {
                searcher.PageSize = 1000;
                searcher.Filter = $"((sAMAccountName={username}))";
                searcher.PropertiesToLoad.AddRange(new[] { "lastLogon" });
                foreach (SearchResult searchResult in searcher.FindAll())
                {
                    if (!searchResult.Properties.Contains("lastLogon")) continue;
                    var lastLogOn = DateTime.FromFileTime((long)searchResult.Properties["lastLogon"][0]);
                    logons.Add(lastLogOn);
                }
            }
        }
    }
    return logons.Any() ? logons.Max() : (DateTime?)null;
}

Upvotes: 2

Ashigore
Ashigore

Reputation: 4678

EDIT: After re-reading your question I realised what the problem actually is. You believe you have a replication issue because a user's Last Logon attribute doesnt match on all domain controllers? This is by design! That attribute is domain controller specific and IS NOT REPLICATED. To check the true last logon time of a user you must always query every domain controller to find the latest time!

You are almost there, try this:

public static List<UserPrincipal> GetInactiveUsers(TimeSpan inactivityTime)
{
    List<UserPrincipal> users = new List<UserPrincipal>();

    using (Domain domain = Domain.GetCurrentDomain())
    {
        foreach (DomainController domainController in domain.DomainControllers)
        {
            using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainController.Name))
            using (UserPrincipal userPrincipal = new UserPrincipal(context))
            using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
            using (PrincipalSearchResult<Principal> results = searcher.FindAll())
            {
                users.AddRange(results.OfType<UserPrincipal>().Where(u => u.LastLogon.HasValue));
            }
        }
    }

    return users.Where(u1 => !users.Any(u2 => u2.UserPrincipalName == u1.UserPrincipalName && u2.LastLogon > u1.LastLogon))
        .Where(u => (DateTime.Now - u.LastLogon) >= inactivityTime).ToList();
}

It won't show people who've never logged in though. If you need that you can probably figure it out.

Upvotes: 0

Related Questions