Hey, Scripting Guy! How can I determine the home directory for all the disabled user accounts in Active Directory?
— DB
Hey, DB. You know, it’s not like the Scripting Guy who writes this column is addicted to TV or anything, but last night he was watching TV (as usual) when he saw a brief feature on a new device that will scramble eggs for you. As the commentator noted, scrambling eggs is a “difficult and tiring chore.” But you’ll never have to scramble your own eggs again; instead, you can just let this new egg scrambler thing scramble those eggs for you.
Now, if you want to know the truth, the Scripting Guy who writes this column has never really found egg scrambling to be all that difficult or all that tiring. Admittedly, he usually lies down and takes a nap after scrambling eggs, but that’s due more to him being lazy than it is to egg scrambling being particularly difficult or tiring. Apparently, however, egg scrambling is a major problem for people. Or – check that – egg scrambling used to be a major problem for people. Now, thanks to the wonders of modern technology, you’ll “never use that whisk again.” Instead, the next time you want scrambled eggs all you have to do is take an egg, slide it onto this little needle-like thing, then turn on the egg scrambler. When you take the egg out and crack it open – voila! the egg has already been scrambled! Right in its very own shell!
Will wonders never cease?
At any rate, DB, we almost didn’t answer your question today; in fact, we toyed with the idea of never bothering to answer a question ever again. We’re sure you can understand why. After all, a script that determines the home directory for all the disabled user accounts in Active Directory looks pretty lame when compared to an automatic egg scrambler. But the show must go on, right? The following script won’t scramble eggs for you (and for that we apologize), but maybe someone can get some use out of it:
On Error Resume NextSet objConnection = CreateObject(“ADODB.Connection”) Set objCommand = CreateObject(“ADODB.Command”) objConnection.Provider = “ADsDSOObject” objConnection.Open “Active Directory Provider” Set objCommand.ActiveConnection = objConnection
objCommand.Properties(“Page Size”) = 1000
objCommand.CommandText = _ “<LDAP://dc=fabrikam,dc=com>;” & _ “(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=2));” & _ “Name,homeDirectory;Subtree” Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
Do Until objRecordSet.EOF Wscript.Echo objRecordSet.Fields(“Name”).Value Wscript.Echo objRecordSet.Fields(“homeDirectory”).Value Wscript.Echo objRecordSet.MoveNext Loop
As usual, we won’t discuss each and every line of code in this script; if you could use a refresher course on writing Active Directory search scripts we recommend you take a look at our two-part Tales from the Script series Dude, Where’s My Printer? However, we will spend a miute or two looking at the query we use in this script, a query that retrieves the values of the Name and homeDirectory attributes for all the users whose user accounts have been disabled:
objCommand.CommandText = _ “<LDAP://dc=fabrikam,dc=com>;” & _ “(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=2));” & _ “Name,homeDirectory;Subtree”
Why does our query look like this? Well, in order to determine whether or not a user account has been disabled we need to check the value of the userAccountControl attribute. As it turns out, userAccountControl is a bitmask attribute, an attribute that actually contains a number of properties and their values. (For a list of those properties, see the Active Directory Schema Reference on MSDN.) Because userAccountControl is a bitmask attribute, we can’t use a standard SQL query (e.g., “Select Name, homeDirectory From ….”) in order to retrieve information based on that attribute; it just won’t work. Instead, we have to use the LDAP query syntax. And that’s why this query looks so weird.
Without going into too much detail (for that, see our Scripting Guys webcast on writing Active Directory search scripts) here’s an explanation of the various parts of the query (note that the individual parts in the query itself are separated using semicolons):
<LDAP://dc=fabrikam,dc=com>. This one you can probably figure out yourself: it’s simply the domain we want to search.
(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=2)). This, believe it or not, is our query filter, the part where we specify the criteria for the search. In this case, we have two criteria that must be met: the object must have an objectCategory equal to user, and the flag for a disabled user account (which has a value of 2) must be set. Granted, none of that is particularly obvious just from looking at the query, but it’s true. For example, the 1.2.840.113556.1.4.803 is the “LDAP bit matching rule,” something which is essentially equal to the AND keyword in a Boolean command like If objUserAccountControl AND 2.
So why didn’t the LDAP query folks just use the word AND? We don’t know; we can only speculate that it really wouldn’t be an LDAP bit matching rule if they didn’t. But don’t worry about that; just use the syntax exactly as we’ve shown and you’ll be fine.
Incidentally, the ampersand (&) at the beginning of the filter string simply ties together the two parts of our query: the objectCategory must be equal to user and the appropriate flag in the userAccountControl must be set. Again, take a look at our webcast if you’d like more information on LDAP queries. If you’d just as soon not have more information, well, that’s fine. In that case, just copy the code shown here and tweak it if you ever want to query on a different property found in the userAccountControl. For example, if you’re a user who has a password that never expires, the flag with the value 65536 will be enabled in your userAccountControl. That means we can search for you (and your fellow non-expiring password types) using this filter:
(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536))
All we did was change the value that we’re looking for, substituting 65536 for the value 2 we used in our original filter.
Note. You might have noticed that our LDAP query seems kind of jammed together; for example, we have objectCategory=user instead of spacing that out a bit nicer: objectCategory = user. Is that important? Surprisingly enough, it is. If you put blank spaces between the attribute, the equals sign, and the value (e.g., objectCategory = user) your script will fail. In other words, jam everything together as closely as you can. |
Name,homeDirectory. This filter piece is a bit easier to understand: it’s just a list of the property values we want the script to return. Note that we separate the individual values using commas. The semicolon (as noted) is used to separate the individual filter pieces.
Subtree. This is the search scope, and simply tells the script to start the search in the domain root and then search every container found in that domain.
Yes, that is kind of crazy. However, it does work, and once we define the query all we have to do is call the Execute method to kick off the search and get back a recordset containing the Name and homeDirectory for all the disabled user accounts. As soon as we have that recordset we can set up a Do Until loop that loops through the returned data, echoing back the appropriate values for each account:
Do Until objRecordSet.EOF Wscript.Echo objRecordSet.Fields(“Name”).Value Wscript.Echo objRecordSet.Fields(“homeDirectory”).Value Wscript.Echo objRecordSet.MoveNext Loop
Two things to pay attention to here. First, note the syntax for referring to an item in the recordset:
objRecordSet.Fields(“Name”).Value
Again, don’t worry about the why. Just copy the code as-is and your script should work just fine.
Second, remember to call the MoveNext method in order to move on to the next record in the recordset. Leave out MoveNext and your script will echo back information for the first record, return to the beginning of the loop, and then – alas – echo back information for the first record all over again. This will continue forever and ever and ever. Any time you work with an Active Directory recordset you must specifically tell the script to move to the next record. If you don’t, it won’t.
And that should do it, DB. Like we said, this won’t help you much with your scrambled egg problems; fortunately, though, there are entire teams of researchers and engineers working around the clock to help alleviate those problems.
Note. In the interests of full disclosure we should note that the Scripting Guys actually took a whack at this ourselves: we shook a chicken really, really hard right before she laid her eggs, hoping that she would go ahead and lay a pre-scrambled egg. (Editor’s Note: No, we really didn’t. And don’t you try it, either.) How did that go? Let’s put it this way: not quite as well as we had hoped. And the chicken wasn’t all that happy with the process either. |
0 comments