使用 C# 从通讯组列表中获取电子邮件地址
Get email addresses from distribution list using c#
获取包含交换分发列表的所有个人电子邮件地址的最佳方法是什么?
例如:我有一个名为 abc@domainname.com
的分发列表,其中包含电子邮件地址:
a@domainname.com
b@domainname.com
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) + "<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 += "<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) + "</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;
}
获取包含交换分发列表的所有个人电子邮件地址的最佳方法是什么?
例如:我有一个名为 abc@domainname.com
的分发列表,其中包含电子邮件地址:
a@domainname.com
b@domainname.com
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) + "<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 += "<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) + "</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;
}