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.