Contact

Technology

Jun 01, 2011

Deciphering Active Directory Data Types

Jeff Hewitt

Jeff Hewitt

Default image background

From authenticating your application users against Active Directory to managing Active Directory objects in custom code, it seems that a developer in a Microsoft environment is never too far away from Active Directory – at least that has been true in my experience.

At first glance, integrating your code with Active Directory seems pretty straight forward. Simply leverage a few managed objects from the System.DirectoryServices namespace, and in less than 10 lines of code you can retrieve an object from an Active Directory repository. Unfortunately, once you get the requested object back, you’re likely to lose momentum if you haven’t interfaced with Active Directory in a while. The primary reason is that Active Directory objects aren’t strongly typed and therefore don’t offer any intellisense to help you find the information you need. As an example, you can’t type user.DisplayName to retrieve the string representation of the user’s display name.  Instead, you have to type user(“displayName”), which adds to the confusion by returning an array of objects.  So, not only do you have to know that there should be a property on the object called, displayName, you also have to know what to expect from the property when it is invoked.  In this case, it’s pretty simple – displayName returns an array of objects with a length of one and contains the string representation of the user’s display name.  Not as straight forward as I would like, but it’s pretty easy to get to the display name once you know the property name.  So, having mastered displayName, what if you wanted to know when the when the user had last logged into the network?  You would start by getting the value of the lastLogOn property, which will also return an array of objects containing a single value.  However, this time, the value is a COM object of type Interop.ActiveDs.IADsLargeInteger, which consists of a high and low part representing a date and time value.  To make easy use of the value, it would need to be converted to a System.DateTime object.  If you wanted to make a modification to the property and persist it back to the repository, you would also have to convert it back to the IADsLargeInteger type.

In addition to the IADSLargeInteger, there are three other types returned from Active Directory that require some sort of conversion before they can be leveraged in your .NET application.  Together, these types are:

  • IADsLargeInteger

  • GUID

  • SID

  • The User Account Control value

Let’s start by converting the IADsLargeInteger object into something a little more usable.

IADsLargeInteger Conversion

Unfortunately, converting to and from the IADsLargeInteger requires direct calls to the WIN32 API.  Fortunately, these external methods do virtually all of the work for you.  So, the first thing that you’ll need to include in your code are references to the external methods as well as a few structures to support the calls (the DLLImport attribute requires a reference to System.Runtime.InteropServices).

        private struct SYSTEMTIME         {             public int iYear;             public int iMonth;             public int iDayOfWeek;             public int iDay;             public int iHour;             public int iMinute;             public int iSecond;             public int iMilliseconds;         }

        private struct FILETIME         {             public int iLow;             public int iHigh;         }

        [DllImport("kernel32.dll")]         static extern long SystemTimeToFileTime(ref SYSTEMTIME lpSystemTime , ref FILETIME lpFileTime);

        [DllImport("oleaut32.dll")]         static extern long VariantTimeToSystemTime(DateTime VariantTime , ref SYSTEMTIME lpSystemTime);

Since these external methods aren’t very self explanatory, I’ve abstracted them with two conversion functions.

        public static DateTime GetDateTimeFromLargeInteger(IADsLargeInteger largeIntValue)         {             long int64Value = (long)((uint)largeIntValue.LowPart +                 (((long)largeIntValue.HighPart) << 32));

            return DateTime.FromFileTimeUtc(int64Value);         }

        private IADsLargeInteger DateTimeToIADsLargeInteger(DateTime dateAndTime)         {             FILETIME fileTime = default(FILETIME);             SYSTEMTIME systemTime = default(SYSTEMTIME);             IADsLargeInteger aDDate = new IADsLargeInteger();

            VariantTimeToSystemTime(dateAndTime.AddDays(1), ref systemTime);             SystemTimeToFileTime(ref systemTime, ref fileTime);

            aDDate.HighPart = fileTime.iHigh;             aDDate.LowPart = fileTime.iLow;

            return aDDate;         }

Guid Conversion

