Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I get a list of all the local users who have an expiring password?

-- JM

SpacerHey, Scripting Guy! AnswerScript Center

Hey, JM. Thanks for the question. Before we answer it, however, we have a question for you: you don’t by any chance have any spare dice lying around, do you? That’s right, dice, the kind you use when playing board games and craps and stuff like that. As it turns out, the Scripting Guys have an urgent need for dice; in fact, we could use 1,000 individual die ASAP. Unfortunately, we’re having a little bit of trouble locating that many dice at a price we can afford.

Mainly because if something actually has a price, we can’t afford it.

Why do the Scripting Guys need so many dice? Oh, no reason, really. After all, we are technical writers at Microsoft, and 1,000 dice is pretty much standard equipment for a technical writer. It’s hard enough being a Scripting Guy as it is. But just try being a Scripting Guy who doesn’t have any dice.

See? What did we tell you: you really can’t be a Scripting Guy without plenty of dice, can you?

Anyway, JM, if you happen to have any dice you aren’t using we’d be happy to take them off your hands. And we’d like to extend that offer to anyone else who might be reading this column. Got boxes and boxes of dice filling up your attic or your basement? Then send them to this address:

The Microsoft Scripting Guys
Microsoft Corporation
Building 42/4039
One Microsoft Way
Redmond, WA 98052

We’ll be happy to take them, in all shapes, sizes and colors.

Except maybe the fuzzy ones.

Of course, the Scripting Guys would never be so rude as to ask for dice without offering a little something in return. Let’s see what we got here … hey, how about a script that lists all the local users who have an expiring password:

Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
 
strComputer = "atl-ws-01"

Set colAccounts = GetObject("WinNT://" & strComputer & "")

colAccounts.Filter = Array("user")

For Each objUser In colAccounts
    If Not objUser.UserFlags AND ADS_UF_DONT_EXPIRE_PASSWD Then
        Wscript.Echo objUser.Name
    End If
Next

OK, let’s see if we can figure out how this baby works. As you can see, we start out by defining a constant named ADS_UF_DONT_EXPIRE_PASSWD (a delightful name, don’t you think?) and setting the value to &H10000. And you’re right, this constant will help us determine whether or not a user has a password that expires. We next assign the name of the computer (the computer where the local accounts are to be found) to a variable named strComputer, then use this line of code to bind to the computer’s System Account Manager (SAM):

Set colAccounts = GetObject("WinNT://" & strComputer & "")

By default, binding to the SAM in this fashion returns information about all the local user accounts on the computer; it also returns information about all the local groups on that computer, all the services installed on that computer, all the printers installed on that computer, etc. The truth is, we don’t care about those other things; all we care about are user accounts. (As his colleagues will attest, the Scripting Guy who writes this column is definitely a people person.) Therefore, our next step is to apply a Filter that limits the returned data to user accounts:

colAccounts.Filter = Array("user")

You know that’s a good question: why did we return a collection of all the user accounts? Wouldn’t it have been faster and easier to write a SQL query that searches the local computer and returns a collection consisting of only those users who have an expiring password? To tell you the truth, yes, that would have been much faster and much easier. Unfortunately, though, there’s a bit of a problem with that approach: it won’t work. Unlike Active Directory, we can’t run SQL queries against the SAM. Consequently, any time we need to work with local accounts we need to do things the old-fashioned way: return a collection of all the accounts, and then individually check each one to see whether or not that user has a password that expires.

Which, coincidentally enough, brings us to the next part of our script. After setting up a For Each loop to walk through each account in the collection we then execute this line of code:

If Not objUser.UserFlags AND ADS_UF_DONT_EXPIRE_PASSWD Then

Believe it or not, what we’re doing here is checking to see whether the user’s password ever expires. Password expiration information happens to be stored in a bitmasked attribute named UserFlags. If you aren’t familiar with bitmasked attributes, this type of attribute is a single property that can hold multiple values. (For a complete list of the values stored in UserFlags, see the WinNT User Object documentation on MSDN.) How can a single property contain multiple values? Well, without going into too much detail, a bitmask consists of a series of “switches,” each with a unique value (in this case, the switch for expiring passwords has a value of &H10000). If a given switch is on, that means the property is enabled; if the switch is off, the property is disabled. The syntax for checking the value of a switch is a bit clumsy, but this portion of the code tells us whether or not the user has a non-expiring password (that is, it tells us whether the ADS_UF_DONT_EXPIRE_PASSWD switch is on):

objUser.UserFlags AND ADS_UF_DONT_EXPIRE_PASSWD

Good point: we aren’t interested in users with non-expiring passwords, are we? That’s why we also used the equally-clumsy syntax If Not:

If Not objUser.UserFlags AND ADS_UF_DONT_EXPIRE_PASSWD Then

This is simply VBScript’s way of saying, “If the user doesn’t have a non-expiring password then run the next block of code.”

Note. So then why didn’t VBScript just say that rather than making us use a crazy coding convention like If Not? We don’t know. Chalk that up as one of life’s great unanswered questions, alongside “What is the meaning of life?” and “Why doesn’t Microsoft just give the Scripting Guys dice any time they say they need dice?”

If it turns out that the user does, indeed, have a password that expires we then use this line of code to echo back the user name:

Wscript.Echo objUser.Name

And then it’s back to the top of the loop where we repeat the process with the next item in the collection.

Didn’t someone already ask that question, the one about why we need 1,000 dice? Well, to tell you the truth, we don’t; we actually need 2,000 dice. However, we decided to start with 1,000 and then go from there.

Other than that, well, we can’t really tell you anything; that would spoil the surprise. (Besides, there’s a good chance we’ll never actually implement what we have in mind anyway.) So, in other words, you’ll just have to trust that the Scripting Guys will put those dice to good use. But that’s no big deal; after all, have the Scripting Guys ever let you down before?

Oh, right; we forgot about that. But this time we won’t let Peter anywhere near the project. Promise!