Click here to Skip to main content
13,300,002 members (48,122 online)
Click here to Skip to main content
Add your own
alternative version


63 bookmarked
Posted 3 Apr 2007

Storer.ActiveDirectory - Active Directory User/Group Encapsulation Classes

, 12 Apr 2007
Rate this:
Please Sign up or sign in to vote.
A couple of classes to handle Users and Groups in Active Directory


For almost three years now I've been interfacing with Active Directory through C#. Utilizing DirectoryEntry-style access is slow and cumbersome when you're dealing with multiple users, especially when your goal is simply read-only access. Plus, remembering all the different parameters to use can be difficult when you're trying to write a quick program to solve something, and not a several-month project.

So, for all of you who use Active Directory (or are thinking about it), this class library is for you!

Here's the Idea

System.DirectoryServices has a nice search feature for Active Directory called DirectorySearcher. This class is much faster than a DirectoryEntry for accessing data in a user or group object.

The idea is this: set up a way to make it both fast and simple for Active Directory User/Group Access. With my class, it can be a simple as:

static void Main(string[] args)
        // Find the User "Administrator" and View/Modify
        User _User = Search.ForUser(User.Properties.SAMACCOUNTNAME, 
        Console.WriteLine("Username:            {0}", 
        Console.WriteLine("Full Name:           {0}", 
        Console.WriteLine("DistinguishedName:   {0}", 
        Console.WriteLine("Logon Count:         {0}",  
        Console.WriteLine("Object SID (string)  {0}", 

        foreach (string GroupName in _User.TokenGroups)
            Console.WriteLine("Token Group:         {0}", 

        _User.Enabled = false;
        _User.FirstName = "Bob";
        _User.MiddleInitial = "T";
        _User.LastName = "Admin";

        // Find the Group "Administrators" and View/Modify

        Group _Group = Search.ForGroup(Group.Properties.COMMONNAME, 
        Console.WriteLine("Group:               {0}", 
        foreach (string _Member in _Group.Members)
            Console.WriteLine("Member:              {0}", _Member);


    catch (Exception Error)
        Console.WriteLine("Error: {0}", Error);

An extension to this is finding a list of Users or Groups, which can be done with the Search.ForUsers(...) or Search.ForGroups(...) method.

Using the code

Simply Add the reference to Storer.ActiveDirectory to your code, then use the following Classes for your projects:

  • Storer.ActiveDirectory.User: The User Class.
  • Storer.ActiveDirectory.Group: The Group Class.
  • Storer.ActiveDirectory.Search: Static Methods for Finding Users, Group and DirectoryEntries.
  • Storer.ActiveDirectory.Methods: Static Methods for doing useful things like getting the Domain Name, or Authenticating a User (with a password), or even converting a Byte[] ObjectSID to an ObjectSIDString (Which is already done for you in the User/Group Classes).

You may also need to add a reference to System.DirectoryServices if you're going to be using custom search parameters and/or moving DirectoryEntry objects.

Also: Don't worry about disposing of any unused COM Objects in the User/Group code. Any necessary disposal is handled for you, except for if you pass a DirectoryEntry object into the Search.ForUsers(...) Method, in which case you'll have to dispose of the Search Root used; I would recommend a using clause for that.

Points of Interest

The secret, as said above, is using the DirectorySearcher class instead of the DirectoryEntry class for accessing the properties of a User or Group Object.

Accessing the Values from a DirectorySearcher are the same as accessing them from a DirectoryEntry. I encapsulated the process in the following method:

private void PopulateFields(ResultPropertyCollection Collection)
    if (Collection.Contains(Properties.ACCOUNTCONTROL))
        AccountControl = (int?)Collection[Properties.ACCOUNTCONTROL][0] ?? 0;

    if (Collection.Contains(Properties.ASSISTANT))
        Assistant = Collection[Properties.ASSISTANT][0] as string;

    if (Collection.Contains(Properties.CELLPHONE))
        CellPhone = Collection[Properties.CELLPHONE][0] as string;
    if (Collection.Contains(Properties.STREETADDRESS))
        StreetAddress = Collection[Properties.STREETADDRESS][0] as string;

    if (Collection.Contains(Properties.USERPRINCIPALNAME))
        UserPrincipalName = Collection[Properties.USERPRINCIPALNAME][0] as 

    if (Collection.Contains(Properties.ZIPCODE))
        ZipCode = Collection[Properties.ZIPCODE][0] as string;

Saving the Changes made to a User is handled by retrieving the DirectoryEntry by ObjectSID and saving only the Changed Values.

public void SaveChanges()
        using (DirectoryEntry deUser = 
            Search.ForDirectoryEntry(Properties.OBJECTSID, ObjectSIDString))
            if (_PropertiesLoaded.Contains(Properties.ACCOUNTCONTROL))
                if (!object.Equals(deUser.Properties[Properties.
                    ACCOUNTCONTROL].Value, AccountControl))
                    SetPropertyValue(deUser, Properties.ACCOUNTCONTROL, 

            if (_PropertiesLoaded.Contains(Properties.ASSISTANT))
                if (!object.Equals(deUser.Properties[Properties.
                    ASSISTANT].Value, Assistant))
                    SetPropertyValue(deUser, Properties.ASSISTANT, 

            if (_PropertiesLoaded.Contains(Properties.CELLPHONE))
                if (!object.Equals(deUser.Properties[Properties.
                    CELLPHONE].Value, CellPhone))
                    SetPropertyValue(deUser, Properties.CELLPHONE, 
            if (_PropertiesLoaded.Contains(Properties.STREETADDRESS))
                if (!object.Equals(deUser.Properties[Properties.
                    STREETADDRESS].Value, StreetAddress))
                    SetPropertyValue(deUser, Properties.STREETADDRESS,  

            if (_PropertiesLoaded.Contains(Properties.USERPRINCIPALNAME))
                if (!object.Equals(deUser.Properties[Properties.
                    USERPRINCIPALNAME].Value, UserPrincipalName))
                    SetPropertyValue(deUser, Properties.USERPRINCIPALNAME, 

            if (_PropertiesLoaded.Contains(Properties.ZIPCODE))
                if (!object.Equals(deUser.Properties[Properties.
                    ZIPCODE].Value, ZipCode))
                    SetPropertyValue(deUser, Properties.ZIPCODE, ZipCode);


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

Handling the Multi-Value Keys are usually very simple, and are made read-only. Almost all of the User properties and All of the Group Properties can be retrieved using a DirectorySearcher object, with the exception of User.TokenGroups. This requires a different approach:

public List<string> TokenGroups
        if (this[Properties.TOKENGROUPS] == null)
            this[Properties.TOKENGROUPS] = GetTokenGroups(ObjectSIDString);
        return (List<string>)this[Properties.TOKENGROUPS];
    private set { this[Properties.TOKENGROUPS] = value; }
public static List<string> GetTokenGroups(string ObjectSIDString)
    List<string> TokenGroups = new List<string>();

        using (DirectoryEntry deUser = 
            Search.ForDirectoryEntry(Properties.OBJECTSID, ObjectSIDString))
            deUser.RefreshCache(new string[] { Properties.TOKENGROUPS });

            if (deUser.Properties.Contains(Properties.TOKENGROUPS))
                if (deUser.Properties[Properties.TOKENGROUPS] != null)
                    foreach (byte[] GroupSID in 
                        string sGroupSID = 
                        string sGroupName = Search.ForGroupName(sGroupSID);
                        if (!string.IsNullOrEmpty(sGroupName))
    { throw; }

    return TokenGroups;

You have to use a DirectoryEntry object to retrieve the Token Groups, because it is a calculated property. Notice that the methods that require direct DirectoryEntry access are static methods, as to keep them separate from the rest of the class.

Another point of interest is setting user flags. Here in the User class, I set four: Enabled, MustChangePasswordOnNextLogin, CannotChangePassword, and PasswordNeverExpires. All but the CannotChangePassword are handled through the AccountControl & PasswordLastSet properties. The CannotChangePassword switch is shown below; it's a bit more complicated:

public static void SetFlag_CannotChangePassword(string ObjectSIDString, 
    bool Value)

    Guid ChangePasswordGUID = new 
    bool wasModified = false;

        using (DirectoryEntry deUser = 
            Search.ForDirectoryEntry(Properties.OBJECTSID, ObjectSIDString))
            ActiveDirectorySecurity ads = deUser.ObjectSecurity;
            AuthorizationRuleCollection arc = ads.GetAccessRules(true, true, 

            foreach (ActiveDirectoryAccessRule adar in arc)
                if (adar.ObjectType == ChangePasswordGUID && 
                    (adar.IdentityReference.Value == @"EVERYONE" || 
                    adar.IdentityReference.Value == @"NT 
                    ActiveDirectoryAccessRule AccessRule = new 
                        adar.ActiveDirectoryRights, AccessControlType.Deny, 
                        adar.ObjectType, adar.InheritanceType);
                    if (!ads.ModifyAccessRule((Value ? 
                        AccessControlModification.Add : 
                        AccessRule, out wasModified))
                        throw new Exception("ACE Not Modified: (" + 
                            adar.IdentityReference.Value + ")");

            deUser.ObjectSecurity = ads;
    { throw; }

Note the GUID: It took me forever to figure that out, and without it very odd behavior occurs with users if you set every ADAR to Disallow/Allow. It's usually the simplest things!

The User.Path property is calculated from the DistinguishedName, and is simply a list of the parts of the path in reverse order, much like a Directory is listed (such as C:\Windows\Somewhere\Somefile.txt, it's listed like: "com\company\ouname\ouname\commonname").

The best part of the Search method's deal with the PropertiesToLoad method variables. When you search for a user, you can choose to only return a few of the properties of a user (such as FirstName or SAMAccountName) instead of the whole thing. Make sure to check it out: it will make retrieving your Users and Groups that much faster!

I encourage you to experiment and develop the code. I've gone through many iterations of this class and this has been the best/fastest. I only included the Properties that I use most often, but there are other User and Group Properties. Add or remove whatever you need to: always tailor it to suit your needs. Please let me know if/when you do, so that I can make my own adjustments.


  • Sunday, April 1, 2007 [2.0]: Uploaded to CodeProject.
  • Sunday, April 9, 2007 [2.1]: General Update of Code. Many changes occurred; please see source code.
    • Properties changed to allow nulls from string (Convert.ToString(object) will return null when the object is null, but Convert.ToString(string) return string.Empty on null.
    • List<*> Properties now use ?? to prevent nulls, which cuts down on Exceptions thrown.
    • SaveChanges() and PopulateValues() bug fixes/updates.
    • User.Path changed from List<string> to string with "\" separators.
    • Various other fixes/updates.


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


About the Author

John Storer II
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.

You may also be interested in...

Comments and Discussions

Questionwhat account/credentials are used to perform operations Pin
Member 724777628-Jul-10 9:15
memberMember 724777628-Jul-10 9:15 
AnswerRe: what account/credentials are used to perform operations Pin
John Storer II28-Jul-10 11:07
memberJohn Storer II28-Jul-10 11:07 
GeneralRe: what account/credentials are used to perform operations [modified] Pin
Member 724777629-Jul-10 6:26
memberMember 724777629-Jul-10 6:26 
GeneralRe: what account/credentials are used to perform operations Pin
John Storer II29-Jul-10 11:48
memberJohn Storer II29-Jul-10 11:48 
GeneralRe: what account/credentials are used to perform operations [modified] Pin
Member 72477762-Aug-10 9:26
memberMember 72477762-Aug-10 9:26 
GeneralRe: what account/credentials are used to perform operations Pin
Member 72477763-Aug-10 10:01
memberMember 72477763-Aug-10 10:01 
GeneralIsUserInGroup method Pin
Cabbi17-Mar-10 6:44
memberCabbi17-Mar-10 6:44 
GeneralRe: IsUserInGroup method Pin
John Storer II17-Mar-10 7:04
memberJohn Storer II17-Mar-10 7:04 
GeneralRe: IsUserInGroup method Pin
Cabbi17-Mar-10 21:57
memberCabbi17-Mar-10 21:57 
GeneralRe: IsUserInGroup method Pin
Cabbi18-Mar-10 5:57
memberCabbi18-Mar-10 5:57 
GeneralRe: IsUserInGroup method Pin
John Storer II18-Mar-10 6:57
memberJohn Storer II18-Mar-10 6:57 
GeneralRe: IsUserInGroup method Pin
Cabbi18-Mar-10 7:08
memberCabbi18-Mar-10 7:08 
GeneralRe: IsUserInGroup method Pin
John Storer II18-Mar-10 7:18
memberJohn Storer II18-Mar-10 7:18 
GeneralRe: IsUserInGroup method Pin
Cabbi17-Mar-10 23:28
memberCabbi17-Mar-10 23:28 
GeneralRe: IsUserInGroup method Pin
Cabbi18-Mar-10 7:22
memberCabbi18-Mar-10 7:22 
GeneralRe: IsUserInGroup method Pin
John Storer II18-Mar-10 7:31
memberJohn Storer II18-Mar-10 7:31 
AnswerRe: IsUserInGroup method *** RESOLVED *** Pin
Cabbi18-Mar-10 7:52
memberCabbi18-Mar-10 7:52 
QuestionHow to find all the user in AD GROUP in sqlserver 2005. Pin
krazzz21-Sep-09 21:02
memberkrazzz21-Sep-09 21:02 
Generaluse of the properties Pin
royalstar24-Nov-08 4:18
memberroyalstar24-Nov-08 4:18 
GeneralQuering multiple domains Pin
Joao Matos12-Sep-08 1:32
memberJoao Matos12-Sep-08 1:32 
GeneralTelephone Number Pin
ajonas11-Jun-08 13:25
memberajonas11-Jun-08 13:25 
GeneralRe: Telephone Number Pin
ajonas12-Jun-08 9:03
memberajonas12-Jun-08 9:03 
GeneralGet rights of User on a group [modified] Pin
Offlinesurfer31-May-08 1:48
memberOfflinesurfer31-May-08 1:48 
GeneralRe: Get rights of User on a group Pin
John Storer II31-May-08 6:29
memberJohn Storer II31-May-08 6:29 
GeneralRe: Get rights of User on a group Pin
Offlinesurfer1-Jun-08 2:50
memberOfflinesurfer1-Jun-08 2:50 
GeneralRe: Get rights of User on a group Pin
Offlinesurfer1-Jun-08 4:47
memberOfflinesurfer1-Jun-08 4:47 
GeneralStorer.ActiveDirectory.User.Create() Pin
ajonas30-May-08 8:10
memberajonas30-May-08 8:10 
GeneralRe: Storer.ActiveDirectory.User.Create() Pin
John Storer II30-May-08 8:21
memberJohn Storer II30-May-08 8:21 
GeneralRe: Storer.ActiveDirectory.User.Create() Pin
ajonas3-Jun-08 7:35
memberajonas3-Jun-08 7:35 
GeneralRe: Storer.ActiveDirectory.User.Create() Pin
John Storer II3-Jun-08 8:15
memberJohn Storer II3-Jun-08 8:15 
GeneralRe: Storer.ActiveDirectory.User.Create() Pin
ajonas3-Jun-08 12:08
memberajonas3-Jun-08 12:08 
GeneralRe: Storer.ActiveDirectory.User.Create() Pin
John Storer II3-Jun-08 12:17
memberJohn Storer II3-Jun-08 12:17 
GeneralPermission to use Pin
job260022-May-08 16:04
memberjob260022-May-08 16:04 
JokeRe: Permission to use Pin
John Storer II23-May-08 2:43
memberJohn Storer II23-May-08 2:43 
GeneralBug with DirectoryEntry, searching from User then to Groups (damn DirectoryEntry again!). Pin
Glyder18-Mar-08 22:04
memberGlyder18-Mar-08 22:04 
QuestionASP.NET Web-site with Impersonate Pin
kasyan16-Dec-07 20:54
memberkasyan16-Dec-07 20:54 
GeneralRe: ASP.NET Web-site with Impersonate Pin
kasyan18-Dec-07 5:29
memberkasyan18-Dec-07 5:29 
GeneralUser.SaveChanges(); error Pin
Seishin#11-Oct-07 1:34
memberSeishin#11-Oct-07 1:34 
GeneralRe: User.SaveChanges(); error Pin
John Storer II11-Oct-07 1:47
memberJohn Storer II11-Oct-07 1:47 
GeneralUsers from Group Pin
kasyan19-Sep-07 1:50
memberkasyan19-Sep-07 1:50 
GeneralRe: Users from Group Pin
John Storer II19-Sep-07 2:20
memberJohn Storer II19-Sep-07 2:20 
GeneralRe: Users from Group Pin
kasyan21-Sep-07 2:14
memberkasyan21-Sep-07 2:14 
GeneralRe: Users from Group Pin
John Storer II21-Sep-07 2:32
memberJohn Storer II21-Sep-07 2:32 
GeneralRe: Users from Group Pin
kasyan21-Sep-07 3:18
memberkasyan21-Sep-07 3:18 
GeneralRe: Users from Group Pin
John Storer II21-Sep-07 4:16
memberJohn Storer II21-Sep-07 4:16 
GeneralRe: Users from Group Pin
kasyan21-Sep-07 7:02
memberkasyan21-Sep-07 7:02 
GeneralRe: Users from Group Pin
John Storer II21-Sep-07 10:23
memberJohn Storer II21-Sep-07 10:23 
GeneralRe: Users from Group Pin
kasyan28-Nov-07 23:19
memberkasyan28-Nov-07 23:19 
GeneralSome issues [modified] Pin
atw1234526-Jul-07 2:45
memberatw1234526-Jul-07 2:45 
QuestionConnect to another Directory Service Pin
Diedzz24-Jul-07 2:57
memberDiedzz24-Jul-07 2:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171207.1 | Last Updated 12 Apr 2007
Article Copyright 2007 by John Storer II
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid