Determining the Expiration of AD Domain Passwords

Q: How can I find out the date and time a user's Active Directory (AD) domain password expires?

A: You might think this excellent question has a simple answer. However, several factors add to the question's complexity. To get the answer, you need to ask several questions:

Does the user's password expire? If not, you don't need to calculate an expiration date.

What is the domain's maximum password age? That is, how long can a password be used before it expires? If the domain doesn't specify a maximum password age, you don't need to calculate an expiration date.

At what date and time was the user's password last set? If the password hasn't been set, you can't calculate an expiration date.

I'll present each of these questions as a subtask, then I'll give you a script that lets you calculate and display a user's password expiration date. These subtasks are presented here in VBScript, but I also wrote JScript versions that you can download from the Windows Scripting Solutions Web site at http://www.windowsitpro.com/ windowsscripting, InstantDoc ID 94256. I wrote the final script in JScript for reasons I'll explain shortly.

Subtask 1: Determine Whether the User's Password Expires To perform the first subtask, we need to retrieve the userAccountControl attribute from the user account and determine whether the password doesn't expire bit is set. The userAccountControl attribute is a 32-bit number, but it's not used as a traditional number. It's a bit map; that is, if you convert the number to binary, each binary digit position in the number represents a discrete on/off value. You can then use the logical AND operator to determine whether a particular bit is set. In this case, we're looking for the bit that's represented by the value 65536 (10000 in hexadecimal). For this value, AD uses a named constant: ADS_UF_DONT _EXPIRE_PASSWD. Listing 1, Does PwdExpire.vbs, shows a sample script that names a user account and tests to determine whether the ADS _UF_DONT_EXPIRE_PASSWD bit is set in the user's userAccountControl bit map. (Web Listing 1 is the JScript version of the code.)

Subtask 2: Determine the Domain's Maximum Password Age The next subtask is to retrieve the domain's maximum password age, a value set in the Default Domain Policy Group Policy Object (GPO Computer Configuration\Windows Settings\ Security Settings\Account Policies\ Password Policy). The Group Policy console expresses this value in days; for example, setting the value to 60 means that passwords expire after 60 days. The corresponding AD attribute is maxPwdAge, which is stored as an IADsLargeInteger object (a 64-bit value) that contains the number of 100-nanosecond intervals that a password is valid. An IADsLargeInteger object provides two properties, HighPart and LowPart, which represent the 64-bit integer s high-order and low-order 32-bit values, respectively. To convert the maxPwdAge 64-bit integer to a single number that you can use in a script, use this formula:

(HighPart * 2 32 ) + LowPart

The absolute value of the result of this conversion is a number we can work with in a script. However, as I mentioned, this value tells you only the number of 100-nanosecond intervals. To get the number of days, we can divide the number of nanoseconds by 10,000,000 (giving the number of seconds), and divide the result by 86,400 (the number of seconds in a day). In other words:

days = (nanoseconds / 10000000) / 86400

If the LowPart property of the IAD-sLargeInteger object contains zero, then the entire value is zero, and we don't need to perform the conversion. Listing 2, MaxPwdAge.vbs, shows a short script that returns the domain's maximum password age as a number of days. (Web Listing 2 shows a JScript version of this code.)

Subtask 3: Determine When a User's Password Was Last Set The pwdLastSet attribute of an AD account contains a 64-bit integer that corresponds to the number of 100-nanosecond intervals since January 1, 1601. We can't convert this to a single numeric value (as we can with the domain's maxPwdAge attribute) without losing precision in the calculation, because JScript (and VBScript) don't support 64-bit values containing dates. (The calculation works with the domain's maxPwdAge attribute because maxPwdAge is an interval rather than a date.)

Fortunately, AD performs the calculation for us and stores it in the user account's PasswordLastChanged attribute, which is returned as a date value. The Pass-wordLastChanged attribute won't exist if the password has never been set. In this case, attempting to read the Pass-wordLastChanged attribute will raise error 8000500D (property not found). Listing 3, PwdLastSet.vbs, shows a script that reads a user account's PasswordLast-Changed attribute. Web Listing 3 shows the same code in JScript. Note that the script uses the On Error Resume Next statement in case the attribute doesn't exist.

Obtaining the Date a Password Expires Based on the previous subtasks, we can now calculate the date and time a user's password will expire. Listing 4, WhenPwdExpires.js, is a script that takes a user account name as a command-line argument and displays when that account s password will expire. I decided to use JScript because the JScript Date object stores all date and time values as a number of milliseconds, in Coordinated Universal Time (aka UTC or GMT), since midnight, January 1, 1970. For illustration purposes, I wrote an equivalent VBScript version, Web Listing 4, which you can download from the Windows Scripting Solutions Web site. If you run both versions, you'll notice that the VBScript version can report different expiration times than the JScript version because VBScript date calculations are not locale-independent.

WhenPwdExpires.js uses a single command-line argument: a username. It uses the technique in this article's first Q&A ("Using a Logon to Determine a Distinguished Name," InstantDoc ID 94255) to determine the user's AD distinguished name (DN). If the user doesn't exist, the script returns an error message and exits.

Next, the script next checks to see whether the user's password expires by using the technique I describe in subtask 1. If the user's password doesn't expire, the script returns a message to that effect and exits.

After performing subtask 1, the script uses the technique in subtask 2 to determine the current domain s maximum password age. (Note that this script calculates the maximum password age for only the current domain, so the script won't work correctly if you specify a user in a different domain and the other domain has a different maximum password age than the current domain.) If the current domain's maximum password age is zero, the script returns a message that domain passwords don't expire and exits.

The script then uses the technique in subtask 3 to read the date and time the user's password was last set. If the password hasn't been set, the script returns this fact and exits.

Calculating the Expiration Date At this point, the script has determined that the user's password expires, the number of days a password is valid, and the date and time the password was last set. We can now use the following formula to determine the password's expiration date:

expiration date = date and time password was last set + domain's maximum password age

Callout A in Listing 4 shows how the script performs the calculation, but I need to explain a bit how JScript Date objects work. When you create a Date object, you can initialize it with the number of milliseconds since midnight, January 1, 1970 UTC, which represents the date and time you want to use. The Date object's get-Time method returns its millisecond representation. The domain's maximum password age is a number of days, so the script needs to convert this number into milliseconds (i.e., days X 86,400 X1000). The result is a new Date object that contains the date and time the password expires.

Finally, the script creates a new Date object containing the current date and time. If the current date and time is less than the expiration date, the password has not yet expired, and the script uses the toLocaleString method of the Date object to return a locale-adjusted string representation of the password's expiration date; otherwise, it just reports that the password has expired.