Extending the Active Directory Schema
Bit of a departure from my normal PowerShell-centric posts, I want to talk about extending the Active Directory schema. There’s some really great information on the Internet for doing this, but there are some things to consider and none of that information seems to be in one place, and I wanted to bring it together here.
This all started when…
…I was asked to write a script to report on users who haven’t logged in in 30 days. Scope creep struck and suddenly it became a script to disable users if they haven’t logged in in 30 days. On the surface this is easy enough and could probably be done with a one-liner:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Search-ADAccount –AccountInactive –TimeSpan 30 | Set-ADUser –Enabled:$false |
But as I explained in my last post this may not work. First, LastLogonDate isn’t a perfect indicator, and at athena health we have a couple of other factors to consider. One is users who have been with the company for 8 years or more can go on extended sabbatical’s, meaning they could easily go over 30 days without logging in. Second we sometimes provision users over 30 days in advance, so any script to do this has to take those factors into account. One major difference between the Enterprise and Small Business is also the requirement that things just be right, no compromises. This requires a lot more time for an Administrator (and a PowerShell scripter) to do seemingly simple tasks. For that reason I decided that this script would use the perfect LastLogon date I developed in the last post. But I also had to figure out how to handle the sabbatical and start date problems.
Extend the Schema, you must
Labored Star Wars reference aside, I needed a couple of fields from Active Directory to handle this. Unfortunately there is no field in Active Directory for start date, nor if the user is on an extended leave. If you install Exchange you will get a number of “CustomAttribute” fields you could use but the problem here is there’s no control over what fields are used and I’ve seen 3rd party apps actually use them! What happens if we bring in an app that needs to use a field we’re already using? The other problem is these fields are all strings and I really need a date/time field for Start Date and a boolean field for On Leave.
That means extending the schema in Active Directory and adding our own fields. This is actually a very safe process and shouldn’t be feared, but you do need to be prepared. The first thing is you need to understand how Active Directory is structured. There are classes, which describe the types of objects that can be contained in Active Directory and their our attributes which describe which fields are associated with each class. The interesting thing here is that an object in Active Directory can be associated to multiple classes and attributes can be associated to multiple classes.
So we have two approaches–probably more but I’m just going to keep it simple–we can take in this case. One is we can create a new class and associate all the users to that class. This has the benefit of extending the user class without actually changing it, potentially protecting you from unknown changes that might be introduced a decade from now. But as my friend Jay6111 can attest, actually living with this kind of change from a scripting perspective is a royal pain–and it breaks a major tenant of Information Technology administration: keep it simple! The second approach is to create new attributes and associate it to the user class, automatically extending every user object in Active Directory with the new fields. This is the approach I decided to use, it’s simple, easy and safe.
Adding the Attributes
The ShowMeHowToDoIt website has a fantastic step by step page for adding the attributes and associating it to the user class, and there’s no need for me to replicate it here! Just go there and read it then come back and find out why it’s not complete.
Thanks for coming back. So why isn’t it complete? It’s all about the OID. Microsoft provides a great script for getting a OID, which is really just a unique identifier for your attribute. You can put in any name you want, even one already being used and AD will not have a problem with it because the OID is unique. Please don’t do this!! Why? Just because AD won’t have a problem doesn’t mean that every single application, PowerShell script and everything else out there won’t have a problem. Better to keep your attributes utterly unique at every level. The problem with the OID generated by the vbScript is it’s not totally unique. Now, don’t panic (another Sci-Fi reference) as the number generated is so random that the odds of there every being a collision is on an order much greater than winning the lottery. But if you’re in an Enterprise that’s still too great a chance, and we need to know that there’s no chance of there ever being a problem. You don’t want to be the guy explaining to the CIO why his Active Directory is broken and there might be no–or any–easy fix.
The solution is using a Private Enterprise Number or PEN, which you can register here. The idea is you get a unique PEN and then add additional numbers to it to create your own scheme and ensure a totally unique OID. I went to IANA and checked the registry and sure enough athena health already had one registered! Turns out networking people use a PEN all the time for their custom SNMP traps. So the trick is I had to get the PEN from our networking people and develop a scheme to make sure none of my OID’s ever run over theirs.
To develop our OID scheme I found this fantastic post from the Karlsruhe Institute of Technology. It’s pretty wide open how you construct this, but I followed the general organization laid out in that post:
<ah pen>.10000 WinOps (my department) <ah pen>.10000.1 Active Directory <ah pen>.10000.1.1 User Class <ah pen>.10000.1.2 Attributes <ah pen>.10000.1.2.1 athenaStartDate [DateTime] <ah pen>.10000.1.2.2 athenaOnLeave [Boolean]I picked a random number that was incredibly high for the department so I would never overlap with our networking group–as was commented by them, it’d take about 5000 years for us to every overlap!–and then a simple matter to designate Active Directory, User Class (which won’t be used, but I wanted to allocate it beforehand), Attributes and then the two new attributes we’re adding. Notice I gave them utterly unique field names by adding “athena” to the beginning of them. No way we’ll ever clash with any other application in the future! And if we do? Well, what the hell?
Now the OID’s have been defined it’s a matter of following the steps to create the attributes and associate them to the user class. I made the changes after hours and just sat back and watched the changes replicate out to all of our domain controllers. It was nice to find the Active Directory tools from Microsoft fully able to use the new fields too:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Get-ADUser Martin9700 –Properties athenaStartDate,athenaOnLeave |
Interesting behavior coming from the tool though. If the values for these objects are $null, then Get-ADUser will not add the attributes to the object returned. This will cause you problems with your scripts until those fields have data in them, once they do they’ll behave like any other attribute in your user object. I expect Get-ADUser gets the standard attributes than uses the equivalent of Add-Member to put the new attributes into the object–but since they’re $null it doesn’t actually do it.
Thanks for this! This is about the only concise walkthrough I’ve found online.