使用 C# 从通讯组列表中获取电子邮件地址

Get email addresses from distribution list using c#

获取包含交换分发列表的所有个人电子邮件地址的最佳方法是什么?

例如:我有一个名为 abc@domainname.com 的分发列表,其中包含电子邮件地址:

  1. a@domainname.com
  2. b@domainname.com
  3. c@domainname.com

现在我需要使用 C# 代码获取这些地址。

我找到了使用 LDAP 的解决方案,但我觉得找出我的 Active Directory 的 LDAP 路径会很麻烦。

// 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 方式:

public static List<string> GetDistributionListMembers(string dlName = "abc@domainname.com")
{
    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;
    }
}

所以我选择了 EWS 路线。

EWS 方式:

public static static List<string> GetDistributionListMembers(string dlName = "abc@domainname.com")
{
    try
    {
        var service = new ExchangeService();
        var cred = new WebCredentials("sharedmailbox@domain.com", "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.
    }
}

EWS 方法好吗?

如果是,那我很好。如果没有,我将不得不找出 LDAP 路径。

或者如果有更好的方法,请告诉我。

更新

对我来说,LDAP 方式 仅适用于在 AD 组中查找电子邮件地址,例如:named ITSolutionDeliveryDevelopers 组。 NOT 在 Exchange 分发列表中,例如:命名为 abc@domainname.com.

// 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", "MyName@mycompany.com", "MyEmailAccountPassword");

我的完整答案,请看下面:

如果您 运行 来自的计算机加入了与您要查找的组相同的域,那么您不需要找出 LDAP 路径。你可以这样做:

var search = new DirectorySearcher();

如果您的计算机没有加入同一个域,那么您只需使用域名:

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

这要求您的计算机和域控制器之间没有防火墙阻止端口 389。如果您需要传递凭据,请执行此操作:

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

凭据可以是域中的任何用户。

就是说,您的代码中存在很多低效之处,这会使它 运行 比需要的慢得多。我写了一篇关于此的文章可以帮助您更新代码:Active Directory: Better Performance

Is EWS approach a good way?

如果有效,那就有效。我不是 EWS 方面的专家(尽管我已经使用过),但我相当确定它使用的是基本身份验证,is going to be disabled in October.

如果所有邮箱都在 Office365 上,那么我建议您使用图表 API 而不是例如 https://docs.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http。在安全方面有几个优势,例如,您可以使用应用程序权限,您只需要访问目录,而如果您在 EWS 中做同样的事情,则需要至少一个邮箱的完全访问权限。

如果终极速度是唯一重要的事情,LDAP 将是 3 个中性能最好的。

我的完整解决方案供以后参考。 :)

EWS 方式 - 用于扩展 Exchange 分发列表

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("sharedmailbox@domain.com", "some_password");
            _exchangeService.Credentials = cred;

            //_exchangeService.AutodiscoverUrl("sharedmailbox@domain.com");
            _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 方式 - 用于扩展 Active Directory 组

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;
}