Hey, Scripting Guy! I have a problem and I haven’t quite figured out how to solve it. I have an Active Directory group, and I’d like to remove some users from that group; in particular, I’d like to remove all the users whose user accounts reside in a specified OU. How do I do that?
Hey, AD, and hey to everyone else out there in scripting land. As you can see, the Scripting Guy who writes this column is back from his year-end break (and, you’ll be happy to hear, so is the Scripting Editor), and is ready to crank out the first Hey, Scripting Guy! column of 2008. For those of you who keep track of that sort of thing, this marks the fifth calendar year in which a Hey, Scripting Guy! column has appeared. Hey, Scripting Guy! debuted in August, 2004, which means we’ve published columns in 2004, 2005, 2006, 2007, and, now, 2008. As far as we know, that makes this the longest-running daily scripting column in the history of, well, history.
Although, admittedly, there’s not a whole lot of competition in the Daily Scripting Colum category.
But hey, that’s nothing. For example, here are a few other interesting facts about Hey, Scripting Guy!
Well, OK; here are a few other facts about Hey, Scripting Guy!:
Unofficially, this is the 837thHey, Scripting Guy! column. Will we be holding a special celebration to mark the 1000thHey, Scripting Guy! column? Maybe. Needless to say, though, we actually have to get to 1000 first. And the Scripting Editor isn’t getting any younger, you know.
Of these 837 columns, 836 were written by the Scripting Guy who writes this column; however, the 837th was actually written by Scripting Guy Jean Ross. (Interestingly enough, that ratio holds true for pretty much everything the Scripting Guys do: Greg does 836 things for every thing that Jean does.) Can you figure out which column Jean wrote? And no, sorry. Although it’s technically true, it’s not good enough to just say, “The one column that’s nowhere near as good as the other 836.” Instead, you need to be a little more specific. We’ll send a Scripting Guys T-Shirt to the first 5 people who can tell us the date of the column that Jean wrote.
The good folks at TechNet were actually opposed to the Scripting Guys writing a daily column; they preferred that we do a monthly column “because it’s far less work.” Some 837 columns later we’ve come to a sudden realization: they were right. Turns out that writing a column once a month, as opposed to once a day, really would have been far less work. That’s the last time we let Dean do the math for us.
If you look at the first couple months of Hey, Scripting Guy! you’ll notice that the columns aren’t quite as … colorful … as they are now. (No baseball stories? Say it isn’t so!) That’s because the good folks at TechNet were convinced that no one wanted to read about Scripting Sons and Scripting Editors. To tell you the truth, they were probably right. But after awhile we started writing about Scripting Sons and Scripting Editors anyway.
About two years ago the Scripting Guy who writes this column was able to hammer out the next column in no time at all: both the script and the column practically wrote themselves. “Gee, I’m really getting good at this stuff,” he thought. The next morning, just before he published the column, he suddenly realized why the article had been so easy to write: he’d answered that exact same question, using that exact same script, just a couple of months before. Needless to say, that day’s real column was published a bit later than usual.
Note. Have the Scripting Guys implemented some sort of quality control procedures to ensure that a mistake like that never happens again? No, we have not. But don’t worry; the Scripting Guys make so many new mistakes each day that the chances of them ever repeating a mistake are quite slim.
Today’s column was an easy one to write, too, and not because we’d already written it. Why was it so easy? Well, shortly after we received AD’s question, AD sent a second email stating that he had solved the problem himself. Because this was a good question, because AD really had solved the problem, and because the Scripting Guy who writes this column is having trouble getting back into the swing of things after being off work for nearly three weeks, well, because of all that we decided to go ahead and answer AD’s question, using the very same approach AD came up with. Here’s how you can delete all the members of a group whose user account resides in a specified OU:
Const ADS_PROPERTY_DELETE = 4 Set objGroup = GetObject("LDAP://CN=Finance Users,OU=Finance,DC=fabrikam,DC=com") For Each strUser in objGroup.Member If InStr(strUser,"OU=Kentucky Office,DC=fabrikam,DC=com") Then objGroup.PutEx ADS_PROPERTY_DELETE, "member", Array(strUser) objGroup.SetInfo End If Next
Note to AD. We really appreciate the fact that you sent in a script that answered your own question; it would make our
Seeing as how AD wasn’t able to send us an entire column (at least not this time), let’s see if we can figure out for
ourselves how the script works. As you can see, we start out by defining a constant named ADS_PROPERTY_DELETE
and setting the value of this constant to 4; we’ll explain why we need this constant in just a moment. After defining the
constant, we then use this line of code to connect to the group in question; for our purposes, that’s a group named Finance
Users, a group that resides in the Finance OU in the fabrikam.com domain:
Set objGroup = GetObject("LDAP://CN=Finance Users,OU=Finance,DC=fabrikam,DC=com")
As you probably know, each group object in Active Directory includes an attribute named Member. To be honest, that’s
not an especially good name; a much better name would have been Members, seeing as how this multi-valued
attribute contains a collection of all the members of the group. Regardless of the name, however, we can cycle through
the entire group membership simply by setting up a For Each loop that loops through all the values in the Member
For Each strUser in objGroup.Member
What are we going to do inside this loop? Well, as you might recall, what we want to do is delete any user whose user
account resides in a particular OU; in this case, that’s the Kentucky Office OU in fabrikam.com. How are we supposed
to know if a user account resides in the Kentucky Office OU? Good question. As it turns out, what’s actually stored in
the Member attribute are the distinguished names of each group member; that means an individual group member
is listed like this:
CN=Ken Myer,OU=Kentucky Office,DC=fabrikam,DC=com
So how do we know if this user account resides in the Kentucky Office OU? You got it: all we have to do is check to see
if the string value OU=Kentucky Office,DC=fabrikam,DC=com can be found anywhere in the user’s distinguished
And how do we do that? Why, by using VBScript’s InStr function, of course:
If InStr(strUser,"OU=Kentucky Office,DC=fabrikam,DC=com") Then
If InStr returns False (0), we simply zip back to the top of the loop and repeat the check with the next group member.
If InStr returns True, however, (or, to be more precise, if InStr returns any value other than 0) then we execute these
two lines of code:
objGroup.PutEx ADS_PROPERTY_DELETE, "member", Array(strUser) objGroup.SetInfo
Yet another good question: what is going on in these two lines of code? Well, in line one we’re using the PutEx method
to remove the designated user from the group. (That is, a user whose user account resides in the Kentucky
Office OU.) As you can see, we need to pass PutEx three parameters:
ADS_PROPERTY_DELETE. This, of course, is the constant we defined at the very beginning of the script. It’s also the
constant that tells the PutEx method that we want to delete a value from a specified attribute of the group
"member". What’s that? Which attribute do we want to delete a value from? Needless to say, we want to delete a value
from the Member attribute. It’s no coincidence, then, that the second parameter passed to the PutEx method
is the name of the attribute we want to work with: “member”.
Array(strUser). Last, but surely not least, the third parameter specifies the value to be deleted. (Remember, as a
multi-valued attribute Member can – and typically does – contain many values.) In the case of a group, we remove a
user from group membership by passing the distinguished name of that user. That’s easy to do in this case,
because group members are already listed by distinguished name. Note, too that we need to pass this value as an
array, even though we’re only passing a single value here. Why? Because the rules state that values passed to PutEx
must always be passed as arrays. And far be it from the Scripting Guys to ever break the rules.
Finally, in line two, we call the SetInfo method, which officially removes the specified user from the group membership.
Don’t leave out the SetInfo method. If you leave this out, it will appear as though the user’s group membership was
deleted; that’s because the change takes place in the local cache, the “virtual” version of the group account that resides in
your computer’s memory. However, calling PutEx only affects the virtual group account; to affect the actual Active
Directory account you need to call SetInfo.
That should do it, AD. (Although, seeing as how you wrote pretty much the exact same script yourself, well, you probably
already knew that that would do it.) As for the rest of you, you can look forward to another full year of Hey
Scripting Guy! That means more stories about the Scripting Son, more true facts about the Scripting Editor, and even
more amazing anecdotes about the Scripting Guy who writes this column. (Coming this week: how he managed to hide
a Christmas present from himself.)
And who knows? If we have time this year, maybe we’ll throw in a little information about scripting from time-to-time.