Utility to load Active Directory with sample data and introduce incremental changes

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm

Quite often I find myself in a situation where I need to quickly build a test environment for Active Directory (AD), whether it is to perform some MIIS directory synchronization testing or performance benchmarking of an application that utilizes AD.
I took an initial stab at this problem by creating a simple VB script (ADPopulator) that was using several Excel spreadsheets as an input for creating some randomized by still realistic user information. You can see more info on that script here:  https://blogs.msdn.com/alextch/archive/2006/09/18/AdPopulate.aspx. The script has a number of limitations though:
• It only deals with user accounts and does not provide support for group population
• You can only load accounts using this script. Quite often in directory synchronization tests we need to introduce some random changes into directory (modifications, deletions, additions)
• It has very limited support for randomly populating AD attributes beyond SN, givenName, samAccountName, and userPrincipal Name
• It requires Excel to be installed on the server
So when working on a test harness for my Oracle extensible MA I decided to write something that addressed the above limitations.

Introducing ADModifer 1.0
ADModifier is written in C# (get the source and executable in the attachment section of this blog), and unlike the ADPopulator does not require Excel to be installed on the server. As a matter of fact all that is required to run this utility is .NET 2.0 framework.
Similarly to ADPopulator  ADModifier utilizes a dictionary of European names to generate random material for SN, givenName, samAccountName, userPrincipal Name. This time in order to avoid the need for Excel or other databases I simply included the dictionary right in the executable by creating two classed FirstNames and LastNames. This change helps with rapid test environment setup, where all you need to do is just drop the executable on the server. But if you need to add some additional names into your test environment you would have to modify the source code (take a look at FirstNames and LastNames classes, I am sure it will be evident how to add additional names into the mix).
ADModifer utilizes .NET config file (ADModifer.exe.config) for its configuration settings. I will go over the config file parameters in detail later in this blog.

Some conceptual stuff
Before getting you started with the utility let me explain some of the concepts that I used when developing this utility.
Randomness
There are several aspects of randomness that I utilize in this utility.
First, the user IDs are generated by randomly selecting first and last name from a dictionary of European names. Prior to creating a new account the utility checks if such ID based on a random combination already exists and will add an integer to the end of the ID if a conflict exists. There is logic in the utility to keep looping and incrementing the integer until a unique ID is found, that is why you will see IDs of such format jbrown1234.
Second aspect of randomness in the utility is about picking accounts for deletion and modifications.  At startup the utility will search the directory and create an array of all the user objects currently present in AD. If your directory is empty then run the utility in initialLoad mode to only create new accounts (more on this when we talk about the config file). Once such an array is created we can randomly pick objects from the array by using .NET random object. So if we want delete 5% percent of the existing accounts the utility will iterate through the array and using random number generator as an index into the array of all AD objects will delete the required number of accounts. Same idea for modifying accounts
Before I bring in the third aspect of randomness in the utility let me introduce the concept of a template first.
Prior to running a utility you will create an OU into which you will place accounts that represent your typical user account settings. For example: you may simulate different departments and locations by creating template accounts that represent respective user populations. So let’s say in my test I want to simulate a company with four locations. I will create four accounts in the designated OU (more on this OU in the config section of this blog).  Each template account will have all of the attributes setup to represent that specific location (for example: l, department, manager, postalAddress etc). Also create some AD groups that are typical in that location and add the template accounts into those groups.
The idea here is that when we are creating a new account or modifying and existing account a set of attributes will be copied by randomly selecting a template account and copying those attribute from it. This can simulate employees moving to a different location or a different department. Also this approach allows for introducing some changes into the group membership within AD. So this the third aspect of randomness in this utility – we randomly select a template account from which we copy specified attributes and group membership.  The more template accounts your create the better distribution of values you will get in your test environment.

