Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I get a list of all the users in an OU who are not a member of a specified group?

-- TJ

SpacerHey, Scripting Guy! AnswerScript Center

Hey, TJ. You know, the Scripting Guys are having so much fun with the 2008 Winter Scripting Games that we just can’t bear to see the excitement come to an end; therefore, we’ve decided to extend the Games for a couple more days. The Scripting Games will now close at 8:00 AM on Thursday, March 6, 2008.

Actually, that’s not entirely true; the Games still officially end on Monday, March 3rd. However, one hallmark of the Scripting Games is that you always get a second chance (and even a third or fourth chance if needed): if you get a 0 in an event you always have the opportunity to fix the problem and resubmit your entry. If the revised script works then we’ll change your score and award you the points.

That’s all fine and dandy, except for one thing: at the moment the Scripting Guys are a … bit … behind in their scoring. And that’s a definite problem: after all, how can you resubmit an entry if the Games are over before you know that you even need to resubmit an entry? (Which is very possible: there’s zero chance that we’ll have every single script tested and score by Monday morning.)

What’s that? Why are the Scripting Guys so far behind in their scoring? Well, it’s not for lack of trying; both Scripting Guys were up past 2:00 AM last night testing scripts. (Although we might note that only one of those Scripting Guys made it into work by 7:00 AM.) The problem is simply the sheer volume of entries: we have almost twice as many entrants as we did last year, and last year it was all we could do to keep up. Oh, and just to make things even more exciting, we’re also dealing with an overzealous spam filter that rejects a goodly portion of the scripts sent our way.

Note. Why? Well, as near as we can tell, the spam filter sees an email filled with “nonsensical” text (i.e., script code), and automatically marks it as spam. As you probably know, a common trick used by spammers is to compose an email filled with a random selection of words that make absolutely no sense. Because the spam filter can’t speak VBScript, Perl, or Windows PowerShell, it will occasionally deem emails with that code as being spam.

Incidentally, this also explains why we can’t send Hey, Scripting Guy! out as a daily email (something a number of you have requested). Like we said, the spam filter automatically rejects any email filled with a random selection of words that make absolutely no sense.

Anyway, although the Scripting Games officially end on Monday, we’ll give you at least until Thursday morning to resubmit any entries. (And longer if need be.) In other words, relax: you’ll have plenty of time to correct your mistakes and get that perfect score that everyone covets so much.

Note. So does this extended deadline apply only to scripts being resubmitted for an event? Yes. Does that mean that those of us who missed the Scripting Games are out of luck until next year? Officially, yes. Unofficially, though, if you really want to participate in the Games and you can get your scripts to us by Thursday morning, well, what the heck.

Just don’t tell the Scripting Editor, though. She’s not going to like that idea at all.

Unfortunately, none of the Scripting Games events require you to list all the users in an OU who are not members of a specified Active Directory group. Why is that unfortunate? Because if there was such an event you could simply submit the following script and call it good:

Set objDictionary = CreateObject("Scripting.Dictionary")

Set objGroup = GetObject("LDAP://CN=Finance Users,OU=Finance,DC=fabrikam,DC=com")

For Each strMember in objGroup.Member
    objDictionary.Add strMember, strMember   
Next

Set objOU = GetObject("LDAP://OU=Finance,DC=fabrikam,DC=com")
objOU.Filter = Array("User")

For Each objUser in objOU
    strUser = objUser.distinguishedName    
    If Not objDictionary.Exists(strUser) Then
       Wscript.Echo strUser
    End If       
Next

Let’s see if we can figure out how this all works. To begin with, we create an instance of the Scripting.Dictionary object. Why? Because we’re going to store the list of group members in the Dictionary. Why? We’ll get to that in a minute. After creating the Dictionary object we then use this line of code to bind to an Active Directory group named Finance Users:

Set objGroup = GetObject("LDAP://CN=Finance Users,OU=Finance,DC=fabrikam,DC=com")