Every object in Active Directory, including users and groups, is uniquely identified using a guid serialized as an array of bytes.  This value is accessed using the readonly objectGuid property which returns a 128 length byte array (once again, stored in the first slot of a single slot array).  To convert the byte array to a System.Guid, you can simply instantiate a new guid object with the byte array.

            Guid guid = new Guid(bytes);

However, if you want to use the guid value returned from your Active Directory object in future searches (using the System.DirectoryServices.DirectorySearcher object) or for associations (e.g. adding a user to a group), the value will need to be represented as a split-octet string where each byte is rendered in it’s hexadecimal representation and delimited by the ‘\’ character.  The following method, converts the byte array returned from the objectGuid property into it’s split-octet string representation.

        public string ConvertToSplitOctetString(byte[] bytes)

            StringBuilder sb = new StringBuilder((bytes.GetUpperBound(0) + 1) * 2);             for (int i = 0; i < bytes.GetUpperBound(0) + 1; i++)                 sb.AppendFormat("\\{0}", bytes[i].ToString("x2"));             return sb.ToString();         }

Since we won’t ever be persisting a modification of this value back to Active Directory, there’s no need to convert a split-octet string back to a byte array.

Object SID Conversion

When an Active Directory object is created, it is assigned it’s very own, globally unique identifier.  This identifier is stored in the objectSid property.  This value may be subsequently changed by the system, but once a value is assigned, it will never be used again.  Old values are stored in the object’s sidHistory property, which returns an array of objects where each slot contains a byte array representing a SID.  However, the SID byte array cannot be converted to a guid, because it has 224 bytes, instead of 128.  However, it can be converted to a split-octet string using the same logic applied to the guid conversion.  And this split-octet string is understood by Active Directory in search and association operations.

User Account Control

The userAccountControl property is an integer that represents a user accounts account control flags.  Once you know what the flags are and their values, you can use bitwise operations to set or clear discrete flags – but who wants to memorize 20+ flags and their corresponding values?  Instead, use the following enumeration which provides the account control flag as well as it’s value for manipulating the Active Directory userAccountControl property.

        public enum UserAccountControlFlag : int         {             SCRIPT = 0x0001,             ACCOUNTDISABLE = 0x0002,             HOMEDIR_REQUIRED = 0x0008,             LOCKOUT = 0x0010,             PASWD_NOTREQD = 0x0020,             PASSWD_CANT_CHANGE = 0x0040,             ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080,             TEMP_DUPLICATE_ACCOUNT = 0x0100,             NORMAL_ACCOUNT = 0x0200,             INTERDOMAIN_TRUST_ACCOUNT = 0x0800,             WORKSTATION_TRUST_ACCOUNT = 0x1000,             SERVER_TRUST_ACCOUNT = 0x2000,             DONT_EXPIRE_PASSWORD = 0x10000,             MNS_LOGON_ACCOUNT = 0x20000,             SMARTCARD_REQUIRED = 0x40000,             TRUSTED_FOR_DELEGATION = 0x80000,             NOT_DELEGATED = 0x100000,             USE_DES_KEY_ONLY = 0x200000,             DONT_REQ_PREAUTH = 0x400000,             PASSWORD_EXPIRED = 0x800000,             TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000         }

With the UserAccountControlFlag enumeration, the following functions can be used to manipulate the userAccountControl property value.

        public bool IsFlagSet(UserAccountControlFlag value, UserAccountControlFlag flag)         {             return (value & flag) == flag;         }

        public UserAccountControlFlag ToggleFlag(UserAccountControlFlag value         , UserAccountControlFlag flag)         {             if (this.IsFlagSet(value, flag)) return value & ~flag;             return value | flag;         }

In each of these functions, the value parameter is the value retrieved from the Active Directory object and the flag parameter is the user account control value in question.

References

http://msdn.microsoft.com/en-us/library/ms180872(v=vs.80).aspx

http://support.microsoft.com/kb/305144

http://msdn.microsoft.com/en-us/library/ms675085(v=vs.85).aspx

    Conversation Icon

    Contact Us

    Ready to achieve your vision? We're here to help.

    We'd love to start a conversation. Fill out the form and we'll connect you with the right person.

    Searching for a new career?

    View job openings