Occasionally, I’ve had the need to populate sample users into an Active Directory for projects. There are, no doubt, other ways to do this that are simpler/more direct. For example, it’s likely incredibly easy using PowerShell (but I have never been a PowerShell person) and even in the Perl world, there are Perl modules to interact with LDAP. So using OLE may not seem like the ideal way, and that’s probably right, but OLE comes with virtually every Windows Perl distribution I’ve seen (as opposed to LDAP) and it isn’t terribly difficult. In general, I haven’t seen too many examples of using OLE to access this type of data on Windows systems, so I wanted to write a brief post about how it can be used for those that stumble upon it. It’s also nice that, by virtue of running through OLE, you don’t need anything special for authentication here, as that will be automatically inherited by your active running user.
First things first
In this example, I’ll assume you’ve at least got an Active Directory set up and that it’s generally fairly vanilla. We need to import the Win32::OLE bits into our script, which is relatively straightforward
use Win32::OLE 'in';
Next, we need to know a bit about the Active Directory that’s been set up. If you don’t know the structure of it, you can use a variety of tools including the open source LDAP Explorer or Microsoft’s Sysinternals Active Directory Explorer. You’ll need:
- The domain to put things in, in LDAP format
- The base path to put the new users/groups in
- To be a user that has access to create users (and groups, if you’re looking to do so) at the given base path
- To create or use some format of e-mail addresses for the new users
- An LDAP URL to access the system
So in this example, we’ll assume I’ll be putting users into customdomain.sfdemo.mysite.com (LDAP format of “DC=customsubdomain,DC=sfdemo,DC=mysite,DC=com”) and giving them an e-mail address of “firstname.lastname@mysite.com”. The base path will be “OU=UserGroup,OU=DemoUsers,DC=customsubdomain,DC=sfdemo,DC=mysite,DC=com”
So let’s go define these in Perl
my $domain = 'DC=customsubdomain,DC=sfdemo,DC=mysite,DC=com'; my $newpassword = 'pa55w0rd!'; my $emaildomain = 'mysite.com'; my $basepath = 'OU=UserGroup,OU=DemoUsers,' . $domain; my $ldaphost = '127.0.0.1'; my $ADsPath = 'LDAP://' . $ldaphost . '/' . $basepath;
Once we have this all defined, we can go grab the OU object and use it later
my $ou = Win32::OLE->GetObject($ADsPath) or die "Unable to get $ADsPath\n";
So to start out with, we’ll do something pretty easy: adding a security group. Let’s say we want to call this group “LegalUsers”. At its simplest, adding the group is just
- Creating the group
- Assigning an the “sAMAccountName” property to it for Active Directory
- Updating (saving) the object:
my $securitygroup = 'LegalUsers'; my $newgroup; $newgroup = $ou->Create('group','CN=' . $securitygroup); $newgroup->{sAMAccountName} = $securitygroup; #add the sAMAccountName property $newgroup->SetInfo( ); #save the object $newgroup = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=' . $securitygroup . ',' . $basepath) or die "Unable to find OU\n"; #retrieve the updated object for later reference
More complex: Adding a user
The only thing that makes adding users more complex is that there are more fields to add. We need to add the name and set the path of the user in AD and add an e-mail, but may also want to give them a default password, change their account expiration properties, assign them to groups, etc. Let’s walk through an example of this…
#For this example, we'll show 1 particular username. The bulk import later will make more sense with this #Although the bulk import will just take a flat 1-line-per-username format, a CSV could be devised and used instead my $username = 'shane.connelly'; my $email = $username . "\@$emaildomain"; #our new e-mail, as in shane.connelly@mysite.com my @namearray = split(/\./, $username); my $realname = $username; my $fullname = join(' ', map (ucfirst, @namearray)); #name will now be upper-cased first letters with a space as in "Shane Connelly" my $initials = uc(join('', map (substr($_,0,1), @namearray))); #initials will now be the first letters of each word, as in "SC" $initials = substr($initials,1,length($initials) - 2); my $lastname = ucfirst($namearray[$#namearray]); print "Adding $fullname\n"; #As with the groups, simply creating a user is fairly easy. #The only thing that really makes this different than any other LDAP import is that most AD systems have some specific schema as to the field names you use #The below works with that schema my $u = $ou->Create("user", 'cn=' . $fullname) or die "Unable to create user\n"; $u->{sAMAccountName} = $username; #as with the groups, we need a sAMAccountName and this needs to be unique $u->{company} = 'XYZ Corporation'; $u->{initials} = $initials if ($initials); $u->{displayName} = $fullname; $u->{name} = $fullname; $u->{givenName} = ucfirst($namearray[0]); $u->{sn} = $lastname; #sn = surname $u->{mail} = $email; $u->{mobile} = ('(' .(100 + int(rand(800))) . ') ' . (100 + int(rand(800))) . '-' . (1000 + int(rand(8000)))); #create some random phone number for demonstration purposes $u->{co} = 'United States'; #co = country $u->{c} = 'US'; #c = country code $u->SetInfo( ); #We'll give the user a default password. For this example, every user will have the same password, which is obviously quite insecure $u = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=' . $fullname . ',' . $basepath) or die "Unable to find last added user\n"; $u->ChangePassword("",$newpassword); $u->SetInfo( ); #Set some flags for the user #For this example, we'll say "Password doesn't expire" (0x10000 or 65536 in decimal) and make it a "Normal account" (0x0200 or 512 in decimal) #Just sum up the flags (0x10000 + 0x0200 = 0x10200 or 65536+512 = 66048 in decimal) #There are a number of flags you can add to a user account, available at http://support.microsoft.com/kb/305144 $u->{userAccountControl} = '66048'; $u->SetInfo( ); #perhaps we want to also add the user to the "Remote Desktop Users" group my $g = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=Remote Desktop Users,CN=Builtin,' . $domain) or die "Unable to find last remote desktop group\n"; $g->Add('LDAP://cn=' . $fullname . ',' . $basepath); $newgroup->Add('LDAP://cn=' . $fullname . ',' . $basepath) if ($newgroup);
Tying it all together
For creating demo users, I’d often simply create a flat file with usernames in it and read through them. You can also add managers, cities, and addresses for a more “real-looking” scenario.
use strict; use Win32::OLE 'in'; ##### CONFIG ##### my $domain = 'DC=customsubdomain,DC=sfdemo,DC=mysite,DC=com'; my $newpassword = 'pa55w0rd!'; my $emaildomain = 'mysite.com'; my $basepath = 'OU=UserGroup,OU=DemoUsers,' . $domain; my $securitygroup = 'LegalUsers'; my @cities = ('New York:New York','Los Angeles:California','Houston:Texas','Philadelphia:Pennsylvania','Phoenix:Arizona','San Antonio:Texas', 'San Diego:California','Dallas:Texas','San Jose:California','Jacksonville:Florida','Indianapolis:Indiana','San Francisco:California', 'Austin:Texas','Columbus:Ohio','Fort Worth:Texas','Charlotte:North Carolina','Detroit:Michigan'); my @streettypes = ('Avenue','Street','Circle'); my @streetnames = ('Second','Third','First','Fourth','Park','Fifth','Main','Sixth','Oak','Pine','Maple','View','Washington','Lake','Hill','Cedar','Stuart','Smith'); my @managers = ('CN=Ally Mcbeal,OU=UserGroup,OU=DemoUsers,DC=customsubdomain,DC=SFDemo,DC=MySite,DC=com', 'CN=Shane Connelly,OU=UserGroup,OU=DemoUsers,DC=customsubdomain,DC=SFDemo,DC=MySite,DC=com'); ##### END CONFIG ##### my $ADsPath = 'LDAP://127.0.0.1/' . $basepath; my $ou = Win32::OLE->GetObject($ADsPath) or die "Unable to get $ADsPath\n"; my $newgroup; if ($securitygroup) { $newgroup = $ou->Create('group','CN=' . $securitygroup); $newgroup->{sAMAccountName} = $securitygroup; $newgroup->SetInfo( ); $newgroup = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=' . $securitygroup . ',' . $basepath) or die "Unable to find OU\n"; } open(USRS,"users.txt"); while (<USRS>) { chomp; my $username = $_; $username =~ s/\_/\./g; my $email = $username . "\@$emaildomain"; my %altemails = (); my @namearray = split(/\./, $username); my $realname = $username; my $fullname = join(' ', map (ucfirst, @namearray)); my $initials = uc(join('', map (substr($_,0,1), @namearray))); $initials = substr($initials,1,length($initials) - 2); my $lastname = ucfirst($namearray[$#namearray]); print "Adding $fullname\n"; my $u = $ou->Create("user", 'cn=' . $fullname) or die "Unable to create user\n"; $u->{sAMAccountName} = $username; $u->{company} = 'XYZ Corporation'; $u->{initials} = $initials if ($initials); $u->{displayName} = $fullname; $u->{name} = $fullname; $u->{givenName} = ucfirst($namearray[0]); $u->{sn} = $lastname; $u->{mail} = $email; $u->{mobile} = ('(' .(100 + int(rand(800))) . ') ' . (100 + int(rand(800))) . '-' . (1000 + int(rand(8000)))); $u->{co} = 'United States'; $u->{c} = 'US'; my $userlocation = $cities[rand @cities]; my ($city, $state) = split(":", $userlocation); $u->{st} = $state; $u->{l} = $city; $u->{streetAddress} = int(rand(9999)) . ' ' . $streetnames[rand @streetnames] . ' ' . $streettypes[rand @streettypes]; $u->{postalCode} = int(rand(99999)); $u->{userPrincipalName} = $email; $u->{manager} = $managers[rand @managers]; $u->SetInfo( ); $u = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=' . $fullname . ',' . $basepath) or die "Unable to find last added user\n"; $u->ChangePassword("",$newpassword); $u->SetInfo( ); $u->{userAccountControl} = '66048'; $u->SetInfo( ); my $g = Win32::OLE->GetObject('LDAP://127.0.0.1/cn=Remote Desktop Users,CN=Builtin,' . $domain) or die "Unable to find last remote desktop group\n"; $g->Add('LDAP://cn=' . $fullname . ',' . $basepath); $newgroup->Add('LDAP://cn=' . $fullname . ',' . $basepath) if ($newgroup); } close(USRS);