In this age of heightened security, implementing a rigorous network security policy is vital. A strong password policy is a fundamental component of the security equation. Windows Server 2003, Windows 2000, and Windows NT domains can help you implement a secure password policy through Account Policy settings. For example, in Windows 2003 and Win2K domains, you can require that passwords meet a certain complexity level; in Windows 2003, Win2K, and NT domains, you can set the Maximum Password Age policy to force users to change passwords after a set time interval. But you can't use Account Policy settings to implement an automated and random time interval for password expiration. I refer to this type of password policy as a Random Interval Password Age policy.
What exactly is a Random Interval Password Age policy? Implementing a Maximum Password Age policy requires that a user change his or her password after a certain number of days have passed. For example, if the Maximum Password Age policy is set to a 60-day time interval, a user can't use a particular password for more than 59 days. On a particular date, User1 has used her password for 45 days. She then has another 14 days before her password expires (45 + 14 = 59 days). The time interval for password expiration is nonrandom and is set at 60 days. However, if User1 voluntarily changes her password after 45 days, the password expiration counter for her user account is reset to 60 days, the number of days dictated by the Maximum Password Age policy setting in the domain. Thus, by voluntarily changing her password, she made the password expiration interval for her user account random. Rather than being reset at 60 days, the password is reset after 45 days.
Implementing a Random Interval Password Age policy is a valuable security measure for several reasons. For example, the policy makes it extremely difficult for intruders to determine how many days they have to crack a particular user's account password. Also, if passwords are leaked to unauthorized personnel, you can use the Random Interval Password Age policy to implement a swift and effective password change throughout a domain.
In a perfect world, you can implement a Random Interval Password Age policy by simply requesting that all users change their passwords immediately. Unfortunately, we don't live in a perfect world. Just asking users to change passwords is never enough; some users will simply ignore the request. The RndmPwdAge.vbs script addresses this problem by instituting a forced Random Interval Password Age policy in the domain. You can download the code from http://www.winscriptingsolutions.com, InstantDoc ID 38064.
What RndmPwdAge.vbs Does
Based on a specified date, RndmPwdAge.vbs enables the User Must Change Password at Next Logon option for users who don't change their passwords after you request that they do so. The script doesn't enable the option for special user accounts for which you've disabled password expiration. RndmPwdAge.vbs reminds users to change their passwords 2 days and 1 day before enabling the option.
To make sure that your Help desk isn't inundated with calls when you enable this option, the script uses different time frames to configure the option in different organizational units (OUs) and child OUs. For example, you can configure the script so that all user accounts in the Finance OU and its child OUs have a 6-day grace period after you request a password change. On day 4 and day 5, RndmPwdAge.vbs warns users in the Finance OU that they must change their passwords soon. If a user who hasn't changed his or her password logs on within 4 days after the end of the grace period (I call this 4-day postgrace period the local time window), the script enables the User Must Change Password at Next Logon option. If a user doesn't log on within the local time window, the script's catch-all procedure, which contains a domain time window for all OUs in the domain, detects the next logon within the domain time window and enables the User Must Change Password at Next Logon option for the user. RndmPwdAge.vbs specifies a 17-day grace period before the domain time window takes effect, and after it takes effect the time window is 5 days long. Using a domain time window is a good way to address users who might be on vacation and don't log on within the local time window defined for their OUs. The domain time window also addresses user accounts that aren't in the OUs targeted by local time windows.
The Logged-On User
If you make RndmPwdAge.vbs part of a user's logon procedure, when a user logs on, RndmPwdAge.vbs runs in the context of the user account. The script uses the CreateObject function, as the code at callout A in Listing 1 shows, to create the ADSystemInfo Active Directory Services Interfaces (ADSI) object, which implements the IADsADSystemInfo interface that, among other things, returns the user's distinguished name (DN). The script uses the GetObject() function and the Lightweight Directory Access Protocol (LDAP) provider to connect to the Active Directory (AD) user account that corresponds to the user's DN.
The code at callout B retrieves the account's userAccountControl and pwdLastSet attributes and stores their values in an array. RndmPwdAge.vbs uses the userAccountControl attribute to determine whether password expiration is configured for the user account. If it is, the script reads the pwdLastSet attribute to determine when the user last set the password. RndmPwdAge.vbs uses the GetInfoEx method to download just these two attributes instead of all the user account's attributes. The script can function without using the GetInfoEx method, but because you call this script during user logon, you want it to run quickly. Downloading only the necessary attributes speeds processing.
Evaluating the User Account's Password Settings
RndmPwdAge.vbs then determines whether the user account has the Password never expires option enabled. Although enabling the Password never expires option for an account is a security risk, administrators sometimes use that option for special accounts, such as service accounts. Typically, an administrator or account operator is responsible for manually changing these passwords on a regular basis. RndmPwdAge.vbs avoids user accounts that are configured with this option. The CheckForNoExpiration subroutine in the code at callout E determines a user account password's expiration status by evaluating the ADS_UF_DONT_EXPIRE_PASSWD flag in the userAccountControl attribute. When this flag is enabled, the password never expires and RndmPwdAge.vbs quits.
When the Password never expires option is disabled for the user account, the script calls the EvalPwdLastSet function. This function retrieves the pwdLastSet attribute from the user account and uses the return value to perform one of the following actions:
- If the User Must Change Password at Next Logon option is already enabled, end the script.
- If the User Must Change Password at Next Logon option is disabled, determine how long ago the user changed the password. To make this determination, RndmPwdAge.vbs obtains the date when the user last changed the password, then sets the value of the EvalPwdLastSet function equal to the difference between that date and the current date.
A user account's pwdLastSet attribute is never empty. Even if you were to create a user account and clear the account's User Must Change Password at Next Logon option, AD configures the pwdLastSet attribute with the date and time that you cleared the option.
I could have made RndmPwdAge.vbs slightly simpler if I'd used the PasswordLastChanged property of the IADsUser persistent interface instead of using attributes exposed by the IADs core interface. For consistency, I used IADs to obtain both the pwdLastSet and userAccountControl attributes.
Users Who Change Their Passwords as Requested
The script now knows the date when a password was changed for each account for which the User Must Change Password at Next Logon option is disabled. The next step is to determine whether the user has followed your email directions and manually changed his or her password—you don't want to ignore that user's compliance by enabling the User Must Change Password at Next Logon option. RndmPwdAge.vbs compares the days that have elapsed since you requested the password change with the number of days since the user last changed the password. If the former is greater than the latter, the script quits. Otherwise, the script continues.
Implementing Incremental Change
In the code at callout C, the DaysSinceChgReq function returns the number of days since you requested that users change their passwords. If that number is less than the number of days since the user changed the password, you've found someone who ignored your request. The Select Case statement relies on the TopContainer() and SetPwdChange() functions to change the User Must Change Password at Next Logon option for users who haven't changed their passwords.
The TopContainer() function determines whether the user account's parent container is a child of the domain or a child of another OU. The TopContainer() function returns the name of the parent OU that's directly beneath the domain. For example, if a user account is in OU2, which is a child OU of OU1, and OU1 is directly below the domain, TopContainer() returns the name OU1. By setting a grace period and local time window for user accounts in OU1, you can implement the same grace period and local time window for all user accounts in OU2. If you don't use child OUs, the script simply returns the name of the OU in which the user account is located. The TopContainer() function limits the number of grace periods and local time windows you need to configure to work in a domain that contains many child OUs.
The SetPwdChange function requires two values: a grace period (intGracePeriod) and a time window (intTimeWindow). Given those two values, the function can use the DaysSinceChgReq() function to determine how many days have passed since you requested that users change their passwords. If the user whose account is being evaluated logs on within 2 days before the end of the grace period, a warning message appears, reminding the user to change his or her password.
When the value returned by the DaysSinceChgReq() function is greater than the grace period specified by intGracePeriod but less than or equal to the grace period plus the time window (intGracePeriod + intTimeWindow), the script tells the user to change the password at the next logon. Then, the SetPwdChange function sets the pwdLastSet attribute to 0 and commits the change to AD, thereby enabling the User Must Change Password at Next Logon option for the user account.
Configuring the Script
Now that you understand how the script works, you can configure it for your environment. Map out a time horizon for implementing a Random Interval Password Age policy for your network. For example, say you want to make sure that all users change their passwords within 15 business days (3 weeks). Decide on the date on which you'll email all users to tell them that they must change their passwords. In the script, locate the DaysSinceChgReq function, which the code at callout D shows, and change the value of the strDate variable from 11/15/2002 to the date on which you'll send your email message.
Next, determine the parent OUs that contain user accounts whose passwords you want users to change. Locate the code that callout C shows, and customize the code to include a Case statement for each parent OU. Each Case statement should look like
where parent OU name is the name of a parent OU. A call to the SetPwdChange function follows each Case statement. This subroutine requires two parameters. The first parameter specifies the grace period for the specified OU, and the second parameter contains the local time window for user accounts in the OU and its child OUs. The grace period is the number of days you want to give each user to change his or her password before you set the User Must Change Password at Next Logon option for that account. The local time window is the number of days after the grace period ends that the script is active against a particular OU and its child OUs. The local time window is important because the grace period you specify for a particular OU might include a weekend, when users don't typically log on.
Table 1 describes the settings the script uses. On November 15, 2002 (as specified in the StrDate variable in the DaysSinceChgReq function), I sent an email message to all users requesting that they change their passwords. The script applies the settings in Table 1 only to users who didn't change their passwords. Users in the Management OU or its child OUs who didn't change their passwords and who log on Sunday (November 17, 2002) will get a warning that they must change their password within 48 hours. On Monday (11/18/2002), recalcitrant users in those OUs will be warned that they must change their passwords within 24 hours. From Tuesday through Thursday, users who haven't changed their passwords will be informed that the User Must Change Password at Next Logon option is now enabled. The other OUs in the table work in a similar way. To demonstrate the script's flexibility, the table presents different grace periods and a different time window for user accounts in the Finance OU. However, you don't need to make the settings different for each OU.
Catch-All, the last container in the table, looks for users who haven't logged on for an extended period. This setting evaluates users after a 17-day grace period and checks for a 5-day logon time period (the domain time window). This action will evaluate most users who don't log on during the intervals specified for each OU. The catch-all routine applies to all users, not just those in the specified OUs. The script won't affect users who don't log on in any of the specified time periods.
Using logic similar to that in RndmPwdAge.vbs, you can use ADO and the ADSI OLE DB provider to write a search script that creates a result set containing all user accounts that haven't changed their passwords and are configured for password expiration. From the result set, you can use the IADs Put and SetInfo methods to write 0 to the pwdLastSet attribute and commit the change to AD. Then, the next time these users log on, they'll have to change their passwords.
Running the Script
After you customize RndmPwdAge.vbs with the appropriate start date; specify parent OUs, grace periods, and time windows; and send an email message to all users, you can implement the script by calling it from a logon script. Because RndmPwdAge.vbs relies on the ADSI IADsADSystemInfo interface, you can use the script only on Win2K or later clients that log on to an AD domain. For information about how to implement RndmPwdAge.vbs for earlier clients, see the sidebar "Supporting the Bind Operation for Earlier Clients."
To make sure that RndmPwdAge.vbs applies only to clients running Win2K or later, call the script from a logon script implemented in a domain-level Group Policy Object (GPO). From the domain-level GPO, navigate to User Configuration\Windows Settings\Scripts\Logon and add the RndmPwdAge.vbs file to the list of scripts to be processed at logon. If you already have a logon script processing infrastructure in place, you can call the script from another logon script file. Because Win2K and later clients are the only clients that can process GPOs, configuring the script to run from a GPO means that you don't need to worry about script incompatibilities that earlier clients cause.
RndmPwdAge.vbs should help make your network just a little more secure. The most difficult part of customizing the script is specifying appropriate grace periods and time windows. Test the customized script carefully before deploying it domainwide. After you start using RndmPwdAge.vbs, you'll find it invaluable for implementing a Random Interval Password Age policy for your network.