Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

How to Get a List of Active Directory Members in a Distribution List with C#?

Posted on 2009-02-11
14
Medium Priority
?
5,718 Views
Last Modified: 2013-12-24
I have been searching through EE and the internet looking for a way to get a list of active directory members based on user input of the specific distribution list.  I've tried many different  combinations; however, all I get is a -532xxxxxxx COM error or 1000+ results (the test distribution list has 1 member in it).  I have attached the code I have so far.

NOTE: I have blacked out the AD location and I understand I am not filtering on anything but I apparently cannot get the LDAP query right.  I am new to LDAP.
Program.cs
--------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
 
namespace ExactTarget_ADFTP
{
    class Program
    {
        static string GetProperty(SearchResult searchResult, string PropertyName)
        {
            if(searchResult.Properties.Contains(PropertyName))
            {
                return searchResult.Properties[PropertyName][0].ToString() ;
            }
            else
            {
                return string.Empty;
            }
        }
 
        static void Main(string[] args)
        {
            XXXXX.BusinessLogic.ExactTarget_ADFTP.FetchAD oLDAP = new XXXXX.BusinessLogic.ExactTarget_ADFTP.FetchAD();
 
            int iCount = 0;
            Console.Write("What is the distribution list name? ");
            string input = Console.ReadLine();
 
            Console.WriteLine("");
            Console.WriteLine(String.Concat("Fetching Information for ", input, "..."));
 
            SearchResultCollection LDAPResultCollection = oLDAP.GetUserInfo(input);
 
            try
            {
                foreach (SearchResult LDAPResult in LDAPResultCollection)
                {
                    iCount += 1;
                    Console.WriteLine(GetProperty(LDAPResult, "cn")); // 
                    Console.WriteLine(GetProperty(LDAPResult, "givenName")); // First
                    Console.WriteLine(GetProperty(LDAPResult, "sn")); // Last
                    Console.WriteLine(GetProperty(LDAPResult, "mail")); // Email Address
                    Console.WriteLine(GetProperty(LDAPResult, "DistinguishedName")); // Group?
                }
            }
            catch (System.Exception)
            {
            }
 
            Console.Write("Program Ended, Count: " + iCount);
            input = Console.ReadLine();
        }
}
 
FetchAD.cs
-----------------------
#region "Imports"
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.Data;
#endregion
 
namespace XXXXX.BusinessLogic.ExactTarget_ADFTP
{
    public class FetchAD
    {
        protected String SPLDAPADDRESS = "xxxxxx.xxxxxx.net";
 
        public SearchResultCollection GetUserInfo(string Distribution)
        {
            try
            {
                string LDAPAddress = String.Format("LDAP://{0}", SPLAPADDRESS);
                DirectoryEntry deLDAP = new DirectoryEntry(LDAPAddress);
                deLDAP.AuthenticationType = AuthenticationTypes.Delegation;
                
                DirectorySearcher mySearcher = new DirectorySearcher(deLDAP, "(objectClass=person)");
 
                return mySearcher.FindAll();
            }
            catch (System.Exception)
            {
                return null;
            }
        }
    }
}

Open in new window

0
Comment
Question by:atomicstorm
14 Comments
 
LVL 26

Expert Comment

by:Anurag Thakur
ID: 23619588
the following ee link shows how to export it to an excel
may be you can find how to export to List
http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_21311589.html
0
 
LVL 71

Accepted Solution

by:
Chris Dent earned 1500 total points
ID: 23620555

You have two parts to this...

1. Convert the Group Name provided by the user to a Distinguished Name. When you consider this, you must also consider whether or not the group name you're given is accurate and unique. This is required because you cannot construct a filter based on just the group name, and returning all users then searching membership afterwards is excessively wasteful.

2. Return the members of the group. Either by enumerating the "member" attribute or by searching the directory for users that are a "memberOf" the distinguishedName returned above.

I'm not a programmer, so I'll stay clear of modifying the code you have above. However, I do have some samples of what I'm suggesting, those are below. There are two separate subroutines dropped here, it won't just copy and paste then work but do let me know if any of what I've done doesn't make sense.

Chris
// LDAP filter to find objects where sAMAccountName or name matches the value specified.
// txtObject is a field on the form, read as input.
String ldapFilter = "(&(|(sAMAccountName=" + txtObject.Text + ")(name=" + txtObject.Text + "))" +
  "(|(&(objectClass=user)(objectCategory=person))(objectClass=group)))";
 
// Connection to the current AD domain. Only works if we're running this on a domain member
DirectoryEntry adDomain = new DirectoryEntry();
DirectorySearcher adSearch = new DirectorySearcher(adDomain, ldapFilter);
adSearch.PageSize = 1000;
adSearch.PropertiesToLoad.Add("distinguishedName");
 
// Find a single result, hopefully this is unique. Demonstrative only.
SearchResult adSearchResult = adSearch.FindOne();
 
lblError.Text = "";
try {
  txtObject.Text = adSearchResult.Properties["distinguishedname"][0].ToString();
} catch {
  lblError.Text = "Failed to find account in directory";
}
 
// Enumeration of members by searching memberOf attribute
 
// LDAP filter to return all objects where memberOf contains objectDN (a specific group)
String ldapFilter = "(memberOf=" + objectDN + ")";
 
DirectoryEntry adDomain = new DirectoryEntry();
DirectorySearcher adSearch = new DirectorySearcher(adDomain, ldapFilter);
adSearch.PageSize = 1000;
adSearch.PropertiesToLoad.AddRange(new String[] {"name", "distinguishedName"});
SearchResultCollection adSearchResults = adSearch.FindAll();
 
// ADObjectData is a struct (not defined here). 
// ADSearchData is a List (result is bound to a DataGridView)
foreach (SearchResult adSearchResult in adSearchResults) {
  ADObjectData ADObject = new ADObjectData();
  ADObject.name = adSearchResult.Properties["name"][0].ToString();
  ADObject.dn = adSearchResult.Properties["distinguishedname"][0].ToString();
  ADSearchData.Add(ADObject);
}

Open in new window

0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23783288
Was just cruising by and saw this question.  I realise this isn't a direct answer to your question but it does work and does list all users in a requested Active Directory - it might give you a good place to start.

If it helps please post a comment on the article - it's my site.  :)

http://digitalformula.net/net-geekery/follow-up-list-active-directory-users-this-time-in-c/
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 71

Expert Comment

by:Chris Dent
ID: 23783407

Hey Number5ix :)

