Monday, June 1, 2009

LDAP, .NET, and Annual Review

It's that time of the year again - the annual performance review started at Microsoft today.

Annual review is an important part of every Microsoftie's career - it defines not only one's compensation, but, more importantly, reputation and mobility. It's much easier to move around the company if the review history is good. The more fun the project is, the bigger competition it attracts, and the more discriminating the hiring managers are when it comes to review history.

Good reviews are required (but, of course, not sufficient) for a great career at Microsoft.

Our team practices peer performance reviews. This is one of the Googleisms that I brought back from my year there. The theory is that peers know more than a manager about the work done by the employee, and are harder to fool, so majority of the review feedback and the ultimate review grade comes from them.

(See more here: http://1-800-magic.blogspot.com/2007/12/life-by-committee-or-management-google.html, and if you work at Microsoft and want to know more, drop me an email - I have written a whitepaper on peer review system and will be glad to share it with you.)

One of the artifacts of the peer review model is the amount of mail every manager has to send to request it - say, 5 employees times 6 reviewers per employee - that's 30 emails.

With tons of cut and paste, it took me over an hour of frantic typing last time around when we started using peer reviews during the mid-year career discussion cycle.

Then of course there is the fun of verifying that the right mail goes to the right person - individually, because you don't want to share everybody's reviewer list with everybody else, - that I didn't by mistake send a mail that is intended for one reviewer to the other, that all names in the email are correct, etc.

This time I was not about to sit and cut and paste for hours again - the problem called for a programmatic solution.

I could have probably easily put together a web site, but ensuring security a bunch of super-important personal information for a few dozen people is not exactly my idea of fun - and can't be reasonably done in under 30 minutes. The data had to stay in email.

But at least I could send the invitations automatically.

A tool was pretty quickly born that took an alias of the reviewee, and a list of aliases of the reviewers, formatted email from a template, and sent it out. In the process, I found out how to extract information from Active Directory using LDAP - I needed to get the names of the users from their aliases.

The job is surprisingly easy using DirectorySearcher class of the System.DirectoryServices namespace (you do need to add the reference to it before using).

Here's a code snippet that extracts and prints a bunch of interesting information about a user from its AD record.

Happy LDAP'ing!

//-----------------------------------------------------------------------
// <copyright>
// Copyright (C) Sergey Solyanik. All rights reserved.
//
// This software is in public domain and is "free as in beer". It can be
// redistributed in full or in parts for free and without any preconditions.
// </copyright>
//-----------------------------------------------------------------------
namespace ldapquery
{
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;

/// <summary>
/// Sample for LDAP usage in C#. Looks up a name, and then
/// </summary>
class Program
{
/// <summary>
/// Prints out various interesting user properties from ActiveDirectory
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: ldapquery alias1 alias2...");
return;
}

DirectorySearcher ds = new DirectorySearcher();

ds.PropertiesToLoad.Add("displayName");
ds.PropertiesToLoad.Add("givenName");
ds.PropertiesToLoad.Add("telephoneNumber");
ds.PropertiesToLoad.Add("mobile");
ds.PropertiesToLoad.Add("homephone");
ds.PropertiesToLoad.Add("mail");
ds.PropertiesToLoad.Add("title");
ds.PropertiesToLoad.Add("department");
ds.PropertiesToLoad.Add("manager");

foreach (string alias in args)
{
ds.Filter = "(SAMAccountName=" + alias + ")";
SearchResult result = ds.FindOne();
if (result == null)
{
Console.Error.WriteLine("Could not resolve {0}", alias);
continue;
}

Console.WriteLine("{0}:", alias);

if (result.Properties["displayname"].Count > 0)
{
Console.WriteLine("Display name: {0}",
result.Properties["displayname"][0].ToString());
}

if (result.Properties["givenname"].Count > 0)
{
Console.WriteLine("Given name: {0}",
result.Properties["givenname"][0].ToString());
}

if (result.Properties["telephonenumber"].Count > 0)
{
Console.WriteLine("Office Phone: {0}",
result.Properties["telephonenumber"][0].ToString());
}

if (result.Properties["mobile"].Count > 0)
{
Console.WriteLine("Mobile Phone: {0}",
result.Properties["mobile"][0].ToString());
}

if (result.Properties["homephone"].Count > 0)
{
Console.WriteLine("Display name: {0}",
result.Properties["homephone"][0].ToString());
}

if (result.Properties["mail"].Count > 0)
{
Console.WriteLine("Email: {0}",
result.Properties["mail"][0].ToString());
}

if (result.Properties["title"].Count > 0)
{
Console.WriteLine("Title: {0}",
result.Properties["title"][0].ToString());
}

if (result.Properties["department"].Count > 0)
{
Console.WriteLine("Department: {0}",
result.Properties["department"][0].ToString());
}

if (result.Properties["manager"].Count > 0)
{
Console.WriteLine("Manager: {0}",
result.Properties["manager"][0].ToString());
}

Console.WriteLine();
}
}
}
}

1 comment:

Илья Казначеев said...

You could do that with an one-liner ldapsearch in shell.