Search This Blog

Sunday, November 4, 2012

MS Active Directory "accountExpires" value -- getting and setting it

If you use ADSI to get a DirectoryEntry object from Active Directory you'll find that the "accountExpires" property is stored as a DateTime variable in a large integer format called the IADsLargeInteger format (http://msdn.microsoft.com/en-us/library/windows/desktop/aa706037%28v=vs.85%29.aspx).  The format represents a date as an 8 byte variable indicating the number of 100-nanosecond intervals since January 1, 1601 (look up the Wikipedia article for this date if you're curious as to why it's used).

Converting this format into a standard C# DateTime value requires a 2-step process of converting the IADsLargeInteger into a standard C# long value, then converting this long value into a C# date time.

I found several (incorrect) versions of functions to accomplish this on the Internet but the best functioning version (derived from MSDN's articles on accountExpires, IADsLargeInteger, and DateTime conversions) comes from Tobi's 'tips' 4 and 5 in his notes on Active Directory (http://www.fsmpi.uni-bayreuth.de/~dun3/archives/category/it/programming/active-directory):

First, convert the IADsLargeInteger into a long:

private static long ConvertLargeIntegerToLong(object largeInteger)
{
Type type = largeInteger.GetType();
int highPart = (int)type.InvokeMember("HighPart", BindingFlags.GetProperty, null, largeInteger, null);
int lowPart = (int)type.InvokeMember("LowPart", BindingFlags.GetProperty | BindingFlags.Public, null, largeInteger, null);

return (long)highPart <<32 lowpart="lowpart" p="p" uint="uint">}

Then use this to convert the long into a DateTime.  Note that the DateTime is stored in UTC format -- luckily .NET has the "FromFileTimeUTC()" method ready-made to handle this for us:

object accountExpires = DirectoryEntryHelper.GetAdObjectProperty(directoryEntry, "accountExpires");
var asLong = ConvertLargeIntegerToLong(accountExpires);
    
if (asLong == long.MaxValue || asLong <= 0 || 

DateTime.MaxValue.ToFileTime() <= asLong)
{
return DateTime.MaxValue;
}
else
{
return DateTime.FromFileTimeUtc(asLong);
}

   
Note 1:  "Magic values" in the accountExpires attribute

Two values in the accountExpires attribute indicate that the account is set to "Never Expires."  These are zero (0) and  9223372036854775807 (0x7FFFFFFFFFFFFFFF).
  
See http://msdn.microsoft.com/en-us/library/windows/desktop/ms675098%28v=vs.85%29.aspx

Note 2:  Getting the accountExpires attribute from an ADSI "DirectorySearcher" search result

This is very important (and delayed me quite a while when I didn't take it into account).

If you pull an account's DirectoryEntry using ADSI the 'accountExpires' property is returned as an IADsLargeInteger, but if you get it as a search result using "DirectorySearcher" the property will be presented as a standard long.  No translation from IADsLongInteger are required, you'll just need to do the translation from long to DateTime ("step 2" above).

See http://forums.asp.net/t/999913.aspx and comments by MVP "Dunry" for a useful discussion of this.