Hey, Scripting Guy! Question

Hey, Scripting Guy! My company would like me to change our Active Directory logon names from this format – kmyer – to this format: Ken.Myer. Is there a way I can script all these changes?

-- DO

SpacerHey, Scripting Guy! AnswerScript Center

Hey, DO. You know, sometimes the Scripting Guy who writes this column implies that he leads a dull and drab life, that nothing exciting ever happens to him. Well, to be honest, that’s a bit of an exaggeration; as it turns out, exciting things happen to him all the time.

All the time.

For example, a new Jamba Juice smoothie bar recently opened up inside his local grocery store. Admittedly, the Scripting Guy who writes this column didn’t find that to be all that exciting; although he has nothing against smoothies he doesn’t feel an overwhelming need to drink a smoothie while grocery shopping. (Nope, not even a Mango Mantra or a Strawberry Nirvana, hard as that is to believe.)

What he did find exciting – or at least mildly-interesting, which is about all the excitement the Scripting Guy who writes this column can ever really hope for these days – is the reaction of his fellow shoppers: the line at the Jamba Juice now rivals – and oft-times even exceeds – the line at the Starbucks counter (which is also located inside the store). And that’s not because the people have stopped in to buy a smoothie or a latte to take home with them; instead, they’re drinking these beverages while they shop. In fact, all the shopping carts at the store were recently retrofitted with cupholders.

No, wait: make that dual cupholders, which is fortunate: the Scripting Guy who writes this column has seen at least one solo shopper who had a latte in one cupholder and a Jamba Juice in the other.

Now, there’s nothing wrong with that … we think. It just seems funny that you would need a latte and a Jamba Juice just to get through your grocery shopping. The Scripting Guy who writes this column doesn’t quite get it.

Note. Based on the current price of groceries, however, the Scripting Guy who writes this column would understand it if you needed, say, a fifth of vodka in order to get through your grocery shopping.

Admittedly, there are several aisles in the grocery store that the Scripting Guy who writes this column never goes down. (The seafood aisle, to name one.) As near as he can tell, however, the one thing that you can’t get at the grocery store these days are scripts that enable you to change the sAMAccountName for all the users in a domain.

But that’s OK; after all, that’s what the Scripting Guys are here for:

On Error Resume Next

Const ADS_SCOPE_SUBTREE = 2

Set 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.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = _
    "SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE objectClass='user'"  

Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst

Do Until objRecordSet.EOF
    Set objUser = GetObject(objRecordSet.Fields("ADsPath").Value)
    strOldName = objUser.sAMAccountName
    strNewName = objUser.givenName & "." & objUser.sn
    objUser.sAMAccountName = strNewName
    objUser.SetInfo
    Wscript.Echo strOldName & vbTab & strNewName
    objRecordSet.MoveNext
Loop

Before we explain how this script works we need to note that, in real life, this task might be a tad bit more complicated than our solution suggests. For one thing, we’re assuming that you’ve already assigned first names (the givenName attribute) and last name (the sn attribute) to all your users. If you haven’t, you’re going to immediately run into problems when you try to create a new logon name (sAMAccountName) that combines a non-existent first name and a non-existent last name. One way to work around that issue is to retrieve your user accounts using the following query:

objCommand.CommandText = _
    "SELECT AdsPath FROM 'LDAP://dc=wingroup,dc=windeploy,dc=ntdev,dc=microsoft,dc=com' " & _
        "WHERE objectClass='user' AND givenName = '*' AND sn = '*'"

This query returns only those user accounts (objectClass = 'user') where the user has both a first name (givenName = '*') and a last name (sn = '*'). Alternatively, you could use this query to retrieve a collection of all those users who are missing either a first name or a last name:

objCommand.CommandText = _
    "SELECT AdsPath FROM 'LDAP://dc=wingroup,dc=windeploy,dc=ntdev,dc=microsoft,dc=com' WHERE " & _
        objectClass='user' AND (givenName <> '*' OR sn <> '*')"

That’s entirely up to you. For this script we’re assuming your user accounts have already been assigned first and last names.

Wait, wait; we’re not done yet. We also didn’t address the possibility of there being two users with identical first and last names; for example, two different people named Ken Myer. That’s a problem, because the script will try to assign both of those users the same logon name: Ken.Myer. That’s a no-no: logon names must be unique within the domain. So how can you work around this problem? Well, one thing you could do is construct a proposed sAMAccountName and then – before assigning that name to a user – do a quick check of Active Directory to see if that name is already in use. If it’s not, then you’re home free; if it is, then you’ll have to do something to the name (e.g., tack a 1 on the end) to make it unique. That’s a bit more than we have time to go over today, but if anyone out there would like to know how to do this then drop us a line and we’ll take it up in a future column.