Getting Started
So enough with theory let’s take ADModifier for a spin
1. Installation. Copy ADModifer and ADModifier.exe.config to a folder of your choosing on the server.
2. Preparing AD.
a. Create an OU structure into which you want ADModifier to create new accounts. I suggest creating an OU dedicated for testing. This OU can have children; there are no limitations on the depth of the tree.  New accounts will be evenly and randomly distributed between OUs in this structure.
b. Create a separate OU outside the OU you created in step A for groups.  Manually create as many groups as you think are necessary for your tests.
c. Create a separate OU outside the OU you created in step A for template accounts. Create as many template accounts as you need (the names of the template accounts are arbitrary). Set all of the AD attributes that personalize a template account (department, location, address, manager, etc). Also add the template accounts into the groups created in step B. The idea here is that each template account will be a member of specific set of groups. Those group memberships and attributes will be copied into new and modified accounts.
3. Modify ADModifier.exe.config to adjust it to your environment.
Below is a sample config file that I used in my test environment.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
 <add key="initialLoad" value="FALSE"/>
 <add key="initialLoadAmount" value="10"/>
 <add key="usersRoot" value="OU=employees,DC=contoso,DC=msft"/>
 <add key="addAtTheRoot" value="FALSE"/>
 <add key="userTemplateRoot" value="OU=templates,DC=contoso,DC=msft"/>
 <add key="defaultPwd" value="pass@word1"/>
 <add key="attributesToCopyFromTemplate" value="l;deparment”/>
 <add key="percentToAdd" value="5"/>
 <add key="percentToDelete" value="5"/>
 <add key="percentToModify" value="5"/>
 </appSettings>
</configuration>
Let me go through each of those setting one at a time
initialLoad – if your AD is empty you will need to do initial loading of sample data. In other words you are not at the point to specify in terms of % how much change to introduce, you simply want to get some initial data loaded. Note: even for the initial load you will need template accounts setup, since new accounts will be personalized based on those tamplates.
initialLoadAmount – This attribute is used in conjunction with the initialLoad and specifies how many accounts to create
userRoot – Distinguished name of the OU where new accounts will be created. This OU is also used as a target for account deletions and modifications. OUs that reside below this OU will also be used by addition, deletion and modification processes
addAtTheRoot – Quite often you may want to setup your OU structure as such: employees->East, employees->West. In this setup East and West are children of the employee OU, and if you don’t want new accounts to be created at the root (in this case employees, but only in sub OUs: east and west) set this value to FALSE. If you have only one OU into which you want to load all of you new accounts make sure to set this to TRUE.
defaultPwd – The value of a password that will be assigned to the new accounts
attributesToCopyFromTemplate – This is a list of attributes separated by semi-column which you want to be copied from the template accounts
percentToAdd - % of new accounts to be created
percentToDelete - % of accounts to be deleted
percentToModify - % of accounts to be modified
4. On your first run set the initialLoad to TRUE and specify the number of accounts you need to create.
5. On subsequent runs set the intialLoad to FALSE and adjust percent values for additions, deletions and modifications.

Certain things to be aware of
1. Performance. Since at startup the utility needs to create an array of all existing accounts in the AD, this may take quite a bit of time and memory if you are building really large AD database. In my environment with 12000 accounts it takes over a minute for the utility to start doing some work.
2. Make sure to put correct values in the config file. I am only doing limited error checking so some error messages may be quite generic if you put wrong values into the config file.

 After publishing this blog, I got a couple of e-mails where people were getting "The Server is unwilling to process the request error". After investigating this further I found a bug in my code, which is not fixed.

The issue was around setting the userAccessControl attribute. Initially I was setting this attribute in the following sequence:

newUser.Properties["userAccountControl"].Value = 512; newUser.CommitChanges();
newUser.CommitChanges();
newUser.Invoke("SetPassword", new object[] { ConfigurationSettings.AppSettings.Get("defaultPwd") });

which worked fine in my lab environment where password complexity policy was disabled, but would produce the above mentioned error if password complexity policy is enabled.

Rearanging the sequence like so fixed the issue:

newUser.Invoke("SetPassword", new object[] { ConfigurationSettings.AppSettings.Get("defaultPwd") });
newUser.Properties["userAccountControl"].Value = 512; newUser.CommitChanges();
newUser.CommitChanges();

 

ADModifier.zip