Querying Active Directory Using Principal Extensions in System.DirectoryServices.AccountManagement
I recently had a project where I needed to query the extensionAttribute1 – extensionAttribute15 attributes for users from Active Directory. I’ve already written about how to query Active Directory using System.DirectoryServices.DirectoryEntry. In that post, a reader named Marc suggested I look at the new System.DirectoryServices.AccountManagement namespace, so I decided to challenge myself and create a solution using this newer namespace.
For those not familiar, when you install Exchange it adds new attributes to your forest to the Person class named “extensionAttribute1” through “extensionAttribute15”. You can see that in the following screen shot from my test domain.
I needed to programmatically query this data. The first step was reading the code examples in MSDN called “Principal Extensions”, which gives an example of querying attributes in the inetOrgPerson class. I tried this, and did not return data for extensionAttribute1-15, so I decided to code my own. The code is almost verbatim to the example in the MSDN documentation, but I will point out a few differences.
To begin with, I highly suggest you start by reading About System.Security.AccountManagement as it is a great overview of the capabilities. For instance, you can use the same code to query ADLDS, the machine SAM, or Active Directory, and the namespace takes care of the differences for you. From there, there are some fantastic resources on this at Using System.DirectoryServices.AccountManagement. If you are interested in learning more about the capabilities, take a look at the example.
First, I changed the class attribute DirectoryObjectClassAttribute to have a value of “person” rather than “inetOrgPerson” as in the documentation. The FindByIdentity method overloads are largely the same as they were in the documentation, in fact I removed the advanced filter because I didn’t need it in my scenario. The code is pretty straightforward, then, I am just setting up properties that map to the attributes so it is easier for someone using my code to leverage the library later.
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("person")]
public class Person : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public Person(PrincipalContext context)
: base(context)
{
}
// Implement the constructor with initialization parameters.
public Person(PrincipalContext context,
string samAccountName,
string password,
bool enabled)
: base(context,
samAccountName,
password,
enabled)
{
}
[DirectoryProperty("extensionAttribute1")]
public string ExtensionAttribute1
{
get
{
if (ExtensionGet("extensionAttribute1").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute1")[0];
}
set
{
ExtensionSet("extensionAttribute1", value);
}
}
[DirectoryProperty("extensionAttribute2")]
public string ExtensionAttribute2
{
get
{
if (ExtensionGet("extensionAttribute2").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute2")[0];
}
set
{
ExtensionSet("extensionAttribute2", value);
}
}
[DirectoryProperty("extensionAttribute3")]
public string ExtensionAttribute3
{
get
{
if (ExtensionGet("extensionAttribute3").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute3")[0];
}
set
{
ExtensionSet("extensionAttribute3", value);
}
}
[DirectoryProperty("extensionAttribute4")]
public string ExtensionAttribute4
{
get
{
if (ExtensionGet("extensionAttribute4").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute4")[0];
}
set
{
ExtensionSet("extensionAttribute4", value);
}
}
[DirectoryProperty("extensionAttribute5")]
public string ExtensionAttribute5
{
get
{
if (ExtensionGet("extensionAttribute5").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute5")[0];
}
set
{
ExtensionSet("extensionAttribute5", value);
}
}
[DirectoryProperty("extensionAttribute6")]
public string ExtensionAttribute6
{
get
{
if (ExtensionGet("extensionAttribute6").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute6")[0];
}
set
{
ExtensionSet("extensionAttribute6", value);
}
}
[DirectoryProperty("extensionAttribute7")]
public string ExtensionAttribute7
{
get
{
if (ExtensionGet("extensionAttribute7").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute7")[0];
}
set
{
ExtensionSet("extensionAttribute7", value);
}
}
[DirectoryProperty("extensionAttribute8")]
public string ExtensionAttribute8
{
get
{
if (ExtensionGet("extensionAttribute8").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute8")[0];
}
set
{
ExtensionSet("extensionAttribute8", value);
}
}
[DirectoryProperty("extensionAttribute9")]
public string ExtensionAttribute9
{
get
{
if (ExtensionGet("extensionAttribute9").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute9")[0];
}
set
{
ExtensionSet("extensionAttribute9", value);
}
}
[DirectoryProperty("extensionAttribute1")]
public string ExtensionAttribute10
{
get
{
if (ExtensionGet("extensionAttribute10").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute10")[0];
}
set
{
ExtensionSet("extensionAttribute10", value);
}
}
[DirectoryProperty("extensionAttribute11")]
public string ExtensionAttribute11
{
get
{
if (ExtensionGet("extensionAttribute11").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute11")[0];
}
set
{
ExtensionSet("extensionAttribute11", value);
}
}
[DirectoryProperty("extensionAttribute12")]
public string ExtensionAttribute12
{
get
{
if (ExtensionGet("extensionAttribute12").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute12")[0];
}
set
{
ExtensionSet("extensionAttribute12", value);
}
}
[DirectoryProperty("extensionAttribute13")]
public string ExtensionAttribute13
{
get
{
if (ExtensionGet("extensionAttribute13").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute13")[0];
}
set
{
ExtensionSet("extensionAttribute13", value);
}
}
[DirectoryProperty("extensionAttribute14")]
public string ExtensionAttribute14
{
get
{
if (ExtensionGet("extensionAttribute14").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute14")[0];
}
set
{
ExtensionSet("extensionAttribute14", value);
}
}
[DirectoryProperty("extensionAttribute15")]
public string ExtensionAttribute15
{
get
{
if (ExtensionGet("extensionAttribute15").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute15")[0];
}
set
{
ExtensionSet("extensionAttribute15", value);
}
}
// Implement the overloaded search method FindByIdentity.
public static new Person FindByIdentity(PrincipalContext context,
string identityValue)
{
return (Person)FindByIdentityWithType(context,
typeof(Person),
identityValue);
}
// Implement the overloaded search method FindByIdentity.
public static new Person FindByIdentity(PrincipalContext context,
IdentityType identityType,
string identityValue)
{
return (Person)FindByIdentityWithType(context,
typeof(Person),
identityType,
identityValue);
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("DistinguishedName:{0}", DistinguishedName);
sb.AppendLine();
sb.AppendFormat("DisplayName:{0}", DisplayName);
sb.AppendLine();
sb.AppendFormat("EmailAddress:{0}", EmailAddress);
sb.AppendLine();
sb.AppendFormat("UserPrincipalName:{0}", UserPrincipalName);
sb.AppendLine();
sb.AppendFormat("Name:{0}", Name);
sb.AppendLine();
sb.AppendFormat("GivenName:{0}", GivenName);
sb.AppendLine();
sb.AppendFormat("SamAccountName:{0}", SamAccountName);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute1:{0}", ExtensionAttribute1);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute2:{0}", ExtensionAttribute2);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute3:{0}", ExtensionAttribute3);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute4:{0}", ExtensionAttribute4);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute5:{0}", ExtensionAttribute5);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute6:{0}", ExtensionAttribute6);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute7:{0}", ExtensionAttribute7);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute8:{0}", ExtensionAttribute8);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute9:{0}", ExtensionAttribute9);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute10:{0}", ExtensionAttribute10);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute11:{0}", ExtensionAttribute11);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute12:{0}", ExtensionAttribute12);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute13:{0}", ExtensionAttribute13);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute14:{0}", ExtensionAttribute14);
sb.AppendLine();
sb.AppendFormat("ExtensionAttribute15:{0}", ExtensionAttribute15);
sb.AppendLine();
return sb.ToString();
}
}
You can also see that I overrode the ToString method.
The next step was calling my new class. I create a domain Principal, pointing to the OU “Employees” within my domain “SharePoint.com”. I start out by listing all users (using a green foreground color), then I ask for input to search for the sAMAccountName for a single user (using a cyan foreground color).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.AccountManagement;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
PrincipalContext domainContext =
new PrincipalContext(ContextType.Domain, "SharePoint", "OU=Employees,DC=SharePoint,DC=Com");
Person user = new Person(domainContext);
PrincipalSearcher searcher = new PrincipalSearcher(user);
var ret = searcher.FindAll();
ConsoleColor originalColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
foreach (var item in ret)
{
user = (Person)item;
Console.WriteLine("=========" + user.SamAccountName + "=============");
Console.WriteLine(user);
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
do
{
Console.WriteLine("Enter a user identity, we'll try it. Q to quit.");
Console.WriteLine("==============================");
string identity = Console.ReadLine();
try
{
user = Person.FindByIdentity(domainContext, identity);
Console.WriteLine("=========" + user.SamAccountName + "=============");
Console.WriteLine(user);
}
catch (Exception oops)
{
Console.WriteLine(oops.Message);
}
} while (Console.ReadLine() != "Q");
Console.ForegroundColor = originalColor;
}
}
And in the spirit of creating the world’s ugliest user interfaces… here’s a Console application! At least I colored the text two different colors so you could tell the sections apart.
OK, that was a TON more code than what I would have done with System.DirectoryServices.DirectoryEntry, but at the same time it was TONS easier to consume once I extended the Principal, meaning the person using my Principal Extension would have to know very little about Active Directory programming. I very much like the Query By Example capabilities of System.Security.AccountManagement as well, making search operations quite a bit easier than they would have been previously, and again, the same code can be used with SAM, ADLDS, and Active Directory. V
For More Information
Query Active Directory using System.DirectoryServices.DirectoryEntry
About System.Security.AccountManagement