Before we can figure out which users are not members of the Finance Users group we need to figure out which users are members of this group. As it turns out, each Active Directory group includes a Member attribute; as the name implies (well, sort of) this attribute stores a list of all the group members.

Note. In that case, shouldn’t that be the Members attribute? Probably. Apparently no one ever considered the possibility that a group might have more than one member.

In order to get at the group membership all we have to do is set up a For Each loop that walks through all the items (members) in the Member attribute:

For Each strMember in objGroup.Member

Inside this loop we do one thing and one thing only: we add the member’s name (technically, his or her distinguishedName) to the Dictionary:

objDictionary.Add strMember, strMember

As you can see, this is a pretty simple operation: all we do is call the Add method, using the member’s name (stored in the loop variable strMember) as both the Dictionary key and the Dictionary item.

By the time the loop is finished our Dictionary object will contain a list of all the members of the Finance Users group.

Our next step is to retrieve a list of all the users in a specified OU (in this case, the Finance OU in fabrikam.com) and then determine if any of those users are not members of the Finance Users group. To do that, we first bind to the OU in Active Directory, then set a Filter on the returned data, a Filter that limits that data to user account objects:

objOU.Filter = Array("User")

After that we set up another For Each loop, this one designed to walk through the collection of user accounts stored in the Finance OU. Inside this loop we use the following line of code to retrieve the value of the user’s distinguishedName attribute, storing that value in a variable named strUser:

strUser = objUser.distinguishedName

Good question: why the distinguishedName attribute? Well, as you might recall, the Member attribute stores the distinguished name of each group member; in turn, that means that our Dictionary object also contains the distinguished name of each group member. How do we know if a given user is a member of the Finance Users group? That’s easy: we simply check to see if the user’s distinguished name can be found anywhere in the Dictionary. If it can then that means the user is a member of the group.

As a matter of fact, that’s what this line of code is for:

If Not objDictionary.Exists(strUser) Then

Here we’re simply using the Exists method to determine if the user’s distinguished name (stored in the variable strUser) appears in the Dictionary. If it does then we simply go back to the top of the loop and repeat the process with the next user in the Finance OU. If it doesn’t, then we echo back the user’s distinguished name:

Wscript.Echo strUser

And then it’s back to the top of the loop where we try again with the next user account.

When we’re all done we should have output that reports back the distinguished name of each user in the Finance OU who is not a member of the Finance Users group. In other words, we should see something similar to this:

CN=Ken Myer,OU=Finance,DC=fabrikam,DC=com
CN=Pilar Ackerman,OU=Finance,DC=fabrikam,DC=com
CN=Jonathan Haas,OU=Finance,DC=fabrikam,DC=com

What’s that? Well, you know, now that you mention it that is kind of goofy-looking output, isn’t? OK, tell you what; try this modified block of code instead:

For Each objUser in objOU
    strUser = objUser.distinguishedName 
    strUserName = objUser.displayName   
    If Not objDictionary.Exists(strUser) Then
       Wscript.Echo strUserName
    End If       
Next

We’ve done two things different here. First, we’ve added a line of code that retrieves the user’s displayName attribute and stores that value in a variable named strUserName:

strUserName = objUser.displayName

Second, inside our If statement we display this value (the display name) rather than the distinguished name:

Wscript.Echo strUserName

Will that give us output that’s a little less goofy-looking? See for yourself:

Ken Myer
Pilar Ackerman
Jonathan Haas

Much better.

That should do it, TJ. Remember, if you or anyone else out there would like to resubmit a script for the 2008 Winter Scripting Games there’s still plenty of time to do so. And don’t worry; this is definitely not cheating. We always have, and always will, give people a chance to fix their mistakes. After all, our goal is to help people learn something about scripting (and maybe have some fun while doing so). To tell you the truth, we’d be thrilled if everyone ended up getting a perfect score.

Besides, if it was cheating, do you think the Scripting Guys would let you get away with it?

OK, good point: the Scripting Guy who writes this column probably would. But the Scripting Editor? No way.