Ash K
Ash K

Reputation: 3631

Get email addresses from distribution list using c#

Update:

For me, LDAP way only worked for finding email addresses inside AD groups, for eg: named ITSolutionDeliveryDevelopers group. NOT inside Exchange Distribution Lists, for eg: named [email protected].

// I was able to figure out entry as suggested by @Gabriel Luci and
// all of the following possible formats worked for me:
// ngroupnet.com is my company domain name.
var entry = new DirectoryEntry();
var entry = new DirectoryEntry("LDAP://ngroupnet.com");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "MyAccountUsername", "MyAccountPassword");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "[email protected]", "MyEmailAccountPassword");

For my complete answer, take a look below: https://stackoverflow.com/a/71518937/8644294


Original Question:

What is the best way to get all the individual email addresses comprising an exchange distribution list?

For eg: I have this distribution list called [email protected] that has email addresses:

  1. [email protected]
  2. [email protected]
  3. [email protected]

Now I need to get these addresses using C# code.

I found solution using LDAP but I felt it'd be a hassle to figure out LDAP path to my Active Directory.

// How do I get this LDAP Path, username and password?  
// Is the username and password service account credentials of the app?  
// And do they need to be registered in AD?  
var entry = new DirectoryEntry("LDAP Path");//, username, password);

LDAP Way:

public static List<string> GetDistributionListMembers(string dlName = "[email protected]")
{
    var result = new List<string>();
    try
    {
        // How do I get this LDAP Path?
        var entry = new DirectoryEntry("LDAP Path");//, username, password);
        var search = new DirectorySearcher(entry);
        search.Filter = $"CN={dlName}";
        int i = search.Filter.Length;

        string str = "", str1 = "";
        foreach (SearchResult AdObj in search.FindAll())
        {
            foreach (String objName in AdObj.GetDirectoryEntry().Properties["member"])
            {
                str += Convert.ToString(objName) + "&lt;Br>";
                int selIndex = objName.IndexOf("CN=") + 3;
                int selEnd = objName.IndexOf(",OU") - 3;
                str1 += objName.Substring(selIndex, selEnd).Replace("\\", "");

                DirectorySearcher dsSearch = new DirectorySearcher(entry);
                dsSearch.Filter = "CN=" + objName.Substring(selIndex, selEnd).Replace("\\", "");
                foreach (SearchResult rs in dsSearch.FindAll())
                {
                    //str1 += "&lt;p align='right'><font face='calibri' color='#2266aa' size=2>" + Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["displayName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["sAMAccountName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["department"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["memberOf"].Value) + "&lt;/font></p>";
                    str1 = Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value);
                    result.Add(str1);
                }
            }

        }
        return result;
    }
    catch (Exception ex)
    {
        //Do some logging or what have you.
        throw;
    }
}

So I just went with the EWS route.

EWS Way:

public static static List<string> GetDistributionListMembers(string dlName = "[email protected]")
{
    try
    {
        var service = new ExchangeService();
        var cred = new WebCredentials("[email protected]", "some_password");
        service.Credentials = cred;
        service.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
        service.TraceEnabled = true;
        service.TraceFlags = TraceFlags.All;

        var expandedEmailAddresses = new List<string>();

        ExpandGroupResults myGroupMembers = service.ExpandGroup(dlName);

        foreach (EmailAddress address in myGroupMembers.Members)
        {
            expandedEmailAddresses.Add(address.Address);
        }

       return expandedEmailAddresses;
    }
    catch (Exception ex)
    {
        // The DL doesn't have any members. Handle it how you want.
        // Handle/ Log other errors.
    }
}

Is EWS approach a good way?

If Yes, then I'm good. If not, I'll have to figure out that LDAP path.

Or if there's even a better way, please let me know.

Upvotes: 4

Views: 3475

Answers (3)

Ash K
Ash K

Reputation: 3631

My complete solution for future reference. :)

EWS Way - For expanding Exchange Distribution Lists

public class SomeHelper
{
    private static ExchangeService _exchangeService = null;
    
    public static async Task<HashSet<string>> GetExchangeDistributionListMembersAsync(IEnumerable<string> dlNames)
    {
        var allEmailAddresses = new HashSet<string>();

        foreach (var dlName in dlNames)
        {
            if (!SomeCache.TryGetCachedItem(dlName, out var dlMembers))
            {
                var groupEmailAddresses = new List<string>();
                var exchangeService = await GetExchangeServiceAsync();

                try
                {
                    var myGroupMembers = exchangeService.ExpandGroup(dlName);
                    // Add the group members.
                    foreach (var address in myGroupMembers.Members)
                    {
                        groupEmailAddresses.Add(address.Address);
                    }
                }
                catch (Exception ex)
                {
                    //If it can't expand the dlName, just return it.
                    groupEmailAddresses.Add(dlName);
                    //groupEmailAddresses.Add($"Attempting to expand '{dlName}' resulted in error message: '{ex.Message}'.");
                }

                // Cache the groupEmailAddresses for 7 days.
                // Because Distribution Lists rarely change and expanding DL is an expensive operation.- AshishK Notes
                SomeCache.AddItemToCache(dlName, groupEmailAddresses, 10080);
                allEmailAddresses.UnionWith(groupEmailAddresses);
            }
            else
            {
                allEmailAddresses.UnionWith((List<string>)dlMembers);
            }
        }

        return allEmailAddresses;
    }
    
