When you're getting started with scripting, you're generally thrilled about the magic of automating simple operations. After some initial success, you might decide to turn your attention to more advanced scripting tasks that require a chain of commands and utilities to generate the information you want. Using the results from one command as input for the next command is a common scripting technique. But when using this technique, you must build in code logic so that if one command fails, the script will skip the next chained command and enter an appropriate log entry.
The GetGroupStats.bat script, which Listing 1, page 10, shows, uses a built-in Nbtstat command and two third-party utilities (PsLoggedOn and MemberOf) to create a report that lists how many local groups a logged-on user is a member of. In explaining this script, I introduce you to a new way to control script flow and make your scripts easier to read and more similar in coding syntax to the Perl and VBScript languages. (Since the days of MS-DOS, the Goto and Call commands have typically served as flow-control mechanisms in command-shell scripting. But using conditional If statements is the preferred method for controlling flow in most other scripting languages.)
SID Token Bloat
I wrote GetGroupStats.bat to address the SID token bloat problem, which the Microsoft article "Group Policy May Not Be Applied to Users Belonging to Many Groups" (http://support.microsoft.com/?kbid=263693) describes. According to the article, Windows might not properly apply Group Policy to a user who is a direct or indirect member of more than 70 to 80 groups. Because of some complex group memberships within my company's environment, I wasn't able to determine whether users in the user community belonged to that many groups. Looking at a small sampling of users didn't fully reveal whether the company had a potential problem, and I didn't have a full list of users because the user community consists of many organizational units (OUs) that I don't administer. I did have a list of computers that users log on to, so the requirements of the script were to parse that list for the PC names, determine whether each computer was on and who was logged on, then create a spreadsheet report listing how many groups each logged-on user belonged to. I then used Microsoft Excel to sort the report's data.
Another use for GetGroupStats.bat is to identify any group membership anomalies or security problems that you might have in your user community. If you find that the average nonadministrative user in your environment is a member of 10 groups and you locate one user who belongs to 25 or 30 groups, you'll want to investigate. Perhaps the user was granted additional memberships after he or she moved to a new department, or perhaps a novice account operator granted the user excessively lenient access. You can identify potential problems easily by sorting the columns of Excel's tab-separated value (.tsv) output file.
The PsLoggedOn and MemberOf Utilities
GetGroupStats.bat uses Sysinternals' PsLoggedOn utility to determine who is logged on. The script also uses Joe Richards's MemberOf utility to determine how many groups each user belongs to. (Windows NT 4.0 provides the Findgrp and IfMember utilities to enumerate group memberships, but if you have nested local groups in your environment, these old tools will understate the actual number of group memberships.) MemberOf can also give you the group names and types, but GetGroupStats.bat uses the utility only to capture a total number of group memberships for each user.
Nbtstat as a Ping Alternative
You can use the Nbtstat command and the Ping command interchangeably to determine whether a machine is online. You should always verify that a machine is on before performing any other function or query because some commands have long timeout periods that can cause delays if a system doesn't respond. The following is an example of the basic Nbtstat command that determines whether a PC named workst1 is turned on:
The -a switch retrieves the specified remote machine's name table. After embedding the Nbtstat command in a For loop, the script looks for the string "——", which is part of a table that Nbtstat outputs when it returns information about an online machine. The code block looks like the code that Listing 2 shows.
The code that Listing 3 shows uses the PsLoggedOn utility to determine whether the user is logged on.
Say Farewell to Call and Goto Commands
The Call and Goto commands let you route the flow and simulate the If...Then...Else statement that exists in VBScript. If you've been writing your own command-shell scripts, you've likely been using the For command to set environment variables and the Call or Goto commands to move script flow to the next operation. The Call and Goto commands are also common following conditional If statements.
Most scripts, however, contain several code blocks similar to the one in Listing 2. Using Call and Goto commands in blocks such as these makes following script flow difficult because you must scroll through the code to figure out where flow is going and in what order the code is executing. Furthermore, a Call command sends flow to another location, then returns to the code below the sending Call, whereas a Goto command takes out flow and never returns it to the sending Goto. As your scripts become longer and more complex, you'll have a more difficult time figuring out all the flow possibilities when you need to determine why a script isn't working.
You can take command-shell scripting a leap forward by leveraging the new Setlocal ENABLEDELAYEDEXPANSION command, which Windows 2000 and later versions support. The code that Listing 4 shows is the same as the code in Listing 2, but without the Goto and Call commands.
Windows shell scripting has never looked like this before. In Listing 4, note the nested For command and the use of the Setlocal ENABLEDELAYEDEXPANSION command, which lets you set a variable and start using it immediately, while still in the For command loop. The ability to use variables within a For command, without having to use the Call command to move out to another code block, is powerful. This feature will make your command-shell scripts similar in flow to other scripting languages you might want to learn in the future. In addition, your scripts will be easier to read and follow. (For more information about using this technique, see the sidebar, "Tips for Using and Debugging the Nested For Command.") For more information about using Setlocal and the delayed environment variable expansion feature, open a command prompt and type
Running the Script
I tested GetGroupStats.bat on Windows XP Service Pack 1 (SP1) and Win2K SP3. To get the script working, take the following steps:
- Download PsLoggedOn from http://www.sysinternals.com/ntw2k/freeware/pstools.shtml, and download MemberOf from http://www.joeware.net/win32.
- Configure the script with the path to a folder that contains these two utilities. Note that some utilities, including PsLoggedOn, are sensitive to spaces in pathnames. Be sure to specify a pathname that's free of spaces.
- Configure the script with the PC list input file location. Place a list of your target PCs in this file with one entry per line.
- Configure the output file location. The script will create a .tsv file that you can open in Excel.
- If you're dealing with user accounts and groups across several domains, the MemberOf utility will return more accurate results if you specify your Global Catalog (GC) server as the target server for the command. To do so, add -s servername to the arguments on the MemberOf command, where servername is the name of the target server.
After you get GetGroupStats.bat working, you can easily capture your group membership metrics. This report could become a part of your regular security-audit deliverables to management or just a great way to quickly identify access trends in your user community.