C# ALL Active Directory Users in an AD Security Group Recursively

Posted: 2009-06-22 in .Net
Tags: ,

Simple example of stepping through Active directory recursively to extract a complete list of Users in a Security Group. Written in C# and requires .Net 3.5 or better.

Overview:

This is a simple example of how to recursively extract a complete list of Users in a Security Group. Its written in C# and requires .Net 3.5 or better.

The program has three classes,

AdGroupMember.cs-> the Brains that calls AD and creates the AdUsers

AdUser.cs -> Simple Data Entity class used for storing of information

program.cs -> Sample entry point

Due to cut n Paste issue the code is below is not formatted correctly so copy it into Visual Studio to get a better look. Use the copy code button or show source to get the unformatted code.
I also recommend you use resharper, agent smith, agent johnsson and Ghost Doc plugins for Visual Studio
No copywrite and is an as is EULA so enjoy.

Setup and Running It:

Create a new console application.
Create the three new classes and copy the relivant code into the correct place.
Force visual studio to reformat the page (Ctrl-k, d)
Add reference to System.DirectoryServices

Change the constant called LDAP_DC_COMPANY_DC_COM in class AdGroupMember to point to your companys Active Directory server name (ask your IT man here).
Usually its the company name and DC=com to DC=net or DC=org
EG: “LDAP://DC=microsoft,DC=net”;

The Code:

program.cs

[csharp]
using System;
using System.Collections.Generic;

namespace ActiveDirectoryRecursively
{
class Program
{
static void Main(string[] args)
{
List<AdUser> usersInGroup = new List<AdUser>();
usersInGroup.AddRange(AdGroupMembers.LoadAllAcitiveDirectoryUsers(“DL-My-First-Group”));
usersInGroup.AddRange(AdGroupMembers.LoadAllAcitiveDirectoryUsers(“DL-My-Second-Group”));

foreach (AdUser user in usersInGroup)
{
// The formatting is done in the overridden ToString
Console.WriteLine(user);
}

Console.WriteLine(“Press any key to exit..”);
Console.Read();
}
}
}

[/csharp]

AdGroupMembers.cs

[csharp]
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Runtime.Serialization;

