One truth about companies is that people come and go. Another truth is that sometimes a person is very unhappy when he or she leaves a company. If an unhappy ex-employee has keys to the office, you change the locks. If the ex-employee has a user account, you need to make sure that he or she can't use it—especially if that user account has privileges that would let the employee do damage. This month, I show you how to programmatically enable and disable user accounts and set passwords for accounts. I also show you a script you can use to permit users to change their own passwords, even if you've disabled access to the Windows Security dialog box for security reasons.
Enabling and Disabling Accounts
Even if an employee's gone only temporarily, rendering his or her account unusable while he or she is gone is often a good idea so that intruders can't use the account to break into the system. (As you might remember, the security breach in Clifford Stoll's The Cuckoo's Egg—Doubleday, 1989—originally took place through the account of an authorized user who was out of the office for an extended period.) However, you don't want to delete an inactive account, because if you do, you'll have to recreate all the security settings associated with that account if or when the user returns. Rather, as any experienced administrator knows, you should disable the account while it's not in use, deleting it only when you're positive that you'll never need the account again.
To make disabling and enabling user accounts easy, you can use the script that Listing 1 shows to disable a user's account when the user leaves and reenable it when he or she returns. The script is simple: It connects to the account in question, then sets the AccountDisabled property. (You can also disable an account by editing a user flag associated with the account, but the method I've shown here is simpler and just as effective for our purposes.) You can make the same code do double duty (i.e., enable or disable the account) by making the value of AccountDisabled an argument to the script; a short Help subroutine tells the user what value to enter to get the desired result. Because the script uses the WinNT namespace, it'll work with both SAM and Active Directory (AD) domains. When you refresh the view in the Microsoft Management Console (MMC) Active Directory Users and Computers snap-in on the domain controller (DC), you'll see that the named account has been enabled or disabled as specified.
Changing Account Passwords
Sometimes disabling the account isn't an appropriate solution—for example, you can't disable an administrative account that multiple people use just because one person leaves under unhappy circumstances. In such a case, you'll need to change the account password.
Active Directory Service Interfaces (ADSI) supports two methods for changing passwords: ChangePassword and SetPassword. ChangePassword requires that the person doing the changing know the original password. ChangePassword isn't much of a tool for administrators because in an ideal world, administrators don't know user passwords. However, ChangePassword is handy when you've used Group Policy or some other means to shut off access to the Windows Security dialog box but want to let users change their passwords. SetPassword, in contrast, just overwrites the existing password with a new one.
A script that lets the currently logged-on user change his or her password should do the following:
- detect the name of the currently logged-on user
- ask for the user's current password
- prompt the user for the new password
- prompt the user to confirm the new password
- compare the two passwords and reject them if they don't match
- change the password and write the new password to the security database
- report that the password has been changed
I've shown you how to script most of these steps in previous columns, so I focus here on how the steps fit together in the script that Listing 2 shows.
First, you need to identify the currently logged-on user. As you might recall, the WshNetwork object's UserName property returns the username of the currently logged-on user. So, to obtain the username, you create the WshNetwork object (WScript.Network), associate that object with a variable (oNet), then use the UserName property to retrieve the username, as the code at callout A in Listing 2 shows.
Now that you have the username for the current user, you can use the InputBox function to ask the user for the current and new passwords. Creating a little subroutine to handle these tasks is the simplest approach. The CheckPwd subroutine uses the global variables sPword1, sPword2, and sPword3 to store the current and new passwords. The subroutine prompts the user for the current password (sPword1), for the new password (sPword2), and for the new one again (sPword3) for confirmation. If the new password and the confirmation password don't match, the subroutine informs the user and starts over. The comparison of sPword 2 and sPword 3 is case sensitive, so FROG and frog won't match.
As I explained in an earlier column, VBScript's InputBox function always displays an OK button and a Cancel button and can take as many as three arguments, as in
InputBox(prompt, title, default)
where prompt is the text within the message box, title is the text in the title bar, and default is the default value. Because you separate these arguments with commas, you can't use commas within the prompt or title. Instead, you must use the concatenation operator (&) as I've done in the CheckPwd subroutine.
Using the InputBox function has one problem: It doesn't support input masking. Thus, when Joe User types swordfish into the dialog box as his password, he'll see swordfish on the screen, rather than the preferred row of asterisks (*********). If you're running the password-changing script on Windows 2000 or earlier, you'll have to live with this limitation, but if you're running the script on Windows Server 2003 or Windows XP, you have another option. These versions of Windows support the scriptpw.dll component, which masks user input. Scriptpw isn't perfect—it doesn't represent each character with one asterisk to help the user see what he or she is doing—but it's more secure than letting users type unhidden passwords into an input box.
The HidePwd subroutine shows the code for using Scriptpw. HidePwd's code is similar to that of CheckPwd except that HidePwd prompts the user for input from the command line instead of from a dialog box and HidePwd hides the user input. The echoed line feed after each request for the password inserts a carriage return after the script output. Without the return, all the output would be on one line, which is ugly.
So that you don't have to have two versions of the password-changing script, Userpword.vbs tests for the OS, then runs the appropriate password-collecting subroutine. The object that represents the computer has an OperatingSystemVersion property that you can query. You don't need an exact match. You just need to know whether the version is less than 5.1, which is the version number for XP. The script already used the WshNetwork object to collect the computer's name, so the code at callout B in Listing 2 checks the OS version on the local computer.
Whichever password-collecting subroutine it uses, the script stores the new password in sPword2 and the old password in sPword1, then plugs them into the code at callout C, which actually changes the password. If the user doesn't provide the correct old password, the script fails with a relatively self-explanatory error: The specified network password is not correct.
Setting Account Passwords
To set a password for an account for which you don't have the current password, you take a slightly different approach. Your script needs to
- accept the input of a username and domain
- look up the username in the security database
- prompt for the new password
- set the new password
- require the user to change the password at the next logon (so that the administrator will no longer know it)
- record that the password has been changed
The password-setting script called Adminpword.vbs, which Listing 3 shows, uses a lot of code already written for the first two scripts. From Disableuser.vbs, Adminpword.vbs gets the code that accepts a username and domain and connects to the appropriate user account. From Userpword.vbs, Adminpword.vbs gets the subroutines that prompt the user for a new password. The subroutines in Adminpword.vbs are slightly modified because you don't need the current password. (You could hard-code a new default password into Adminpword.vbs, but doing so isn't a good idea. If you hard-code a password into a .vbs file, it's saved in plain text.) The remaining tasks of setting the new password, forcing the user to change it at the next logon, and recording that the password has been changed are pretty simple to code.
To set the new password, you substitute the SetPassword method for the ChangePassword method and supply only the new password, as the code at callout A in Listing 3 shows. The PasswordExpired property controls whether users must change the password when they next log on. If the property's value is 0, the password is still valid; if 1, the password has expired. Therefore, to force the change, you just write a value of 1 to this property. If oUser represents the user account, the code at callout B in Listing 3 forces the change.
The final step in disabling an account or changing its password should be to note the change in the Event Log so that you have a record of it. As you'll recall from a previous column about editing the Registry, to write to the Event Log, you need to connect to the WshShell object and use the LogEvent method with the event type and some explanatory text as arguments. In this case, the event is informational, so it's a type 4 event. The time and date will be recorded with the event, so you don't need to include them. To record password changes, include event-logging code at the end of the main body of the script, as the code at callout C in Listing 3 shows.
Disabling unused user accounts and changing passwords are important tasks that need to be as simple as possible to execute so that you'll actually get them done. But changing a password from Win2K's GUI, for example, requires six steps. By using the scripts in this column, you can easily enable and disable user accounts, let users change their own passwords without having access to the Windows Security dialog box, and set new passwords on important accounts. You've also seen how to check the OS of the computer on which a script is running so that you can use the code that's appropriate to that platform.