I have a few comments if I may, hopefully you don't mind :)

> DirectoryEntry de = resEnt.GetDirectoryEntry();

If you're just listing members you do not need to create a directory entry for each user. It is more efficient (costs less) to retrieve the value from the SearchResult, especially important if we're looking at large result sets.

> "(objectClass=user)"

Because of how Classes are defined this filter will return Computer objects as well (if they appear within the specified sub tree). "(&(objectClass=user)(objectCategory=person))" will return just users, where just "(objectCategory=person)" will return users and contact objects.

Paging: You'll need it enabled if you expect to return more than 1000 results in the query.

Returned attributes, again only applies for large(r) result sets, but it is generally sensible to restrict the Properties loaded into the SearchResult to only those that are actually necessary.

Chris
0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23790272
Nope I don't mind at all - they're good tips.  Thanks!  :)
0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23790626
@Chris-Dent:

The objectCategory=person thing works well.  Your comment about not needing to create a DirectoryEntry is interesting though.  On looking at my code again, how would you do it?  Once you've got the SearchResult objects from mySearcher.FindAll() how do you iterate through each of them and get the properties without creating a DirectoryEntry

I've tried the code snippet below but it's WAY slower than my original version ...
// Repeating this line for each property:
 
string emailAddress = resEnt.GetDirectoryEntry().Properties["Mail"].Value.ToString();
 
// is way slower than doing this for each object in mySearcher.FindAll():
 
DirectoryEntry de = resent.GetDirectoryEntry();
string emailAddress = de.Properties["Mail"].Value.ToString();
string givenName = de.Properties["givenName"].Value.ToString();

Open in new window

0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 23792909

You're still getting the Directory Entry though, that's the bit that's slow :)

Instead, we can pull the values directly from resEnt (as below).

The properties loaded into the result can be explicitly set with:

mySearcher.PropertiesToLoad.Add("attributename");

Or:

mySearcher.PropertiesToLoad.AddRange(new String[] {"mail", "givenname"});

The idea being to reduce the cost of the search by returning only attributes we're interested in seeing.

Chris
string emailAddress = resEnt.Properties["mail"][0].ToString();
givenName = resEnt.Properties["givenname"][0].ToString();

Open in new window

0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 23793529

Just noticed I missed the variable type for the second command, but I'm sure you'll catch that anyway :)

Chris
0
 

Author Closing Comment

by:atomicstorm
ID: 31545784
I did something completely different but your solution guided me in the right direction.
0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23800309
Very handy, thanks Chris.  It's nothing to do with what you wrote but it's interesting how the same code returns a different number of users from AD if converted to VB.NET ...
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 23803742

Really? It shouldn't make any difference if both are just using .NET classes, I normally test in both languages.

Chris
0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23804448
Yeah, it surprised me too.  C# returns 439 users in the AD I'm testing on but VB only returns 200 or so.
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 23804466

Hmmm I can't provide an answer for that, I've never noticed a difference when I've been testing things. At least not unless I go and make a mistake :)

Almost tempted to get you to try it in PowerShell as well, that can use the .NET framework as well :)

Chris
0
 
LVL 3

Expert Comment

by:Number5ix
ID: 23812217
Hehe yep, it's probably something I've done but I haven't bothered trying to fix it yet.  PowerShell rocks!  I do a ton of it here at work although to be honest I'm battling with loading an external DLL into PowerShell through reflection at the moment lol
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this blog post, we’ll look at how using thread_statistics can cause high memory usage.
Creating a Cordova application which allow user to save to/load from his Dropbox account the application database.
Video by: Steve
Using examples as well as descriptions, step through each of the common simple join types, explaining differences in syntax, differences in expected outputs and showing how the queries run along with the actual outputs based upon a simple set of dem…
In this video, Percona Solution Engineer Dimitri Vanoverbeke discusses why you want to use at least three nodes in a database cluster. To discuss how Percona Consulting can help with your design and architecture needs for your database and infras…
Suggested Courses

810 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question