Active Directory:获取所有组成员
Active Directory: Get all group members
问题:如何以一致的方式检索所有群组成员?
上下文:我正在检索个人、组、联系人或计算机的所有对象:
Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))"
我现在需要检索组的所有成员。我已经开发了三种方法来做到这一点;但是,他们为同一组返回不同的结果,我不确定为什么。我怀疑它可能是由嵌套组(即组中组)引起的。调试它是一个挑战,因为某些组包含太多成员,以至于调试器超时并且没有显示任何结果。
方法 1 和 2 很慢。方法三很快。所以,我更喜欢使用方法3。
Group Name Retrieval Method Recursive Search Count Members Comment
Group1 AccountManagement TRUE 505
Group1 AccountManagement FALSE 505
Group1 DirectoryServices N/A 101
Group2 AccountManagement TRUE 440 Contains group name 'Group3'
Group2 AccountManagement FALSE 440 Contains group name 'Group3'
Group2 DirectoryServices N/A 100 Contains group name 'Group3'
Group3 AccountManagement TRUE 101 All Group3
Group3 AccountManagement FALSE 2 All Group3
Group3 DirectoryServices N/A 2 1 user 1 group (Group2)
方法一和二:使用S.DS.AM
获取群组成员,其中GetMembers()分别设置为true或false:https://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement(v=vs.110).aspx
private static List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
{
List<Guid> listGroupMemberGuid = null;
GroupPrincipal groupPrincipal = null;
PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
List<Principal> listPrincipalNoNull = null;
PrincipalContext principalContext = null;
ContextType contextType;
IdentityType identityType;
try
{
listGroupMemberGuid = new List<Guid>();
contextType = ContextType.Domain;
principalContext = new PrincipalContext(contextType, strDomainController);
identityType = IdentityType.Guid;
groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);
if (groupPrincipal != null)
{
listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);
listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
foreach (Principal principal in listPrincipalNoNull)
{
listGroupMemberGuid.Add((Guid)principal.Guid);
}
}
return listGroupMemberGuid;
}
catch (MultipleMatchesException)
{
throw new MultipleMatchesException(strPropertyValue);
}
catch (Exception ex)
{
throw ex;
}
finally
{
listGroupMemberGuid = null;
listPrincipalSearchResult.Dispose();
principalContext.Dispose();
groupPrincipal.Dispose();
}
}
方法三:使用S.DS.AD
获取群成员:https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory(v=vs.110).aspx
private static List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
DirectoryEntry directoryEntryGroup;
DirectoryEntry directoryEntryGroupMembers;
DirectorySearcher directorySearcher;
SearchResultCollection searchResultCollection;
DataTypeConverter objConverter = null;
objConverter = new DataTypeConverter();
try
{
directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
directoryEntryGroup.RefreshCache();
}
catch (Exception ex)
{
throw ex;
}
try
{
directorySearcher = new DirectorySearcher(directoryEntryGroup)
{
//Filter = "(objectCategory=group)", // Group
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
};
directorySearcher.PropertiesToLoad.Add("objectGUID");
searchResultCollection = directorySearcher.FindAll();
}
catch (Exception ex)
{
throw ex;
}
try
{
foreach (SearchResult searchResult in searchResultCollection)
{
directoryEntryGroupMembers = searchResult.GetDirectoryEntry();
foreach (object objGroupMember in directoryEntryGroupMembers.Properties["member"])
{
listGroupMemberDn.Add((string)objGroupMember);
}
}
return listGroupMemberDn;
}
catch (Exception ex)
{
throw ex;
}
finally
{
listGroupMemberDn = null;
strPath = null;
directoryEntryGroup.Dispose();
directoryEntryGroupMembers = null;
directorySearcher.Dispose();
searchResultCollection.Dispose();
objConverter = null;
}
}
方法 4:(循环执行 GetNextChunk() 方法)
private static List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
// Variable declaration(s).
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
string strMemberPropertyRange = null;
DirectoryEntry directoryEntryGroup = null;
DirectorySearcher directorySearcher = null;
SearchResultCollection searchResultCollection = null;
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
const int intIncrement = 1500;
// Load the DirectoryEntry.
try
{
// Setup a secure connection with Active Directory (AD) using Kerberos by setting the directoryEntry with AuthenticationTypes.Secure.
directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
// Load the property values for this DirectoryEntry object into the property cache.
directoryEntryGroup.RefreshCache();
}
catch (Exception)
{ }
#region Method1
// Enumerate group members.
try
{
// Check to see if the group has any members.
if (directoryEntryGroup.Properties["member"].Count > 0)
{
int intStart = 0;
while (true)
{
// End of the range.
int intEnd = intStart + intIncrement - 1;
strMemberPropertyRange = string.Format("member;range={0}-{1}", intStart, intEnd);
directorySearcher = new DirectorySearcher(directoryEntryGroup)
{
// Set the Filter criteria that is used to constrain the search within AD.
Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects
// Set the SearchScope for how the AD tree is searched (Default = Subtree).
SearchScope = SearchScope.Base,
// The PageSize value should be set equal to the PageSize that is set by the AD administrator (Default = 0).
PageSize = intActiveDirectoryPageSize,
PropertiesToLoad = { strMemberPropertyRange }
};
try
{
// Populate the searchResultCollection with all records within AD that match the Filter criteria.
searchResultCollection = directorySearcher.FindAll();
foreach (SearchResult searchResult in searchResultCollection)
{
var membersProperties = searchResult.Properties;
var membersPropertyNames = membersProperties.PropertyNames.OfType<string>().Where(n => n.StartsWith("member;"));
foreach (var propertyName in membersPropertyNames)
{
// Get all members from the ranged result.
var members = membersProperties[propertyName];
foreach (string memberDn in members)
{
// Add the member's "distinguishedName" attribute value to the list.
listGroupMemberDn.Add(memberDn);
}
}
}
}
catch (DirectoryServicesCOMException)
{
// When the start of the range exceeds the number of available results, an exception is thrown and we exit the loop.
break;
}
// Increment for the next range.
intStart += intIncrement;
}
}
// return the listGroupMemberDn;
return listGroupMemberDn;
}
#endregion
finally
{
// Cleanup objects.
listGroupMemberDn = null;
strPath = null;
strMemberPropertyRange = null;
directoryEntryGroup.Dispose();
directorySearcher.Dispose();
searchResultCollection.Dispose();
}
}
System.DirectoryServices.AccountManagement
可能更方便,因为它隐藏了 AD 的大部分复杂性,但这也是它变慢的原因。您对正在发生的事情的控制较少。
DirectoryEntry
给你更多的控制权,但你必须处理一些复杂性。
这样就可以解释时差了。
但是您使用 DirectoryEntry
的方法似乎仍然过于复杂。为什么要使用 DirectorySearcher
?它似乎没有添加任何东西。当您设置 directoryEntryGroup
时,您已经拥有该组。之后,您可以访问成员:
foreach (var member in directoryEntryGroup.Properties["member"]) {
//member is a string of the distinguishedName
}
对于非常大的组,请注意默认情况下 AD 将记录限制为 returns 到 1500。因此一旦达到该数字,您将不得不要求更多。你这样做:
directoryEntryGroup.RefreshCache("member;range=1500-*")
然后以同样的方式再次遍历它们。如果你刚好又得到了 1500,那么再要(用 3000 代替 1500),等等,直到你得到所有。
这正是 System.DirectoryServices.AccountManagement
的 .NET Core 实现所做的(我认为 .NET 4.x 也是如此——我只是看不到该代码)。您可以在此处看到用于执行此操作的特殊 class .NET Core 代码(请参阅 GetNextChunk
方法):https://github.com/dotnet/corefx/blob/0eb5e7451028e9374b8bb03972aa945c128193e1/src/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AD/RangeRetriever.cs
作为旁注:
catch (Exception ex)
{
// Something went wrong. Throw an error.
throw ex;
}
如果您打算 re-throw 一个异常而不做任何其他事情,那就不要捕获它。 Re-throwing 具有隐藏异常实际发生位置的效果,因为您的堆栈跟踪现在会说异常发生在 throw ex;
,而不是告诉您异常发生的实际行。
即使是最后一个区块,您也可以使用 try
/finally
而无需 catch
。
问题:如何以一致的方式检索所有群组成员?
上下文:我正在检索个人、组、联系人或计算机的所有对象:
Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))"
我现在需要检索组的所有成员。我已经开发了三种方法来做到这一点;但是,他们为同一组返回不同的结果,我不确定为什么。我怀疑它可能是由嵌套组(即组中组)引起的。调试它是一个挑战,因为某些组包含太多成员,以至于调试器超时并且没有显示任何结果。
方法 1 和 2 很慢。方法三很快。所以,我更喜欢使用方法3。
Group Name Retrieval Method Recursive Search Count Members Comment
Group1 AccountManagement TRUE 505
Group1 AccountManagement FALSE 505
Group1 DirectoryServices N/A 101
Group2 AccountManagement TRUE 440 Contains group name 'Group3'
Group2 AccountManagement FALSE 440 Contains group name 'Group3'
Group2 DirectoryServices N/A 100 Contains group name 'Group3'
Group3 AccountManagement TRUE 101 All Group3
Group3 AccountManagement FALSE 2 All Group3
Group3 DirectoryServices N/A 2 1 user 1 group (Group2)
方法一和二:使用S.DS.AM
获取群组成员,其中GetMembers()分别设置为true或false:https://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement(v=vs.110).aspx
private static List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
{
List<Guid> listGroupMemberGuid = null;
GroupPrincipal groupPrincipal = null;
PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
List<Principal> listPrincipalNoNull = null;
PrincipalContext principalContext = null;
ContextType contextType;
IdentityType identityType;
try
{
listGroupMemberGuid = new List<Guid>();
contextType = ContextType.Domain;
principalContext = new PrincipalContext(contextType, strDomainController);
identityType = IdentityType.Guid;
groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);
if (groupPrincipal != null)
{
listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);
listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
foreach (Principal principal in listPrincipalNoNull)
{
listGroupMemberGuid.Add((Guid)principal.Guid);
}
}
return listGroupMemberGuid;
}
catch (MultipleMatchesException)
{
throw new MultipleMatchesException(strPropertyValue);
}
catch (Exception ex)
{
throw ex;
}
finally
{
listGroupMemberGuid = null;
listPrincipalSearchResult.Dispose();
principalContext.Dispose();
groupPrincipal.Dispose();
}
}
方法三:使用S.DS.AD
获取群成员:https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory(v=vs.110).aspx
private static List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
DirectoryEntry directoryEntryGroup;
DirectoryEntry directoryEntryGroupMembers;
DirectorySearcher directorySearcher;
SearchResultCollection searchResultCollection;
DataTypeConverter objConverter = null;
objConverter = new DataTypeConverter();
try
{
directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
directoryEntryGroup.RefreshCache();
}
catch (Exception ex)
{
throw ex;
}
try
{
directorySearcher = new DirectorySearcher(directoryEntryGroup)
{
//Filter = "(objectCategory=group)", // Group
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
};
directorySearcher.PropertiesToLoad.Add("objectGUID");
searchResultCollection = directorySearcher.FindAll();
}
catch (Exception ex)
{
throw ex;
}
try
{
foreach (SearchResult searchResult in searchResultCollection)
{
directoryEntryGroupMembers = searchResult.GetDirectoryEntry();
foreach (object objGroupMember in directoryEntryGroupMembers.Properties["member"])
{
listGroupMemberDn.Add((string)objGroupMember);
}
}
return listGroupMemberDn;
}
catch (Exception ex)
{
throw ex;
}
finally
{
listGroupMemberDn = null;
strPath = null;
directoryEntryGroup.Dispose();
directoryEntryGroupMembers = null;
directorySearcher.Dispose();
searchResultCollection.Dispose();
objConverter = null;
}
}
方法 4:(循环执行 GetNextChunk() 方法)
private static List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
// Variable declaration(s).
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
string strMemberPropertyRange = null;
DirectoryEntry directoryEntryGroup = null;
DirectorySearcher directorySearcher = null;
SearchResultCollection searchResultCollection = null;
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
const int intIncrement = 1500;
// Load the DirectoryEntry.
try
{
// Setup a secure connection with Active Directory (AD) using Kerberos by setting the directoryEntry with AuthenticationTypes.Secure.
directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
// Load the property values for this DirectoryEntry object into the property cache.
directoryEntryGroup.RefreshCache();
}
catch (Exception)
{ }
#region Method1
// Enumerate group members.
try
{
// Check to see if the group has any members.
if (directoryEntryGroup.Properties["member"].Count > 0)
{
int intStart = 0;
while (true)
{
// End of the range.
int intEnd = intStart + intIncrement - 1;
strMemberPropertyRange = string.Format("member;range={0}-{1}", intStart, intEnd);
directorySearcher = new DirectorySearcher(directoryEntryGroup)
{
// Set the Filter criteria that is used to constrain the search within AD.
Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects
// Set the SearchScope for how the AD tree is searched (Default = Subtree).
SearchScope = SearchScope.Base,
// The PageSize value should be set equal to the PageSize that is set by the AD administrator (Default = 0).
PageSize = intActiveDirectoryPageSize,
PropertiesToLoad = { strMemberPropertyRange }
};
try
{
// Populate the searchResultCollection with all records within AD that match the Filter criteria.
searchResultCollection = directorySearcher.FindAll();
foreach (SearchResult searchResult in searchResultCollection)
{
var membersProperties = searchResult.Properties;
var membersPropertyNames = membersProperties.PropertyNames.OfType<string>().Where(n => n.StartsWith("member;"));
foreach (var propertyName in membersPropertyNames)
{
// Get all members from the ranged result.
var members = membersProperties[propertyName];
foreach (string memberDn in members)
{
// Add the member's "distinguishedName" attribute value to the list.
listGroupMemberDn.Add(memberDn);
}
}
}
}
catch (DirectoryServicesCOMException)
{
// When the start of the range exceeds the number of available results, an exception is thrown and we exit the loop.
break;
}
// Increment for the next range.
intStart += intIncrement;
}
}
// return the listGroupMemberDn;
return listGroupMemberDn;
}
#endregion
finally
{
// Cleanup objects.
listGroupMemberDn = null;
strPath = null;
strMemberPropertyRange = null;
directoryEntryGroup.Dispose();
directorySearcher.Dispose();
searchResultCollection.Dispose();
}
}
System.DirectoryServices.AccountManagement
可能更方便,因为它隐藏了 AD 的大部分复杂性,但这也是它变慢的原因。您对正在发生的事情的控制较少。
DirectoryEntry
给你更多的控制权,但你必须处理一些复杂性。
这样就可以解释时差了。
但是您使用 DirectoryEntry
的方法似乎仍然过于复杂。为什么要使用 DirectorySearcher
?它似乎没有添加任何东西。当您设置 directoryEntryGroup
时,您已经拥有该组。之后,您可以访问成员:
foreach (var member in directoryEntryGroup.Properties["member"]) {
//member is a string of the distinguishedName
}
对于非常大的组,请注意默认情况下 AD 将记录限制为 returns 到 1500。因此一旦达到该数字,您将不得不要求更多。你这样做:
directoryEntryGroup.RefreshCache("member;range=1500-*")
然后以同样的方式再次遍历它们。如果你刚好又得到了 1500,那么再要(用 3000 代替 1500),等等,直到你得到所有。
这正是 System.DirectoryServices.AccountManagement
的 .NET Core 实现所做的(我认为 .NET 4.x 也是如此——我只是看不到该代码)。您可以在此处看到用于执行此操作的特殊 class .NET Core 代码(请参阅 GetNextChunk
方法):https://github.com/dotnet/corefx/blob/0eb5e7451028e9374b8bb03972aa945c128193e1/src/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AD/RangeRetriever.cs
作为旁注:
catch (Exception ex)
{
// Something went wrong. Throw an error.
throw ex;
}
如果您打算 re-throw 一个异常而不做任何其他事情,那就不要捕获它。 Re-throwing 具有隐藏异常实际发生位置的效果,因为您的堆栈跟踪现在会说异常发生在 throw ex;
,而不是告诉您异常发生的实际行。
即使是最后一个区块,您也可以使用 try
/finally
而无需 catch
。