namespace ActiveDirectoryRecursively
{
/// <summary>
/// Active Directory helper class
/// </summary>
public class AdGroupMembers
{

/// <summary>
/// The company LAD setting, usually companyname.net
/// </summary>
private const string LDAP_DC_COMPANY_DC_COM = “LDAP://DC=YOURCOMPANY,DC=net”;

/// <summary>
/// Gets all users from the Active directory including sub groups.
/// </summary>
/// <param name=”activeDirectoryGroup”>The active directory group to be searched.</param>
/// <exception cref=”ActiveDirectoryException”>Thrown if the group is not found</exception>
/// <returns>List of AdUser’s contained in the group.</returns>
static public IEnumerable<AdUser> LoadAllAcitiveDirectoryUsers(string activeDirectoryGroup)
{
// find group
DirectorySearcher search = new DirectorySearcher(LDAP_DC_COMPANY_DC_COM)
{
Filter = String.Format(“(&(objectCategory=group)(cn={0}))”, activeDirectoryGroup)
};
search.PropertiesToLoad.Add(“distinguishedName”);

SearchResult searchResult = search.FindOne();
if (searchResult == null)
{
throw new ActiveDirectoryException(string.Format(“Active Directory Group {0} was not found”, activeDirectoryGroup));
}

DirectoryEntry group = searchResult.GetDirectoryEntry();
HashSet<AdUser> users = new HashSet<AdUser>();

//Recursive scan the groups for users
RecursivelyCheckGroup(new List<string> {group.Properties[“distinguishedName”].Value.ToString()}, new HashSet<string>(), users);
return users;
}

/// <summary>
/// Recursivelies the check group.
/// </summary>
/// <param name=”activeDirectoryGroup”>The active directory group.</param>
/// <param name=”searchedGroups”>The searched groups.</param>
/// <param name=”groupMembers”>The group members.</param>
private static void RecursivelyCheckGroup(IEnumerable<string> activeDirectoryGroup, HashSet<string> searchedGroups, HashSet<AdUser> groupMembers)
{
foreach (string adGroup in activeDirectoryGroup)
{
if(searchedGroups.Contains(adGroup))
{
continue;
}
searchedGroups.Add(adGroup);

LoadAdUsers(adGroup, groupMembers);
RecursivelyCheckGroup(GetNestedGroups(adGroup), searchedGroups, groupMembers);
}
}

/// <summary>
/// Loads the ad users.
/// </summary>
/// <param name=”activeDirectoryGroup”>The active directory group.</param>
/// <param name=”groupMembers”>The group members.</param>
/// <returns></returns>
private static void LoadAdUsers(string activeDirectoryGroup, HashSet<AdUser> groupMembers)
{
// find all users in this group
DirectorySearcher ds = new DirectorySearcher(LDAP_DC_COMPANY_DC_COM)
{
Filter = String.Format(“(&(memberOf={0})(objectClass=person))”, activeDirectoryGroup)
};

//Load the columns we care about
ds.PropertiesToLoad.Add(“sAMAccountName”);
ds.PropertiesToLoad.Add(“mail”);
ds.PropertiesToLoad.Add(“displayName”);

//Add user to the list
foreach (SearchResult sr in ds.FindAll())
{
string name = sr.Properties[“sAMAccountName”][0].ToString();

string email = string.Empty;
if (sr.Properties.Contains(“mail”))
email = sr.Properties[“mail”][0].ToString();

string displayname = string.Empty;
if (sr.Properties.Contains(“displayName”))
displayname = sr.Properties[“displayName”][0].ToString();

//Add the AdUser if we don’t have them already (used overrided equals)
AdUser adUser = new AdUser(name, displayname, email);
adUser.AdGroups.Add(activeDirectoryGroup);
if (groupMembers.Contains(adUser) == false)
{
groupMembers.Add(adUser);
}
}
}

/// <summary>
/// Gets the nested groups from the active directory.
/// </summary>
/// <param name=”activeDirectoryGroup”>The active directory group to check.</param>
/// <returns>List of the groups in the active directory group</returns>
private static IEnumerable<string> GetNestedGroups(string activeDirectoryGroup)
{
// find all nested groups in this group
DirectorySearcher ds = new DirectorySearcher(LDAP_DC_COMPANY_DC_COM)
{
Filter = String.Format(“(&(memberOf={0})(objectClass=group))”, activeDirectoryGroup)
};
ds.PropertiesToLoad.Add(“distinguishedName”);
return (from SearchResult sr in ds.FindAll() select sr.Properties[“distinguishedName”][0].ToString()).ToList();
}
}

/// <summary>
/// Active Directory Exception
/// </summary>
public class ActiveDirectoryException : Exception
{
public ActiveDirectoryException()
{
}

public ActiveDirectoryException(string message) : base(message)
{
}

public ActiveDirectoryException(string message, Exception innerException) : base(message, innerException)
{
}

protected ActiveDirectoryException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

[/csharp]

ADUser.cs

[csharp]
using System.Collections.Generic;

namespace ActiveDirectoryRecursively
{
public class AdUser
{
#region Properties

/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; private set; }

/// <summary>
/// Gets or sets the display name.
/// </summary>
/// <value>The display name.</value>
public string DisplayName { get; private set; }

/// <summary>
/// Gets or sets the email.
/// </summary>
/// <value>The email.</value>
public string Email { get; private set; }

/// <summary>
/// Gets or sets a value indicating whether this instance is handled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is handled; otherwise, <c>false</c>.
/// </value>
public bool IsHandled { get; set; }

/// <summary>
/// Gets or sets the ad groups this user is in
/// </summary>
/// <value>The ad groups.</value>
public List<string> AdGroups { get; private set; }

#endregion

/// <summary>
/// Initializes a new instance of the <see cref=”AdUser”/> class.
/// </summary>
/// <param name=”name”>The name.</param>
/// <param name=”fullName”>The full name.</param>
/// <param name=”email”>The email.</param>
public AdUser(string name, string fullName, string email)
{
Name = name;
DisplayName = fullName;
Email = email;
IsHandled = false;
AdGroups = new List<string>();
}

/// <summary>
/// Determines whether the specified <see cref=”T:System.Object”/> is equal to the current <see cref=”T:System.Object”/>.
/// </summary>
/// <param name=”obj”>The <see cref=”T:System.Object”/> to compare with the current <see cref=”T:System.Object”/>.</param>
/// <returns>
/// true if the specified <see cref=”T:System.Object”/> is equal to the current <see cref=”T:System.Object”/>; otherwise, false.
/// </returns>
/// <exception cref=”T:System.NullReferenceException”>
/// The <paramref name=”obj”/> parameter is null.
/// </exception>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(AdUser)) return false;
return Equals((AdUser)obj);
}

/// <summary>
/// Determines whether the specified <see cref=”T:System.Object”/> is equal to the current <see cref=”T:System.Object”/>.
/// </summary>
/// <param name=”other”>The <see cref=”T:System.Object”/> to compare with the current <see cref=”T:System.Object”/>.</param>
/// <returns>
/// true if the specified <see cref=”T:System.Object”/> is equal to the current <see cref=”T:System.Object”/>; otherwise, false.
/// </returns>
/// <exception cref=”T:System.NullReferenceException”>
/// The <paramref name=”other”/> parameter is null.
/// </exception>
public bool Equals(AdUser other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;

return Equals(other.Name.ToLowerInvariant(), Name.ToLowerInvariant()); //&& Equals(other.DisplayName, DisplayName);
}

/// <summary>
/// Copies this instance.
/// </summary>
/// <returns></returns>
public AdUser Copy()
{
AdUser newAdUser = new AdUser(Name, DisplayName, Email) { IsHandled = IsHandled};
foreach (string adGroup in AdGroups)
{
newAdUser.AdGroups.Add(adGroup); //strings so we don’t have to worry about getting a reference pointer
}

return newAdUser;
}

/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref=”T:System.Object”/>.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int result = (Name != null ? Name.ToLowerInvariant().GetHashCode() : 0);
return result;
}
}

