这是构造AD组织结构(Organizational Unit,以下称为OU)的树形结构的另一个方法。之前的方法请参见:递归构造AD组织结构(Organizational Unit)的树形结构
需要引用两个dll:
using System.DirectoryServices; using System.DirectoryServices.ActiveDirectory;
定义一个树节点类:
public class OuTreeNode { public string Name { get; set; } public string Id { get; set; } private List<OuTreeNode> _children = new List<OuTreeNode>(); public List<OuTreeNode> Children { get { return _children; } set { _children = value; } } }这个类的名字是OuTreeNode,其中保存OU节点的基本信息,这里定义了OU的name,id,和子节点的列表。
与之前递归查找OU的方法不同,这个方法使用查询,一次查询出所有的OU,然后遍历这些OU就可以构造出一棵组织结构树。基本的思路是,利用OU对象的distinguishedName构造出一个OU相对于根节点的相对路径,这个路径正好可以作为一个xml表的xpath,根据这个xpath,就可以构造一个xml,最后通过xml表递归构造OU树。这是对xml查询的反向应用,一般都是给定一个xml表,使用xpath查询其中的节点,这次是使用xpath,构造一个xml表。
首先我们需要一个方法,将一个OU的distinguishedName转换成相对路径:
private static string FormatDNToRelativePath(string rawDn) { StringBuilder path = new StringBuilder(); rawDn = rawDn.Replace(" ","_").Replace("&","_"); string[] dnParts = rawDn.Split(new[] { ',' }); foreach (string part in dnParts) { if (part.StartsWith("OU",StringComparison.OrdinalIgnoreCase)) { string name = XmlConvert.EncodeName((part.Split(new char[] {'='})[1])); path.Insert(0,string.Format("/{0}",name)); } } string domainName = string.Empty; using (Domain root = Domain.GetCurrentDomain()) { domainName = root.Name; } path.Insert(0,string.Format("{0}",domainName)); return path.ToString(); }这个方法可以将形如“OU=Office1,OU=Dev Department,DC=Test,DC=com”的转换为Test.Com\Dev Department\Office1这样的相对路径,借助于xml表来构造OU树,会有字符转义的问题,在AD中OU的名字可以使用任意的字符,但是xml不行,因此要对OU的名字进行转义,这里使用XmlConvert.EncodeName方法来做到这一点,之后在构造树的时候,再转义回来。
我们需要获取OU的信息,来展示给最终用户,这里作为示例,只获取OU的name和id,其中name也需要转义,因为name会作为xml的一个属性保存:
private static string GetName(DirectoryEntry entry) { return XmlConvert.EncodeLocalName(entry.Properties["name"].Value.ToString()); } private static string GetId(DirectoryEntry entry) { byte[] bytes = entry.Properties["ObjectGuid"].Value as byte[]; Guid id = new Guid(bytes); return id.ToString(); }然后就是获得每个OU的相对路径,以及这个OU的父节点的相对路径,这个路径可以看成是xpath,用来将OU节点添加到xml中的正确位置:
private static string GetPath(DirectoryEntry entry) { string xPath = string.Format("/{0}",FormatDNToRelativePath(entry.Properties["distinguishedName"].Value.ToString())); return xPath; } private static string GetParentPath(DirectoryEntry entry,string name) { string path = FormatDNToRelativePath(entry.Properties["distinguishedName"].Value.ToString()); if (path.LastIndexOf(name) != -1) { path = path.Remove(path.LastIndexOf(name)); } string parentPath = string.Format("/{0}",path); parentPath = parentPath.Substring(0,parentPath.LastIndexOf('/')); return parentPath; }有个这些方法,就可以开始构造OU树了,下面是代码,具体的步骤详见注释:
public static OuTreeNode GetOuTree() { XmlDocument xmlDoc = new XmlDocument(); //声明一个XmlDocument XmlElement rootElement = null; string domainName = string.Empty; using (Domain domain = Domain.GetCurrentDomain()) { rootElement = xmlDoc.CreateElement(domain.Name); xmlDoc.AppendChild(rootElement); //将domain节点作为根节点 using (DirectorySearcher ds = new DirectorySearcher(domain.GetDirectoryEntry(),"(objectClass=organizationalUnit)",null,SearchScope.Subtree)) { SearchResultCollection src = ds.FindAll(); //使用查询,一次性获取所有的OU foreach (SearchResult result in src) //遍历OU { using (DirectoryEntry ouEntry = result.GetDirectoryEntry()) { try { string nodeName = GetName(ouEntry); //获得OU的name string nodeId = GetId(ouEntry); //获得OU的guid string xPathParent = GetParentPath(ouEntry,nodeName); //获取OU的父节点的相对路径 string xPath = GetPath(ouEntry); //获取OU的相对路径 if (xmlDoc.SelectSingleNode(xPath) == null) //判断当前的OU,是否在xml中存在,如果不存在就创建一个节点 { XmlElement newElement = xmlDoc.CreateElement(nodeName); //创建OU节点 newElement.SetAttribute("name",nodeName); //将name作为一个属性保存 newElement.SetAttribute("guid",nodeId); //将gud作为一个属性保存 XmlNode parent = xmlDoc.SelectSingleNode(xPathParent); //查找父节点是否存在,如果存在就将当前的OU节点append上去 if (parent != null) parent.AppendChild(newElement); } } catch { } } } } } OuTreeNode tree = BuildOuTree(xmlDoc); //根据xml文件构建OU树 return tree; }根据之前构造的xml文档,递归构造一棵树:
private static OuTreeNode BuildOuTree(XmlDocument tempXml) { OuTreeNode root = new OuTreeNode(); using (Domain domain = Domain.GetCurrentDomain()) { root.Name = domain.Name; } GetOUTreeNode(root,tempXml.FirstChild); return root; } private static void GetOUTreeNode(OuTreeNode root,XmlNode tempXml) { foreach (XmlNode xmlNode in tempXml.ChildNodes) { OuTreeNode childNode = new OuTreeNode() { Name = XmlConvert.DecodeName(xmlNode.Attributes["name"].Value),Id = xmlNode.Attributes["guid"].Value }; //将转义的name属性转义回来。 root.Children.Add(childNode); GetOUTreeNode(childNode,xmlNode); } }使用这种方法的好处就是一次获取所有的OU,减少访问AD的次数,适用于访问AD较慢的情况,但是这个方法是有一个问题的,就是会丢OU,因为在构建xml的时候,有可能父节点还没有创建出来,就处理子节点了,为了解决这个问题,可以将查询出来的OU列表,先做一下排序,根据“canonicalName”属性排序就可以确保父节点一定会在子节点添加之前就存在了。