Security administrators and managers frequently request a user account status report—that is, a report of which accounts in a domain are active, which are locked out, and which are disabled. Windows NT administrators must purchase a third-party tool such as BindView's Vulnerability Management Solutions or create a script that uses the Net User command to determine the status of user accounts. Windows Server 2003 and Windows 2000 administrators have an additional option: Active Directory (AD) user accounts have an attribute called userAccountControl that you can check to determine user account status. I've written a script that uses this attribute to generate a user account status report. Listing 1 shows an excerpt from userstatusrpt.vbs; you can download the full script from http://www.secadministrator.com.
The script requires two parameter values: the base distinguished name (DN—i.e., the DN of the domain that the script will search, for example, CN=USERS,DC=DOMAIN,DC=COM) and the name of the output file that will contain the script results. The syntax for invoking the script is
Be sure to enter the entire command all on one line. The output is a comma-separated value (CSV) file that contains a line for each user and three values on each line: the user's DN and username and the user's account status (i.e., Active, Locked Out, or Disabled).
To query AD, the script must first create an instance of the AD connection object ADODB.Connection. (ADODB is the library for ActiveX Data Objects—ADOs.) The script then sets the provider property of this object to the Active Directory Service Interfaces (ADSI) OLE DB provider ADSDSOObject to tell ADODB that the script wants to communicate with AD. The script can then open the connection by using the ADODB.Connection object's Open method and specifying a data source name (in this case, ADs Provider) as its parameter. At this point, the data source name doesn't really matter because the value is just there for convenience. A query string that the script passes to the ADODB.Connection object later in the script names the data source (i.e., the baseDN object that the script will connect to). The code at callout A in Listing 1 shows these preparatory steps for an AD connection.
The next major step is to define the query string. The code at callout B shows that the query has the format "LDAP://baseDN;(filter);returnAttributes ;scope". When a user runs the script, he or she specifies the baseDN value. The filter value is (&(ObjectClass=User) (userAccountControl=*)) because we want the query to return information about only user accounts (specified by ObjectClass=User) that have a userAccountControl attribute (specified by userAccountControl=*). The userAccount Control attribute is crucial for an AD user object because it's an internal flag for account status. Among other things, it signifies whether an account is active, disabled, or locked out. The returnAttributes value is a comma-separated list of attributes that we want the query to return: distinguishedName, samAccountName,userAccountControl. DistinguishedName corresponds to the user account's DN value, sam-AccountName is the username value, and, again, userAccountControl determines the account status. Finally, the script specifies the scope value as subtree because we want to report on all user accounts in the specified baseDN and all its subcontainers.
After the script sets the query string, the script executes the string against the ADODB.Connection object and stores the results in the ResultSet object RS. Then, the script uses the Scripting.FileSystemObject's OpenTextFile method to iterate through the ResultSet object. Before the script can output the results to a file, it must check the userAccountControl attribute (which has a value of 512 if the account is operating as usual) to determine the user account status. The attribute has flags that AD sets to specify the different states an account can be in. The Microsoft article "How to Use the UserAccountControl Flags to Manipulate User Account Properties" (http://support.microsoft.com/?kbid=305144) lists and describes these flags. The script checks only whether the user account has the ACCOUNTDISABLE flag (which has a value of 2) or LOCKOUT flag (which has a value of 16) set. For this check, the script performs a bitwise AND operation that compares the userAccountControl attribute's value with the ACCOUNTDISABLE value, followed by another bitwise AND operation that compares the userAccountControl value with the LOCKOUT value. If the result of the operation is a nonzero value, the flag is set.
After the script determines the status of an account, it writes the entire row of data to the output file. The code at callout C demonstrates how the script generates the output by retrieving the values from the ResultSet object, determining the status based on the userAccountControl attribute, then using the FileSystemObject's WriteLine method to write the row to the output file.
Using the script to generate a user account status report is extremely fast (less than 3 seconds for more than 1000 users) and helps maintain a secure network. For example, you can ensure that accounts that should be disabled are truly disabled and identify frequently locked accounts for further investigation. I tested the script on Win2K Professional and Win2K Server running Service Pack 3 (SP3). If you run the script on a large domain, you'll find that the output file cuts off at a number of users such as 1000 or 2000. This limitation isn't part of the script but rather is imposed by the Lightweight Directory Access Protocol (LDAP) policies on your domain controllers (DCs). To get around this limitation, increase the MaxPageSize LDAP policy value. Or, if you've split your users among several organizational units (OUs), run the script for each top-level OU that contains no more users than the maximum number that an LDAP query can return. For more information about these workarounds, see the Web-exclusive sidebar "Working Around LDAP Administration Limits," http://www.secadministrator.com, InstantDoc ID 40076.
The short, simple userstatusrpt.vbs script generates a useful report that can aid you in securing your network. You could use almost exactly the same code to generate other reports based on AD queries, both in the Windows Script Host (WSH) environment and in Active Server Pages (ASP) pages. Using the userAccountControl attribute value within the While loop to display the corresponding user account status is unique to this particular script, but the code sequence for querying AD would be the same for other AD-query scripts.