    private static async Task<ExchangeService> GetExchangeServiceAsync()
    {
        if (_exchangeService == null)
        {
            _exchangeService = new ExchangeService();
            var exchangeUrl = "https://outlook.office365.com/ews/exchange.asmx";
            var cred = new WebCredentials("[email protected]", "some_password");
            _exchangeService.Credentials = cred;

            //_exchangeService.AutodiscoverUrl("[email protected]");
            _exchangeService.Url = new Uri(exchangeUrl);

            _exchangeService.TraceEnabled = true;
            _exchangeService.TraceFlags = TraceFlags.All;

            return _exchangeService;
        }
        else
        {
            return _exchangeService;
        }
    }
}

public class SomeCache
{
    private static readonly ObjectCache _cache = MemoryCache.Default;

    public static void AddItemToCache(string key, object itemToAdd, int cacheDurationMinutes)
    {
        var _policy = new CacheItemPolicy
        {
            Priority = CacheItemPriority.Default,
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(cacheDurationMinutes)
        };

        _cache.Set(key, itemToAdd, _policy);
    }

    public static bool TryGetCachedItem(string key, out object cachedObject)
    {
        try
        {
            cachedObject = _cache[key] as object;
        }
        catch (Exception ex)
        {
            cachedObject = null;
        }
        return !(cachedObject == null);
    }
}

LDAP Way - For expanding Active Directory Groups

public static List<string> GetADGroupDistributionListMembers(string adGroupName)
{
    var returnResult = new List<string>();
    var entry = GetDirectoryEntry();
    DirectorySearcher groupSearch = new DirectorySearcher(entry)
    {
        Filter = "(SAMAccountName=" + adGroupName + ")"
    };
    groupSearch.PropertiesToLoad.Add("member");
    SearchResult groupResult = groupSearch.FindOne(); // getting members who belong to the adGroupName
    if (groupResult != null)
    {
        for (int iSearchLoop = 0; iSearchLoop < groupResult.Properties["member"].Count; iSearchLoop++)
        {
            string userName = groupResult.Properties["member"][iSearchLoop].ToString();
            int index = userName.IndexOf(',');
            userName = userName.Substring(0, index).Replace("CN=", "").ToString(); // the name of the user will be fetched.

            DirectorySearcher search = new DirectorySearcher(entry)
            {
                Filter = "(name=" + userName + ")"
            };
            search.PropertiesToLoad.Add("mail");
            SearchResult result = search.FindOne(); //finding the mail id
            if (result != null)
            {
                returnResult.Add(result.Properties["mail"][0].ToString());
            }
        }
    }

    return returnResult;
}

public static DirectoryEntry GetDirectoryEntry()
{
    DirectoryEntry entryRoot = new DirectoryEntry("LDAP://RootDSE");
    string Domain = (string)entryRoot.Properties["defaultNamingContext"][0];
    DirectoryEntry de = new DirectoryEntry
    {
        Path = "LDAP://" + Domain,
        AuthenticationType = AuthenticationTypes.Secure
    };

    return de;
}

Upvotes: 0

Glen Scales
Glen Scales

Reputation: 22032

If all the Mailboxes are on Office365 then i would suggest you use the Graph API instead eg https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http . There are several advantage in terms of security eg you could use Application permissions and all you need is access to the directory while if you did the same thing in EWS it would require full access to at least one mailbox.

LDAP will be best performing of the 3 if ultimate speed in the only thing that is important.

Upvotes: 1

Gabriel Luci
Gabriel Luci

Reputation: 40858

If the computer you run this from is joined to the same domain as the group you're looking for, then you don't need to figure out the LDAP path. You can just do:

var search = new DirectorySearcher();

If your computer is not joined to the same domain, then you just use the domain name:

var entry = new DirectoryEntry("LDAP://domainname.com");

This requires that there is no firewall blocking port 389 between your computer and the domain controller(s). If you need to pass credentials, then do that:

var entry = new DirectoryEntry("LDAP://domainname.com", username, password);

The credentials can be any user on the domain.

That said, there are a lot of inefficiencies in your code that will make it run much slower than needed. I wrote an article about this that can help you update your code: Active Directory: Better Performance

Is EWS approach a good way?

If it works, it works. I'm not an expert on EWS (although I have used it), but I'm fairly certain that's using Basic Authentication, which is going to be disabled in October.

Upvotes: 2

Related Questions