Introduction
This article describes how to get the real last-logon date-time from an user from Active Directory and how to use custom Active Directory attributes.
Background
The .NET System.DirectoryServices.AccountManagement
classes (from Framework 3.5) provide some neat functionality to access active directory users in a rather simple way. Retrieving a user is as simple as this:
using (var adContext = new PrincipalContext(ContextType.Domain, ADDomainName, ADContainer)
{
username = "ADusername";
var foundUser = UserPrincipal.FindByIdentity(adContext, username);
if (foundUser != null)
{
}
}
Now you would expect (as any other normal human being) that getting the lastlogon datetime from the
Active Directory user is as simple as this from the UserPrincipal
object we just created:
var lastlogonDateTime = founduser.LastLogon;
But unfortunately it isn't. After some research I found the following: 'Because the
lastLogon
attribute is not replicated throughout the domain, if our new user has never logged on to domain controller B then domain controller B will have no knowledge of the user’s last logon time.'
Anyway, anyhow, it doesn't come up with the right last-logon time. It simply doesn't work, how do we solve this? This gave me a severe headache.
It is there, but it is hidden. You must retrieve the right time by using extension attributes. By extending the
Userprincipal
class, you can access these attributes. Not only the real lastlogon time, but also different properties like fax number and so on.
Using the code
Below is my extended
Userprincipal
class which you can use instead of UserPrincipal
:
using System;
using System.DirectoryServices.AccountManagement;
using System.Reflection;
namespace MyNameSpace
{
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context) { }
public UserPrincipalExtended(PrincipalContext
context,
string samAccountName,
string password,
bool enabled)
: base(context, samAccountName, password, enabled) { }
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context,
string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context,
typeof(UserPrincipalExtended),
identityValue);
}
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context,
IdentityType identityType,
string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context,
typeof(UserPrincipalExtended),
identityType,
identityValue);
}
#region custom attributes
[DirectoryProperty("RealLastLogon")]
public DateTime? RealLastLogon
{
get
{
if (ExtensionGet("LastLogon").Length > 0)
{
var lastLogonDate = ExtensionGet("LastLogon")[0];
var lastLogonDateType = lastLogonDate.GetType();
var highPart = (Int32)lastLogonDateType.InvokeMember("HighPart",
BindingFlags.GetProperty, null, lastLogonDate, null);
var lowPart = (Int32)lastLogonDateType.InvokeMember("LowPart",
BindingFlags.GetProperty | BindingFlags.Public, null, lastLogonDate, null);
var longDate = ((Int64)highPart << 32 | (UInt32)lowPart);
return longDate > 0 ? (DateTime?) DateTime.FromFileTime(longDate) : null;
}
return null;
}
}
#endregion
}
}
This will give you the proper LastLogon datetime. And you can add properties to retrieve other (custom) attributes for future purposes:
[DirectoryProperty("HomePage")]
public string HomePage
{
get
{
if (ExtensionGet("HomePage").Length != 1)
return null;
return (string)ExtensionGet("HomePage")[0];
}
set { this.ExtensionSet("HomePage", value); }
}
This is very convenient as the original Userprincipal
class only exposes about 10% of the active directory attributes.
Mind you use the attributes above the class UserPrincipalExtended
. Copy the class literally in your project, change only the namespace and everything will work fine. Good luck!