A few weeks ago, a friend of mine asked a question about using WPF to display Organizational Units in a TreeView control. My friend had a good grasp about getting OU’s from Active Directory. He just needed to be shown the way to bind his results to the TreeView control. So I took the challenge. The first thing I had to do was setup an environment that had multiple OU’s defined. Luckily I had a set of virtual machines with three Domain Controllers with many levels of OU’s. So I fired them up and created a machine to act as a client in the domain. So the client machine consisted of Windows XP with SP3, Visual Studio 2008, SQL Server 2005, Office 2007, etc. Very typical developer machine configuration. So, here is the proof of concept WPF to bind the results of the LDAP query.
using System;
using System.DirectoryServices;
using System.Text;
using System.Windows.Controls;
namespace ActiveDirectory
{
public partial class Window1
{
#region Fields
private static string preferredServer = null;
private static string appAccountId = null;
private static string appAccountPwd = null;
#endregion
#region Constructors
public Window1()
{
this.InitializeComponent();
using (SearchResultCollection src = GetTopOUs())
{
foreach (SearchResult result in src)
{
TreeViewItem item = new TreeViewItem();
if (result.Properties.Contains("Name"))
{
foreach (string name in result.Properties["Name"])
{
item.Tag = name;
item.Header = name;
}
}
OrganizationUnits.Items.Add(item);
this.OrganizationalHiearchy(result.GetDirectoryEntry(), item);
}
}
}
#endregion
#region Methods
/// <summary>
/// This method retrieves the first level of Organizational Units in the Active Directory.
/// </summary>
/// <returns>A SearchResultCollection of the first level oranizational units in the directory.</returns>
public static SearchResultCollection GetTopOUs()
{
string searchFilter = "(objectcategory=organizationalunit)";
SearchResultCollection results = FindObjects(searchFilter, null, SearchScope.OneLevel);
return results;
}
/// <summary>
/// This method retrieves the first level down from the passed in Directory Entry.
/// </summary>
/// <param name="searchRoot">The parent DirectoryEntry object</param>
/// <returns>A SearchResultCollection of the single level organizational units in the directory.</returns>
public static SearchResultCollection GetSingleLevelSubOU(DirectoryEntry searchRoot)
{
string searchFilter = "(objectcategory=organizationalunit)";
SearchResultCollection results = FindObjects(searchFilter, searchRoot, SearchScope.OneLevel);
return results;
}
/// <summary>
/// This method retrieves the all Organizational Units below the seach root in the Active Directory.
/// </summary>
/// <param name="searchRoot">The DirectoryEntry the corresponds to the root of the search.</param>
/// <returns>A SearchResultCollection of oranizational units in the directory.</returns>
public static SearchResultCollection GetSubOUs(DirectoryEntry searchRoot)
{
string searchFilter = "(objectcategory=organizationalunit)";
SearchResultCollection results = FindObjects(searchFilter, searchRoot, SearchScope.Subtree);
return results;
}
/// <summary>
/// This method returns the SearchResultCollection of objects for the given LDAP query filter, initial
/// search root and scope for the search. It will return an empty collection if no match is found.
/// </summary>
/// <param name="filter">The LDAP query filter.</param>
/// <param name="searchRoot">The DirectoryEntry that represent s the root of the search.</param>
/// <param name="scope">The scope for the search (i.e. Base, OneLevel, Subtree)</param>
/// <returns>The collection of SearchReults that matched the query.</returns>
public static SearchResultCollection FindObjects(string filter, DirectoryEntry searchRoot, SearchScope scope)
{
DirectoryEntry entry = null;
DirectorySearcher searcher = null;
if (filter == null)
{
throw new ArgumentNullException("filter", "The search filter cannot be null.");
}
if (searchRoot == null)
{
entry = GetDirectoryEntry();
searcher = new DirectorySearcher(entry);
}
else
{
searcher = new DirectorySearcher(searchRoot);
}
searcher.Filter = filter;
searcher.SearchScope = scope;
SearchResultCollection results = searcher.FindAll();
return results;
}
/// <summary>
/// This method retreives a new directory entry object.
/// </summary>
/// <returns>A DirectoryEntry object.</returns>
public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry entry;
if (preferredServer != null)
{
entry = new DirectoryEntry("LDAP://" + preferredServer, appAccountId, appAccountPwd, AuthenticationTypes.ServerBind | AuthenticationTypes.Secure);
}
else
{
entry = new DirectoryEntry("LDAP://" + GetDomainName(), null, null, AuthenticationTypes.Secure);
}
return entry;
}
/// <summary>
/// This method returns the simple Domain Name of the AD Domain
/// </summary>
/// <returns>The first value of the DC=* domain name from the default naming context.</returns>
public static string GetDomainName()
{
DirectoryEntry rootDse = new DirectoryEntry("LDAP://rootDSE");
//Get the defaultNamingContext and parse in into a properly formatted string
//to use for binding with the global catalog
string ldapDomain = rootDse.Properties["defaultNamingContext"][0].ToString();
rootDse.Close();
rootDse.Dispose();
return ConvertDNToUPNSuffix(ldapDomain);
}
#endregion
#region Helper Methods
/// <summary>
/// Takes a DN-formatted domain name and returns a domai name in UPN-suffix format
/// For example: DC=ads,DC=uscg,DC=mil --> ads.uscg.mil
/// </summary>
/// <param name="domainDN">The raw distinguished name for a domain.</param>
/// <returns>UPN suffix.</returns>
private static string ConvertDNToUPNSuffix(string domainDN)
{
string delimStr = "=,";
char[] delimiter = delimStr.ToCharArray();
string[] split = null;
split = domainDN.Split(delimiter);
StringBuilder buf = new StringBuilder();
foreach (string str in split)
{
if (str.Equals("DC"))
{
continue;
}
else
{
buf.Append(str);
buf.Append(".");
}
}
buf.Remove(buf.Length - 1, 1);
return buf.ToString();
}
private void OrganizationalHiearchy(DirectoryEntry de, TreeViewItem item)
{
using (SearchResultCollection children = GetSingleLevelSubOU(de))
{
foreach (SearchResult child in children)
{
if (child.Properties.Contains("Name"))
{
foreach (string name in child.Properties["Name"])
{
TreeViewItem tvChild = new TreeViewItem();
tvChild.Tag = name;
tvChild.Header = name;
item.Items.Add(tvChild);
OrganizationalHiearchy(child.GetDirectoryEntry(), tvChild);
}
}
}
}
}
#endregion
}
}
I know the code above is hard to read. Just copy and paste it into Visual Studio. It uses a recursive method to put the elements in the right order. So how it works is as follows. The constructor puts the first level OU from the RootDSE in the treeview. Then for each of the OU's returned, it goes into a recursive method that drills down until there are no children OU's left. Then it comes up and continues to the next. Here is what the final product will look like.