Click here to Skip to main content
15,896,912 members
Articles / Web Development / ASP.NET

Storer.ActiveDirectory - Active Directory User/Group Encapsulation Classes

Rate me:
Please Sign up or sign in to vote.
4.79/5 (16 votes)
12 Apr 2007CDDL4 min read 227.6K   525   63  
A couple of classes to handle Users and Groups in Active Directory
/**********************************
 * Author: John Storer II
 * Version: 2.1
 * Do not use without prior consent of the author.
 * 
 * History:
 *  Apr 1 2007: Version 2.0 released.
 *  Apr 9 2007: Version 2.1 General Update/Bug Fix.
 *********************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;

namespace Storer.ActiveDirectory
{
    /// <summary>
    /// Represents a Active Directory Group.
    /// </summary>
    public class Group
    {
        //***************************************************************************************

        #region *** Constructor

        /// <summary>
        /// Represents a Active Directory Group.
        /// </summary>
        protected Group()
        {
        }

        /// <summary>
        /// Represents an Active Directory Group. Populates the internal fields from a SearchResult Property Collection.
        /// </summary>
        /// <param name="Collection">A SearchResult Property Collection.</param>
        public Group(ResultPropertyCollection Collection)
        {
            PopulateFields(Collection);
        }

        /// <summary>
        /// Represents an Active Directory Group. Populates the internal fields from a DirectoryEntry Property Collection.
        /// </summary>
        /// <param name="Collection">A DirectoryEntry Property Collection.</param>
        public Group(PropertyCollection Collection)
        {
            PopulateFields(Collection);
        }

        /// <summary>
        /// Represents an Active Directory Group. Populates the internal fields from a DirectoryEntry User Object.
        /// </summary>
        /// <param name="Collection">A DirectoryEntry User Object.</param>
        public Group(DirectoryEntry UserDirectoryEntry)
        {
            PopulateFields(UserDirectoryEntry.Properties);
        }

        #endregion

        //***************************************************************************************

        #region *** Private Members

        /// <summary>
        /// The Ranged PageSize Attribute. (Win2000 = 1000, Win2003 = 1500)
        /// </summary>
        private const int RangedPageSize = 1500;    // Win2003
        //private const int RangedPageSize = 1000;  // Win2000

        /// <summary>
        /// The Internal Values of the Group Object.
        /// </summary>
        private Dictionary<string, object> _Values = new Dictionary<string, object>();

        /// <summary>
        /// The Properties Loaded into the Group Object.
        /// </summary>
        private List<string> _PropertiesLoaded = new List<string>();

        #endregion

        //***************************************************************************************

        #region *** Private Methods

        /// <summary>
        /// Sets the PropertyLoaded Field for the Specified Key.
        /// </summary>
        /// <param name="Key"></param>
        private void SetPropertyLoaded(string Key)
        {
            if (!_PropertiesLoaded.Contains(Key))
                _PropertiesLoaded.Add(Key);
        }

        /// <summary>
        /// Retrieve Ranged Values.
        /// </summary>
        private void GetRangedValues()
        {
            MemberActions = new SortedDictionary<string, MemberAction>();
            MemberOf = new List<string>();

            int PageSize = RangedPageSize;
            int Page = 0;
            int StartRecord = 0;
            int RecordCount = 0;

            using (DirectoryEntry deGroup = Search.ForDirectoryEntry(Properties.OBJECTSID, ObjectSIDString))
            {
                Page = 0;
                do
                {
                    RecordCount = 0;
                    StartRecord = (Page * PageSize);

                    deGroup.RefreshCache(new string[] { Properties.MEMBER + ";range=" + StartRecord + "-*" });

                    if (deGroup.Properties[Properties.MEMBER] != null)
                    {
                        RecordCount = deGroup.Properties[Properties.MEMBER].Count;

                        foreach (string _Member in deGroup.Properties[Properties.MEMBER])
                            MemberActions.Add(_Member, MemberAction.None);
                    }

                    Page++;
                }
                while (RecordCount == PageSize);

                Page = 0;
                do
                {
                    RecordCount = 0;
                    StartRecord = (Page * PageSize);

                    deGroup.RefreshCache(new string[] { Properties.MEMBEROF + ";range=" + StartRecord + "-*" });

                    if (deGroup.Properties[Properties.MEMBEROF] != null)
                    {
                        RecordCount = deGroup.Properties[Properties.MEMBEROF].Count;

                        foreach (string _MemberOf in deGroup.Properties[Properties.MEMBEROF])
                            MemberOf.Add(_MemberOf);
                    }

                    Page++;
                }
                while (RecordCount == PageSize);
            }

            MemberOf.Sort();
        }

        /// <summary>
        /// Populates the internal fields using a SearchResult Property Collection.
        /// </summary>
        /// <param name="Collection">A SearchResult Property Collection.</param>
        private void PopulateFields(ResultPropertyCollection Collection)
        {
            if (Collection.Contains(Properties.CHANGED))
                Changed = Convert.ToDateTime(Collection[Properties.CHANGED][0]).ToLocalTime();

            if (Collection.Contains(Properties.COMMONNAME))
                CommonName = Collection[Properties.COMMONNAME][0] as string;

            if (Collection.Contains(Properties.CREATED))
                Created = Convert.ToDateTime(Collection[Properties.CREATED][0]).ToLocalTime();

            if (Collection.Contains(Properties.DISTINGUISHEDNAME))
                DistinguishedName = Collection[Properties.DISTINGUISHEDNAME][0] as string;

            if (Collection.Contains(Properties.OBJECTGUID))
            {
                ObjectGUID = Collection[Properties.OBJECTGUID][0] as byte[];
                ObjectGUIDString = Methods.ConvertBytesToStringGUID(ObjectGUID);
            }

            if (Collection.Contains(Properties.OBJECTSID))
            {
                ObjectSID = Collection[Properties.OBJECTSID][0] as byte[];
                ObjectSIDString = Methods.ConvertBytesToStringSid(ObjectSID);
            }

            if (Collection.Contains(Properties.SAMACCOUNTNAME))
                SAMAccountName = Collection[Properties.SAMACCOUNTNAME][0] as string;

            GetRangedValues();
        }

        /// <summary>
        /// Populates the internal fields using a DirectoryEntry Property Collection.
        /// </summary>
        /// <param name="Collection">A DirectoryEntry Property Collection.</param>
        private void PopulateFields(PropertyCollection Collection)
        {
            if (Collection.Contains(Properties.CHANGED))
                Changed = Convert.ToDateTime(Collection[Properties.CHANGED][0]).ToLocalTime();

            if (Collection.Contains(Properties.COMMONNAME))
                CommonName = Collection[Properties.COMMONNAME][0] as string;

            if (Collection.Contains(Properties.CREATED))
                Created = Convert.ToDateTime(Collection[Properties.CREATED][0]).ToLocalTime();

            if (Collection.Contains(Properties.DISTINGUISHEDNAME))
                DistinguishedName = Collection[Properties.DISTINGUISHEDNAME][0] as string;

            if (Collection.Contains(Properties.OBJECTGUID))
            {
                ObjectGUID = Collection[Properties.OBJECTGUID][0] as byte[];
                ObjectGUIDString = Methods.ConvertBytesToStringGUID(ObjectGUID);
            }

            if (Collection.Contains(Properties.OBJECTSID))
            {
                ObjectSID = Collection[Properties.OBJECTSID][0] as byte[];
                ObjectSIDString = Methods.ConvertBytesToStringSid(ObjectSID);
            }

            if (Collection.Contains(Properties.SAMACCOUNTNAME))
                SAMAccountName = Collection[Properties.SAMACCOUNTNAME][0] as string;

            GetRangedValues();
        }

        #endregion

        //***************************************************************************************

        #region *** Public Properties

        /// <summary>
        /// Retrieve the value by the specified key.
        /// </summary>
        /// <param name="Key">The property key.</param>
        /// <returns>An object that must be cast to the correct type to be used.</returns>
        public object this[string Key]
        {
            get
            {
                return (_Values.ContainsKey(Key) ? _Values[Key] : null);
            }
            set
            {
                if (_Values.ContainsKey(Key))
                    _Values[Key] = value;
                else
                    _Values.Add(Key, value);
                SetPropertyLoaded(Key);
            }
        }

        /// <summary>
        /// Changed - Shows the last time the user was updated/modified.
        /// </summary>
        public DateTime Changed
        {
            get { return (DateTime?)this[Properties.CHANGED] ?? DateTime.MinValue; }
            private set { this[Properties.CHANGED] = value; }
        }

        /// <summary>
        /// Common Name - The user's Short Path Name in Active Directory.
        /// This value cannot be set through this property.
        /// </summary>
        public string CommonName
        {
            get { return this[Properties.COMMONNAME] as string; }
            set { this[Properties.COMMONNAME] = value; }
        }

        /// <summary>
        /// Created - Shows the date/time the user was created.
        /// </summary>
        public DateTime Created
        {
            get { return (DateTime?)this[Properties.CREATED] ?? DateTime.MinValue; }
            private set { this[Properties.CREATED] = value; }
        }

        /// <summary>
        /// Distinguished Name - The user's Full Path in Active Directory.
        /// </summary>
        public string DistinguishedName
        {
            get { return this[Properties.DISTINGUISHEDNAME] as string; }
            private set { this[Properties.DISTINGUISHEDNAME] = value; }
        }

        /// <summary>
        /// Member Of - The Groups the this group is a direct member of.
        /// </summary>
        public List<string> MemberOf
        {
            get { return (List<string>)this[Properties.MEMBEROF] ?? new List<string>(); }
            private set { this[Properties.MEMBEROF] = value; }
        }

        /// <summary>
        /// Members - The Groups that are a direct member of this group.
        /// </summary>
        public List<string> Members
        {
            get
            {
                List<string> _Members = new List<string>();
                foreach (string _Member in MemberActions.Keys)
                    _Members.Add(_Member);
                return _Members;
            }
        }

        /// <summary>
        /// Member Actions - The Actions to Perform on Groups that are a direct member of this group.
        /// </summary>
        public SortedDictionary<string, MemberAction> MemberActions
        {
            get { return this[Properties.MEMBERACTIONS] as SortedDictionary<string, MemberAction>; }
            private set { this[Properties.MEMBERACTIONS] = value; }
        }

        /// <summary>
        /// Object GUID - The GUID Identifier in byte[] format.
        /// </summary>
        public byte[] ObjectGUID
        {
            get { return this[Properties.OBJECTGUID] as byte[]; }
            private set { this[Properties.OBJECTGUID] = value; }
        }

        /// <summary>
        /// Object GUID - The GUID Identifier in string format.
        /// </summary>
        public string ObjectGUIDString
        {
            get { return this[Properties.OBJECTGUIDSTRING] as string; }
            private set { this[Properties.OBJECTGUIDSTRING] = value; }
        }

        /// <summary>
        /// Object SID - The Unique Object Identifier in byte[] format.
        /// </summary>
        public byte[] ObjectSID
        {
            get { return this[Properties.OBJECTSID] as byte[]; }
            private set { this[Properties.OBJECTSID] = value; }
        }

        /// <summary>
        /// Object SID String - The Unique Object Identifier in string format.
        /// </summary>
        public string ObjectSIDString
        {
            get { return this[Properties.OBJECTSIDSTRING] as string; }
            private set { this[Properties.OBJECTSIDSTRING] = value; }
        }

        /// <summary>
        /// SAM Account Name - The User's Login Username. (without domain info)
        /// </summary>
        public string SAMAccountName
        {
            get { return this[Properties.SAMACCOUNTNAME] as string; }
            set { this[Properties.SAMACCOUNTNAME] = value; }
        }

        #endregion

        //***************************************************************************************

        #region *** Public Methods

        /// <summary>
        /// Returns True if the Property is being used (by initialization or by access) in this object.
        /// </summary>
        /// <param name="Key">the Property Key.</param>
        /// <returns>True if Property Key is being used. False if not.</returns>
        public bool IsPropertyLoaded(string Key)
        {
            return _PropertiesLoaded.Contains(Key);
        }

        /// <summary>
        /// Add a Member to this Group. Returns true if successful. (Call SaveChanges to Commit)
        /// </summary>
        /// <param name="DistinguishedName">The DistinguishedName of a User Object.</param>
        /// <returns>True if Successful.</returns>
        public bool AddMember(string DistinguishedName)
        {
            if (!MemberActions.ContainsKey(DistinguishedName))
            {
                MemberActions.Add(DistinguishedName, MemberAction.Add);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Remove a Member from this Group. Returns true if successful. (Call SaveChanges to Commit)
        /// </summary>
        /// <param name="DistinguishedName">The DistinguishedName of a User Object.</param>
        /// <returns>True if Successful.</returns>
        public bool RemoveMember(string DistinguishedName)
        {
            if (MemberActions.ContainsKey(DistinguishedName))
            {
                MemberActions[DistinguishedName] = MemberAction.Remove;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Saves the Current Changes.
        /// </summary>
        public void SaveChanges()
        {
            List<string> AddKeys = new List<string>();
            List<string> RemoveKeys = new List<string>();

            try
            {
                using (DirectoryEntry deGroup = Search.ForDirectoryEntry(Properties.OBJECTSID, ObjectSIDString))
                {
                    if (!SAMAccountName.Equals(deGroup.Properties[Properties.SAMACCOUNTNAME].Value) && _PropertiesLoaded.Contains(Properties.SAMACCOUNTNAME))
                        deGroup.Properties[Properties.SAMACCOUNTNAME].Value = SAMAccountName;

                    foreach (string MemberName in MemberActions.Keys)
                    {
                        if (MemberActions[MemberName] == MemberAction.Add)
                        {
                            deGroup.Invoke("Add", new object[] { @"LDAP://" + MemberName });
                            AddKeys.Add(MemberName);
                        }
                        else if (MemberActions[MemberName] == MemberAction.Remove)
                        {
                            deGroup.Invoke("Remove", new object[] { @"LDAP://" + MemberName });
                            RemoveKeys.Add(MemberName);
                        }
                    }

                    deGroup.CommitChanges();

                    // Reset All AddKeys
                    foreach (string Key in AddKeys)
                        MemberActions[Key] = MemberAction.None;

                    // Remove All RemoveKeys
                    foreach (string Key in RemoveKeys)
                        MemberActions.Remove(Key);

                    if (_PropertiesLoaded.Contains(Properties.COMMONNAME))
                        if (!object.Equals(deGroup.Properties[Properties.COMMONNAME].Value, CommonName))
                        {
                            deGroup.Rename("CN=" + CommonName);
                            deGroup.CommitChanges();
                        }
                }
            }
            catch (Exception Error)
            { throw new Exception("Save Error.", Error); }
        }

        #endregion

        //***************************************************************************************

        #region *** Internal Classes

        /// <summary>
        /// The Action to Perform on the Group Member
        /// </summary>
        public enum MemberAction
        {
            /// <summary>
            /// No Change to the Group Member.
            /// </summary>
            None,

            /// <summary>
            /// Add Group Member to Group.
            /// </summary>
            Add,

            /// <summary>
            /// Remove Group Member from Group.
            /// </summary>
            Remove
        }


        /// <summary>
        /// The Property Keys for the Active Directory Group Object.
        /// </summary>
        public class Properties
        {

            /// <summary>
            /// (DateTime, 'whenChanged') Changed - Shows the last time the object was updated/modified.
            /// </summary>
            public const string CHANGED = "whenChanged";

            /// <summary>
            /// (string, 'cn') Common Name - The object's Short Path Name in Active Directory.
            /// </summary>
            public const string COMMONNAME = "cn";

            /// <summary>
            /// (DateTime, 'whenCreated') Created - Shows the time the object was created.
            /// </summary>
            public const string CREATED = "whenCreated";

            /// <summary>
            /// (string, 'distinguishedName') Distinguished Name - The object's Full Path Name in Active Directory.
            /// </summary>
            public const string DISTINGUISHEDNAME = "distinguishedName";

            /// <summary>
            /// (string[], 'member') Member - The groups that are a member of this group..
            /// </summary>
            public const string MEMBER = "member";

            /// <summary>
            /// (string[], 'memberOf') Member Of - The groups that this group is a direct member of.
            /// </summary>
            public const string MEMBEROF = "memberOf";

            /// <summary>
            /// (byte[], 'objectGuid') Object GUID - The Unique Object Identifier.
            /// </summary>
            public const string OBJECTGUID = "objectGuid";

            /// <summary>
            /// (byte[], 'objectSid') Object SID - The Unique Object Identifier.
            /// </summary>
            public const string OBJECTSID = "objectSid";

            /// <summary>
            /// (string, 'sAMAccountName') SAM Account Name - The Group UserName.
            /// </summary>
            public const string SAMACCOUNTNAME = "sAMAccountName";

            /// <summary>
            /// (string, 'tokenGroups') Token Groups - The recursive group membership list. (CANNOT BE RETRIEVED THROUGH DIRECTORYSEARCHER)
            /// </summary>
            public const string TOKENGROUPS = "tokenGroups";


            // NON-ACTIVE DIRECTORY BASED PROPERTIES

            /// <summary>
            /// (string, 'objectGuidString') Object GUID String - String Value of the (byte[]) Object GUID.
            /// [NOTE: Use 'OBJECTGUID' for keys/searching outside of the parent 'User' class.]
            /// </summary>
            public const string OBJECTGUIDSTRING = "objectGuidString";

            /// <summary>
            /// (string, 'objectSidString') Object SID String - String Value of the (byte[]) Object SID.
            /// [NOTE: Use 'OBJECTSID' for keys/searching outside of the parent 'Group' class.]
            /// </summary>
            public const string OBJECTSIDSTRING = "objectSidString";

            /// <summary>
            /// (Dictionary, 'member') MemberActions - The Actions to Perform on Groups that are a member of this group..
            /// </summary>
            public const string MEMBERACTIONS = "MemberActions";
        }

        #endregion

        //***************************************************************************************
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer Employer's Security
United States United States
I'm a Computer Support Technician for a Indiana School System. Really, I'm a C#/ASP.NET Developer in a Software Support coat. Jack-of-all-trades, I do Hardware Support, Software Support, Programming in C#.NET, ASP.NET / Javascript, Web Design, Graphic Arts and lots of other stuff.

I've been programming almost 21 years now, and have used more than 10 different languages in the past.

Comments and Discussions