Pages

September 27, 2008

Using Perl and ADSI to Update Active Directory

Recently, the CEO of my company informed me that he's been really loving our VoIP-based phone system (Enterprise Interaction Center from Interactive Intelligence). In particular, he really digs the available company directory provided by the front-end application, Interaction Client. It allows users to click on a phone number to automatically dial out. It's extremely convenient and many users don't even touch their handsets anymore. On top of that, the built-in company directory integrates with Active Directory and can display many of the attributes available for user objects (i.e. department, title, address). Now that we've expanded into additional office space and have a significant body of telecommuting staff, it's become very important to be able to organize and locate information on all of those users. So the question came: "Can we include additional information in the company directory that displays each employee's department, title, and location?" Yes, my friend, yes.

Centralized Management

I'd like to pause for a moment to provide some background. We use Active Directory for domain accounts, printers, permission/ACL administration, group policy definitions, etc. It's a great tool in our IT belt. And since, at its core, it's LDAP, I've been lobbying for a while now to leverage its capabilities in other areas of our environment. I despise the duplication of effort (i.e. reinventing the wheel) so when we need to provide directory-related services (i.e. organizing contact information or authentication), I prefer to use Active Directory (or OpenLDAP, depending). I've provided proofs of concept, attempted to influence developers' designs, and used it whereever I possibly can. My argument has fallen on deaf ears, until now.

Updating Active Directory

The request was simple enough: we needed each employee's department, title, and some location information added to their respective accounts so that they would display in Interaction Client. And since there is already integration between the phone system and Active Directory (AD), I just needed to update a few attributes and wait for the two to sync. I decided to do so programmatically. Especially because we have 140 employees. Especially because I don't trust my typing skills across a large enough body of data.

All we need is an input file that contains the employees' names, and all the attributes that need to be updated. That's simple enough; HR provided everything I needed in a simple spreadsheet. I simply stripped out any needless headers and then exported the file as a pipe-delimited text file (I chose the '|' symbol as my delimiter because some names may contain commas and this would cause a big headache if I tried to export the data as a comma-separated file).

One last thing: we need access to the 'dsquery' command. This is a simple command that queries AD and returns information on the object specified. We'll need to grab the distinguished name for our user objects before we can update them. You can find the 'dsquery' command in the Windows Server Resource Kit.

Perl and ADSI

My language of choice is Perl. It's partly because that was one of the first languages I was introduced to and partly because of its utility; its cross-platform capabilities are crucial for me as I work in a heterogeneous environment. To interface with Active Directory, I decided to use the ADSI API (a COM derivative). I could have used Perl's LDAP support (Net::LDAP) for this project, but where possible, I always try to use native functionality. Perl supports COM interaction via its Win32::OLE module. For those that haven't used Win32::OLE, note that if you're handy with VBScript, it's usually a very easy matter to convert VBScript code to Perl using this module. In fact, the Microsoft Scripting Guys provide a handy utility that demonstrates how easy it is to write COM-related programs in various languages via their Scriptomatic tool. Download it and give it a go.

NOTE: Neither a discussion of Perl nor ADSI is within the scope of this post. For more information, see perl.org and msdn.microsoft.com.

The Code

The script is short and sweet. It will open our input file for reading, find the DN for each user object (the full name is created from the first and last names for each user), instantiate a COM object based on the DN, and finally, update the attributes we need (please forgive any ugly line wraps):

#!/usr/bin/perl

#
# adUpdate.pl - Update Active Directory using the ADSI API (via the LDAP provider)
#

use warnings;
use strict;
use Win32::OLE qw( in );

my $employeeAttributeFile = 'employeeAttributes.txt'; # our pipe-delimited input
open( ATTRIBUTES, $employeeAttributeFile ) or die "Unable to open \'$employeeAttributeFile\' for reading: $!\n";
while ( < ATTRIBUTES > ) {
   my ( $lastName, $firstName, $streetAddress, $department, $title ) = split(/\|/); # our input is pipe-delimited
   my $fullName = $firstName . ' ' . $lastName; # combine the first and last name for queries later
   print "name: " . $fullName . "\n"; # debug

   # call 'dsquery' to hunt for this user object in our directory and return the DN
   # 'dsquery' is available in the Windows 200x Resource Kit
   my $dsqueryCMD = "dsquery user -name \"$fullName\"";
   my $dn = qx( $dsqueryCMD ); # qx// allows us to shell out and grab the results/output (unlike 'system()')
   $dn =~ s/"//g; # strip the surrounding double-quotes from dsquery's output
   #print "DN: $dn\n"; # debug

   my $adsPath = "LDAP://$dn"; # ADSI path using the LDAP provider
   #print "\$objUser = Win32::OLE->GetObject( $adsPath );\n"; # debug
   my $objUser = Win32::OLE->GetObject( $adsPath );

   # if $objUser isn't set, we have a problem; report it and jump to the next record in our file
   if ( ! $objUser ) {
      print "ERROR: " . Win32::OLE->LastError() . "\n\n";
      next;
   } # end "if ( ! $objUser )..."

   # update the attributes we need and set them
   $objUser->Put( "streetAddress", $streetAddress );
   $objUser->Put( "department", $department );
   $objUser->Put( "title", $title );
   $objUser->SetInfo();

   print "ADDRESS: " . $objUser->{streetAddress} . "\n"; # debug
   print "DEPT: " . $objUser->{department} . "\n"; # debug
   print "TITLE: " . $objUser->{title} . "\n"; # debug

} # end "while ( < ATTRIBUTES > )..."
close ATTRIBUTES;

# fin!

At this point, I've successfully updated specific attributes for all users in AD without any manual data entry. I'm happy and more importantly, the CEO is too.

Disclaimer

This code is provided "as-is" and is simply meant to demonstrate a solution to a problem I needed to solve. As such it carries no warranties or guaranties regarding its use. You can use this code as you see fit; it's hereby in the public domain.

1 comment: