My employer is developing a large application which makes use of Microsoft Reporting Services. We were informed that Reporting Services security plays nicely with Active Directory (as you might guess it would). So, the idea was brought forward to manage our applications reporting security with Active Directory, but we did not want this to be a manual process. We needed a way to automate this. Our application manages projects. The people in those projects are managed in the project roster. If we could somehow, sync our project roster to Active Directory, then we would have what we were looking for.
So, we needed something that would pull data from our project roster and sync it to Active Directory. So I wrote a component to do just that: Create and manage AD groups.
More than just our app, we thought that various other systems in our organization might want to streamline AD group management. So we wanted to come up with a reusable component that could be used across all of these systems. We wanted this component to be totally decoupled from any data store with which it was synced. This goal resulted in the
ADGroupManager component. The model is pictured with this article. To view the model, right-click and Save As.
ADGroupManager component has two main pieces:
GroupDefinition provides a way for consuming code to communicate with the library what data needs to be synced.
GroupDefiniton basically just provides an Add Resource method. Whenever a resource is added,
GroupDefinition is responsible for building up groups which mirror how the groups will be in AD.
GroupDefinition has a
Group collection. Groups can contain groups or resources.
Groups will be built up in
GroupDefinition in different ways based on the
GroupDefinitionMode. We wanted some flexibility in the way that we created groups based on the assumption that multiple systems would be making use of our component. Two modes build flat groups based upon one or two tokens. We thought a common application would be Project and Role for the two tokens. The third mode works off of both tokens, but it builds up a parent group to which child groups belong. In our case, a role level group which would contain all project role level groups. All groups are prefixed with a group prefix, which in our case is the Application name.
GroupDefinition has been built,
ADUpdater can perform the actual sync with AD. It only has one
I have also included the Windows service I wrote to use my component, so you can see what the consuming code looks like. It performs timed operations based on a .NET
Timer. On startup, it reads a config file to get the start and stop times along with the delay between runs. It also contains the connection string and query string for the data store as well as configurable parameters for
ADGroupManager such as the prefix, mode, AD container and path. It also contains a class called
Logger which is basically just a wrapper for .NET
ADGroupManager solution contains all of the code for the component as well as all of the unit tests. The
PSADGroupWinService solution contains all of the code for the service (consuming code). I envisioned all of my classes and methods being
internal, except for the
public interface I imagined. I had to change that slightly to accommodate my testing. The RTF doc is a complete documentation of
ADGroupManager. It contains the model diagram in this article as well as information of every method, property, class, member, enum. You name it, it's there.
Using the Code
GroupDefinition relies on the following to run successfully:
- A mode to determine how groups will be created
- A group prefix which is used as part of the group name
- An LDAP path (not really needed for
GroupDefinition, but for
- An LDAP container. The default is Users, but you can specify your own (also for
Once all of these pieces are in place, resources can be added. There are two overloads for this:
AddResourceToGroupDefinition(string groupToken, string userId) and
AddResourceToGroupDefinition(string groupToken, string userId, string subGroupToken). Based on your
GroupDefinitionMode, only one of these overloads is available to consume code. Calling the wrong overload based on a specific mode will result in a
PolicyException. Also userid must be just the user id and not prefixed with the domain, so DOMAIN\userid is invalid.
Based on the mode, Groups are built up in a flat list or in the case of
PrefixExtendedGroupMode, parent groups are created. Therefore the
public APIs end up delegating to
AddResourceViaPrefixExtendedGroupMode(). Group names are very well defined based on the mode, being either
PrefixToken1Token2 with possibly a parent of
PrefixToken2. Therefore, comparison is done based on group name. Resources behave similarly and are only added to child groups. Groups can only exist in AD once, and that paradigm is kept in
ADUpdater only needs one thing, a
GroupDefinition. The goal was to create it such that it held everything that was needed to create AD groups. That way it could be simpler. It has only one
ADUpdater follows the same convention as the definition by delegating to
SyncExtendedListGroups(). These methods basically check to see if the group exists in AD, if it does not, it creates the group. With group in place, it adds people to the group. This is done by representing the group and user in AD as a .NET
DirectoryEntry and adding the user to the groups member property. This can be seen in
AddResourcesToADGroup(). Groups similarly are added to other groups. These syncing methods make use of various methods performing individual responsibilities such as Creating a group, Removing people from a group, Checking if the group contains a user, existence of groups, etc.
Definitions must always be complete definitions of the data needing to be synced. Deltas are not applied, because
SyncGroups() has the added responsibility of removing users who no longer belong. All updates are made to a group, and then a commit is performed. Currently, the component makes no provision for removing groups. There are several reasons for this whether it be for no defined way yet to identify groups that need removal to the prospect that groups may be used by other systems and should the group even be deleted.
Groups must have a group type. I have provided an
ADGroupType which mirrors the available AD group types. The only one I am making use of at the present time is
DomainLocal type. I believe it meets our needs. My understanding is that these types are flags and can be grouped together.
Group and Resource
These are basically just data containers.
Group does have some add/remove methods to manage its collections, but no biggie.
The consuming code in this article is a Windows service, but could be anything. I tested it quite extensively with a dumb little Windows app with a button and a label. The service reads the following from a config file:
False depending on whether you want to write to event log
GroupDefinitionMode, how groups will be created
PrefixName, the group prefix (our applications name)
ADContainer, the container in AD where groups will be placed
ADPath, the root LDAP path where AD resides
StartTime, service will not perform before this time, daily
EndTime, service will not perform after this time, daily
DelayInHours, combined with minutes to provide a delay between syncs
DelayInMinutes, combined with hours to provide a delay between syncs
ConnectionString, to the data store
QueryString, the appropriate data
When the service starts, the config is read and the service is initialized. Configuration must meet valid conditions for service to perform. Property
ConfigurationIsValid assures this. When the delay has passed, the timer goes off, Property
IsTimeToExecute verifies if it is time to execute based on the start and end time and delay. If a sync is currently executing, a secondary sync will not start. Logging is done to the application Event Log. I wrote a wrapper for it called
Logger. I left room to configure it for other event logs, but was satisfied with the application for now.
Please note, as submitted the solution for the service is set to use the Local System account to run the service. You will need to make sure that the service runs under an account that has access to write to ADAM or AD. I found that out the aggravating way.
Points of Interest
I don't know of any network admins that like you to just start creating AD groups, so I recommend that you contact them and see if they can set up a development environment for you. If you cannot get them to do so, try using ADAM instead. I did and it worked well. Unfortunately I had a horrible time trying to get it installed on my machine. Therefore, I have included as part of this article a Microsoft how to for ADAM install (ADAM_Step-by-Step_Guide) as well as a document that I wrote (ADAM How To) after a lengthy support call to Microsoft. ADAM can be downloaded here with a cool AD explorer here.
I don't currently have any plans for updates to the component, but I would look at the possibility of removing old groups, and working on coming up with a display name for groups that might make it easier for end users to identify groups.
I am not an AD expert. I had never written any code against AD before this small component. So if you notice some egregious error, let me know. That being said, the code works.
- 29th January, 2008: Initial release of this code version 0.0.1.0