/// <summary>
/// Returns a <see cref=”System.String”/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref=”System.String”/> that represents this instance.
/// </returns>
public override string ToString()
{
return Name + ” ” + DisplayName + ” (” + Email + “) “;
}
}
}

[/csharp]

You may have noticed that I have overwritten the Equal method, this was to allow the merging of group access abck to the one real ADUser reference.

Cheers

Choco

Have you ever noticed that everyone driving faster than you is a maniac and everyone driving slower are idiots.

Advertisements
Comments
  1. Dedek says:

    nice piece of code! Thanks for sharing :]

  2. Jason says:

    Looks interesting. What class is the Role object based off of though? For example, when you put: public List Roles { get; private set; } what type of object is Role?

    • choco says:

      Hi
      Good spot, in my case we used AD to administrator application security (controlled by project/domain leaders) and this program to update/import the changes. So each domain/project had usually 4 roles (read, write, superuser, admin) For each AD group scanned I would pass the role as well, but for this example I forgot to remove the Role property.

      So I have cleaned up the code (removed the Role property also) and completely refactored it to make it smaller and bit neater.
      If you are using .Net 4.0 you should be able to swap the for loop with a parallel one to gain more performance.

      Cheers
      Choco

  3. Pipes says:

    Hi Choco,

    Seem to be running into trouble. This seems like the perfect example to loop through nested groups and find all users; however, it seems I can only find the immediate child security groups from the root group. After this, I can’t seem to get back any objects from the child groups (let alone user objects):

    var ds = new DirectorySearcher(LdapDcCompanyDcCom) { Filter = String.Format(“(&(memberOf={0})(objectClass=person))”, activeDirectoryGroup) };

    foreach (SearchResult sr in ds.FindAll())… [find all finds nothing]

    Just not quite sure why this is happening. Any thoughts you could give would be appreciated.

    Cheers,

    Pipes

    • choco says:

      Hi

      Not 100% about why your not seeing full listings.
      One thing to think of though is that with AD queries “groups” don’t actually contain any children (they arn’t container objects).
      For each level you have to re-ask the AD for the results. The GetNestGroup function in the recursivelycheckgroup handles the user re-population and the next run of groups to load.

      Also I do remember when looking up the LDAP stuff that the actual LDAP server won’t support certain calls if the server isn’t on the required SP… This was a while back so I would be surprised if your server wasn’t patched by now 🙂
      I tried to find the KB article on this but was unsuccessful, I have changed positions at work now also so I can’t access the code (even though its mine 😦 ) to find the comments with the kb reference. Sorry.

      Anyway good luck, lets us know how it goes.
      Cheers
      Lachlan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s