Searching for the last logon of users in Active Directory
- by Robert May
I needed to clean out a bunch of old accounts at Veracity Solutions, and wanted to delete those that hadn’t used their account in more than a year. I found that AD has a property on objects called the lastLogonTimestamp. However, this value isn’t exposed to you in any useful fashion. Sure, you can pull up ADSI Edit and and eventually get to it there, but it’s painful. I spent some time searching, and discovered that there’s not much out there to help, so I thought a blog post showing exactly how to get at this information would be in order. Basically, what you end up doing is using System.DirectoryServices to search for accounts and then filtering those for users, doing some conversion and such to make it happen. Basically, the end result of this is that you get a list of users with their logon information and you can then do with that what you will. I turned my list into an observable collection and bound it into a XAML form. One important note, you need to add a reference to ActiveDs Type Library in the COM section of the world in references to get to LargeInteger. Here’s the class: namespace Veracity.Utilities
{
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using ActiveDs;
using log4net;
/// <summary>
/// Finds users inside of the active directory system.
/// </summary>
public class UserFinder
{
/// <summary>
/// Creates the default logger
/// </summary>
private static readonly ILog log = LogManager.GetLogger(typeof(UserFinder));
/// <summary>
/// Finds last logon information
/// </summary>
/// <param name="domain">The domain to search.</param>
/// <param name="userName">The username for the query.</param>
/// <param name="password">The password for the query.</param>
/// <returns>A list of users with their last logon information.</returns>
public IList<UserLoginInformation> GetLastLogonInformation(string domain, string userName, string password)
{
IList<UserLoginInformation> result = new List<UserLoginInformation>();
DirectoryEntry entry = new DirectoryEntry(domain, userName, password, AuthenticationTypes.Secure);
DirectorySearcher directorySearcher = new DirectorySearcher(entry);
directorySearcher.PropertyNamesOnly = true;
directorySearcher.PropertiesToLoad.Add("name");
directorySearcher.PropertiesToLoad.Add("lastLogonTimeStamp");
SearchResultCollection searchResults;
try
{
searchResults = directorySearcher.FindAll();
}
catch (System.Exception ex)
{
log.Error("Failed to do a find all.", ex);
throw;
}
try
{
foreach (SearchResult searchResult in searchResults)
{
DirectoryEntry resultEntry = searchResult.GetDirectoryEntry();
if (resultEntry.SchemaClassName == "user")
{
UserLoginInformation logon = new UserLoginInformation();
logon.Name = resultEntry.Name;
PropertyValueCollection timeStampObject = resultEntry.Properties["lastLogonTimeStamp"];
if (timeStampObject.Count > 0)
{
IADsLargeInteger logonTimeStamp = (IADsLargeInteger)timeStampObject[0];
long lastLogon =
(long)((uint)logonTimeStamp.LowPart + (((long)logonTimeStamp.HighPart) << 32));
logon.LastLogonTime = DateTime.FromFileTime(lastLogon);
}
result.Add(logon);
}
}
}
catch (System.Exception ex)
{
log.Error("Failed to iterate search results.", ex);
throw;
}
return result;
}
}
}
Some important things to note:
Username and Password can be set to null and if your computer us part of the domain, this may still work.
Domain should be set to something like LDAP://servername/CN=Users,CN=Domain,CN=com
You’re actually getting a com object back, so that’s why the LongInteger conversions are happening. The class for UserLoginInformation looks like this:
namespace Veracity.Utilities
{
using System;
/// <summary>
/// Represents user login information.
/// </summary>
public class UserLoginInformation
{
/// <summary>
/// Gets or sets Name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets LastLogonTime
/// </summary>
public DateTime LastLogonTime { get; set; }
/// <summary>
/// Gets the age of the account.
/// </summary>
public TimeSpan AccountAge
{
get
{
TimeSpan result = TimeSpan.Zero;
if (this.LastLogonTime != DateTime.MinValue)
{
result = DateTime.Now.Subtract(this.LastLogonTime);
}
return result;
}
}
}
}
I hope this is useful and instructive.
Technorati Tags: Active Directory