Reduce your dependencies on external tools
In "Auditing File Ownership," March 2004, InstantDoc ID 41504, I talked about how to check a file's ownership to determine who created it. To ensure compatibility with Windows NT 4.0 systems, I used the Microsoft Windows NT Server 4.0 Resource Kit's Subinacl command-line tool. Recently, readers have been reporting problems using the latest version of Subinacl, which is available from the Microsoft Download Center (http://www.microsoft.com/downloads/details.aspx?FamilyID=e8ba3e56-d8fe-4a91-93cf-ed6985e3927b&displaylang=en). After spending many hours trying to isolate the source of the problem, I determined that Subinacl's output doesn't work correctly when it's piped to a Find command. This realization prompted me to redesign the script from the ground up, and I was pleasantly surprised by how much simpler the task became.
When using command-line tools that we don't have immediate control over in our scripts, the biggest problem we face is that we rely on these tools' developers to provide a consistent output format. When we write scripts, we want to be able to reliably use a For command to parse the output and get only those fields in which we're interested. However, as newer versions of these tools become available, format changes can potentially break a script. For example, if one version of a command-line tool outputs a username as the third string of text on a line of output, but the subsequent version changes the username output to the fourth string of text, the script that looks for the username will no longer work. You'll have to rewrite it.
When I'm writing scripts, my goal—particularly when I use Windows shell scripting—is to minimize the number of dependencies I have on external tools. In "Auditing File Ownership," my script relied on two resource kit tools: Subinacl and Getsid. The latter addressed the additional complexity that's introduced when Subinacl displays the file owner's SID instead of the actual username. However, I've decided to limit the script for use on only Windows 2000 or later workstations, so these dependencies are no longer necessary.
As Simple as Dir
To determine a file's owner without using the Windows GUI, you need only type
at a command prompt. If you don't specify a filename, this command will automatically display a directory listing of all the files and folders in the current directory, along with the file's owner displayed in the fourth column in DOMAIN\username format. With this knowledge, our script is theoretically reduced to a simple loop: List the names of every file in a directory and its subdirectories, then compare the owner information for each file with the username we're looking for.
Working Out the Details
The findownfiles.cmd script, which Listing 1 shows, takes three parameters—namely, the directory path in which to search, the username to look for, and the name of the output file to which the script will write. All three parameters are required, so the script starts by checking whether the user has specified all the parameters. To do so, it checks whether %3—the variable that automatically contains the third command-line parameter if it exists—isn't blank. I like to enclose %3 in hash marks (#) and check whether #%3# == ##. This way, even if %3 contains quotation marks, the string comparison won't cause any problems.
Next, as callout A shows, I assign these parameters to variables within the script so that I can readily use them later. (Although the system automatically makes %1, %2, and %3 available to us, these names aren't intuitive, so I reassign more meaningful variable names to them.) Directory names can have spaces in them (e.g., C:\Documents and Settings), so the user will have to enclose such names in double quotes so that the script treats them as one argument (e.g., "C:\Documents and Settings"). If you simply assign this value to a variable within your script, the value will also contain the quotation marks. A neat and simple trick for automatically stripping the starting and ending quotation marks from the string is to use a tilde (~) before the variable name. For example, the command
(where targetdir is the directory path to search) stores the first parameter in the targetdir variable without the starting or ending quotes. Therefore, regardless of whether %1 contains the string "C:\Test" or C:\Test, the value of targetdir would still be C:\Test. (For information about the use of modifiers, see the sidebar "Manipulating Command-Line Parameters.")
Next, we output some header information—the directory we're searching and the username we're looking for—to the output file. Inside a For loop, as you see at callout B, we run the command
dir /s /b /a-d "%targetdir%"
to get a list of all the files in the directory path stored as targetdir and all its subdirectories. For each of these files, we run the checkfile section, which runs two For loops. The first runs Dir /Q on the filename, using a backslash (\) as a delimiter. When you run the Dir /Q command against a filename, it provides the output that Figure 1 shows. By using the backslash to delimit this file, the second token will contain the string
We store this value in a variable called owner. Because the actual owner of the file is testuser1, we want to get rid of the extra information in this string before we perform a comparison. To do so, we run the string through a second For loop, this time leaving the default delimiter (white space) and storing the first token in the owner variable. The owner variable now contains the testuser1 value, which is exactly what we want, as you can see at callout C. Finally, we perform a case-insensitive string comparison between the owner variable and the userid variable, which contains the username we're looking for. If a match exists, the script sends the current filename to the output file, as you see at callout D.
Getting the Script to Work
The elimination of all dependencies on external command-line tools means that the script is self-contained. However, when you run this script, you'll need to keep a few things in mind. First, be sure to use the syntax
findownedfiles.cmd dir_path userid outputfile
For example, to find all the files in E:\Users owned by the user with username testuser and output the contents to C:\results.txt, you would use
findownedfiles.cmd E:\Users testuser C:\results.txt
Remember, if the directory path contains spaces, be sure to enclose the path in quotation marks.
Many administrators forget about the power of the Windows command shell. This simple script, which uses only built-in commands, demonstrates that you can accomplish tasks that would be impossible through the Windows GUI. The script also shows that you can always improve your existing scripts by investigating new built-in features that help with particular tasks. I tested this script on Windows Server 2003, Windows XP Service Pack 1 (SP1) and SP2, and Win2K SP3 and later.