August 29th, 2005

How Can I Get a List of All the Users Whose Passwords Never Expire?

Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I get a list of all the users whose passwords never expire?

— NW

SpacerHey, Scripting Guy! AnswerScript Center

Hey, NW. As you probably know, Internet gambling is illegal in the USA. That’s too bad, because if it wasn’t we’d be willing to bet $10 that the answer to your question will be this: search Active Directory.

And as much as we hate to say “We told you so,” well, guess what:

On Error Resume Next

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.CommandText = _ “<LDAP://dc=fabrikam,dc=com>;” & _ “(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536));” & _ “Name;Subtree” Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst Do Until objRecordSet.EOF Wscript.Echo objRecordSet.Fields(“Name”).Value objRecordSet.MoveNext Loop

You’re right: we should have bet way more than $10. What were we thinking?

Actually, it’s just as well that we didn’t bet on this. The truth is, we cheated: we had some inside information. After all, any time you’re looking for Active Directory-related stuff the answer is going to be the same: search Active Directory. We knew the answer long before you even asked the question.

What we didn’t know, at least in this case, was how to actually conduct our search. We won’t talk too much about the details of writing scripts that search Active Directory; that background information can be found in our two-part Tales from the Script article Dude, Where’s My Printer? However, we will take a minute of two to talk about the query we ended up using in order to conduct the search, a query that might look a bit different from the Active Directory queries you’re used to working with:

objCommand.CommandText = _
    “<LDAP://dc=fabrikam,dc=com>;” & _
        “(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536));” & _
            “Name;Subtree”

If you’re thinking, “Whoa, that doesn’t look like a SQL query to me,” well, there’s a good reason for that: this isn’t a SQL query. Instead, this is an example of the LDAP query syntax, a query syntax that can retrieve the same information as a SQL query, albeit in a much more cryptic fashion.

But if that’s the case, then why did we decide to use this weird-looking syntax rather than the more familiar (and more comfortable) SQL query syntax? Well, as it turns out, the property that determines whether or not a password expires (ADS_UF_DONT_EXPIRE_PASSWD) is not a “stand-alone” property; that is, you can’t get at the value using code similar to this:

Wscript.Echo objUser.ADS_UF_DONT_EXPIRE_PASSWD

Instead, this value is one of several “flags” found in the userAccountControl attribute. The userAccountControl attribute is an example of a bitmask attribute, a single attribute that houses multiple property values; this single attribute tells you whether or not a password will expire, whether or not a password has expired, whether or not an account is disabled, etc. In this case, if the flag with the value 65536 is switched “on,” then the password never expires; if the flag is switched off, then the password does expire.

Note. Yes, we know: many of you have no idea what we just said. For a brief introduction to bitmasks you might take a look at this section of the Microsoft Windows 2000 Scripting Guide.

So what difference does that make to us? Well, it makes a big difference: you can’t (or at least can’t easily) use a SQL query to search for a single value inside a bitmask attribute. You can do this using the LDAP query syntax, however, so that’s what we did. Like the cars driven by a couple of the Scripting Guys, it’s a bit ugly, but it works just fine. (Sorry, guys, but the truth hurts.)

As for the query itself, we’ll briefly explain what the individual pieces do, to give you some idea of how the whole thing works. Thus:

Item

Description

<LDAP://dc=fabrikam,dc=com>

The starting point for the search. We want to search all of Active Directory, so that means starting in the root (i.e., fabrikam.com).

&

Equivalent to the AND operator in a SQL query. We need this because we’re searching for users and we’re searching for a specific value in the userAccountControl attribute. Both of these criteria must be met for an object to be returned.

(objectCategory=User)

Limits the returned data to user accounts.

(userAccountControl:1.2.840.113556.1.4.803:=65536)

Indicates that we want to return only those accounts where the userAccounControl flag for 65536 is switched on; that equates to user accounts where the password doesn’t expire. We’ll explain this werid-looking block of code in a little more detail down below.

Name

The Active Directory attributes we want reported back. We’re asking to get back only a single attribute: Name. To report back additional attributes just tack them on to the end of Name, separating each one using commas:

Name,cn,AdsPath

Subtree

Indicates the type of search. Specifying Subtree causes the script to search all the OUs and containers found in the root of fabrikam.com. Because all the OUs and containers have to be found in the root this causes the script to search all of Active Directory.

As odd as this might look, most of it actually makes sense: once you know what the individual parts represent, it isn’t too hard to read (or even to modify) an LDAP query. Well, OK, with one exception:

(userAccountControl:1.2.840.113556.1.4.803:=65536)

Yes, we know how weird it looks and, yes, we wish some of the werindess had been hidden from view. But, setting looks aside, this is really pretty simple stuff. The 1.2.840.113556.1.4.803 is nothing more than an example of an LDAP matching rule object identifier (OID); in turn, this is just a somewhat-clunky way of saying: “Show me all the objects where the userAccountControl flag with the value 65536 is on.” That’s really all there is to it. That also means that you can search for other attribute values found in userAccountControl simply by substituting the desired value for 65536. Need a list of all the disabled accounts? Here you go:

(userAccountControl:1.2.840.113556.1.4.803:=2)

Cool.

And yes, we know your next question: can you modify our original query to find all the users who don’t have non-expiring passwords? Sure, although the syntax is, again, a bit odd-looking. Without bothering to explain it all, here’s modified code that searches for user accounts where the password does expire:

(!(userAccountControl:1.2.840.113556.1.4.803:=65536))

In this case, the ! indicates “does not equal.” Bet you 10 bucks that you didn’t know that, did you?

Oh. Well, OK then, just let us know where the send the check. This is not going to look good on our expense account. (Or at least it wouldn’t if any of us actually had an expense account.)

Author

0 comments

Discussion are closed.