Assuming, of course, that the grocery store doesn’t beat us to it.

For today’s column we’re going to assume that there are no problems. (Which is par for the course for those of us who work at Microsoft; we never have any problems here.) We’re also going to do what we always do, which is this: skip over the explanation of how you do an Active Directory search. Why do we skip that explanation? That’s easy: that information is covered in great detail in our two-part Tales From the Script series Dude, Where’s My Printer? About all we will do is take a quick peek at the query that returns a collection of all the user accounts in the fabrikam.com domain:

objCommand.CommandText = _
    "SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE objectClass='user'"

As you can see, there’s nothing fancy here at all: we’re just requesting a collection of all the Active Directory objects that have an objectClass equal to user. You might note as well that we’re asking the script to return only a single attribute value: ADsPath, the property that enables us to locate a particular object in Active Directory. That might seem a little strange; after all, if we’re working with the sAMAccountName, givenName, and sn attributes shouldn’t we ask to get back those property values as well?

Well, you’d think so, wouldn’t you? As it turns out, however, Active Directory searches are read-only; you can’t make any changes or updates to an object by using a query. (Bad news for those of you accustomed to using SQL commands like UPDATE.) Instead, any time you want to modify an object in Active Directory you need to bind directly to that object. The search portion of our script makes it easy to retrieve the locations of all our user account objects, but that’s all it can do for us; we still have to bind to the actual user account in order to change the sAMAccountName. We didn’t bother retrieving the sAMAccountName, givenName, and sn attributes for one simple reason: at the moment, there’s nothing we can do with those attributes anyway.

But don’t worry; that will change in just a second.

After we define our query we use the following line of code (and the Execute method) to retrieve a recordset consisting of all the user accounts in the fabrikam.com domain:

Set objRecordSet = objCommand.Execute

From there we call the MoveFirst method to move to the first record in the recordset, then set up a Do Until loop designed to run until we’ve looped through each and every one of those records; that is, until the recordset’s EOF (end-of-file) property is True:

Do Until objRecordSet.EOF

What are we going to do inside that recordset? You know what? This explanation has already drug on for several minutes; why don’t you all run out and get a latte or a Strawberry Surf Rider and then we’ll continue on after that?

And yes, we apologize that this column does not come with built-in cupholders; we’re working on that right now.

OK, so what are we going to do inside that recordset? The first thing we’re going to do is take the Value of the ADsPath attribute for the first record and use that to bind to the actual user account in Active Directory:

Set objUser = GetObject(objRecordSet.Fields("ADsPath").Value)

As soon as we make the connection we grab the user’s existing sAMAccountName and store it in a variable named strOldName:

strOldName = objUser.sAMAccountName

We then use this line of code to construct a new sAMAccountName for the user:

strNewName = objUser.givenName & "." & objUser.sn

Again, there’s nothing fancy going on here at all: we’re simply grabbing the user’s first name (givenName), appending a period, then appending the user’s last name (sn). The net result? A user with a first name of Ken and a last name of Myer gets a new sAMAccountName that looks like this:

Ken.Myer

Which, by a nifty piece of luck, is exactly the sAMAccountName we want Ken Myer to have.

All we have to do now is assign the new value to Ken’s sAMAccountName, then call the SetInfo method to write those changes to Active Directory:

objUser.sAMAccountName = strNewName
objUser.SetInfo

But, of course, we couldn’t just leave it at that. Instead, just for the heck of it, we echo back Ken’s old sAMAccountName and his new sAMAccountName:

Wscript.Echo strOldName & vbTab & strNewName

That gives you a report similar to this, a report that helps you keep track of all the changes that were made:

Kmyer    Ken.Myer
packerman    Pilar.Ackerman
jhaas    Jonathan.Haas

At that point we call the MoveNext method to move to the next record in the recordset, then zip back to the top of the loop and repeat the process for user No. 2. And that, believe it or not, is all we have to do.

In case you’re wondering, the Scripting Guy who writes this column can now do each of the following at his local grocery store:

Buy a Jamba Juice.

Buy a latte.

Buy take-home Chinese food or Panini sandwiches.

Buy gas.

Rent a movie.

Take out a home loan or car loan.

Send a bouquet of roses.

As you can see, about the only thing missing from that list is “buy groceries.” But, then again, a grocery store can’t be expected to do everything.