A client recently asked me whether it was possible to identify the name of every file that a specific user had created in a directory structure containing more than 20,000 files. At first this request seemed absurd—the only way to find this information would be to check the ownership of each file individually and compose a list, a task that would take forever. Then, I remembered that a few years ago I'd used a resource kit utility called subinacl.exe to change the ownership of files. At the time, I'd discovered that among the tool's many features, it lets you view a file's properties, including the SID of the user who owns the file. Knowing that Subinacl could help me locate the information I needed, I created a script called ownedby.cmd, which Listing 1 shows, to audit file ownership.

More than one version of Subinacl exists—Microsoft released the first version with the .Microsoft Windows NT Server 4.0 Resource Kit and a second, updated version with the .Microsoft Windows 2000 Resource Kit. The two versions vary slightly because Microsoft added some functionality in the Win2K version to accommodate some of the new features in the new OS. However, for the purposes of ownedby.cmd, both versions of Subinacl work the same. For a complete list of options for this tool, from the command prompt type

subinacl.exe /help /full

To use the Subinacl command to view details about a file's ACL, simply type

subinacl.exe /file filename

where filename is the file's name. Because you won't need all the information that this command provides, you can add the /noverbose switch to display only a summary of the file ACL information. Figure 1, page 6, shows sample output of the Subinacl command with all the switches specified. As you can see, one of the lines contains the string /owner = S-1-5-32-544, which signifies that the file owner's SID is S-1-5-32-544.

You might be wondering what good knowing the owner's SID will do you. After all, not too many administrators know their users' SIDs by heart. The approach that ownedby.cmd takes is to start with a known username, determine its SID, then find all files associated with that SID. So, for the next step in the process, you need a tool to convert usernames to SIDs. Fortunately, Microsoft includes just such a tool, called Getsid, in the Win2K and NT 4.0 resource kits.

Getsid.exe compares the user SIDs of two accounts. The command syntax is

getsid \\server1 account1 \\server2 account2

where server1 and server2 specify the servers that you want to query to get the SID information and account1 and account2 specify the user accounts you want to compare. Ownedby.cmd uses Getsid to obtain the SID for a specified user account, so in this case, server1 and server2 will be the same, as will account1 and account2. For example, to get the SID for user JDoe from domain controller (DC) mydc, I'd type

getsid \\mydc JDoe \\mydc JDoe

Running the Script
You now have the basic tools to make the script work. Subinacl provides a file owner's SID, and Getsid provides the SID for a specified username. With these tools, you can perform username­to­file-ownership comparisons. Ownedby.cmd's scope is to output the filename and path for every file owned by a specified user within a particular directory structure. The script requires one hard-coded parameter (the name of the DC) and three runtime parameters: the username (userid), the root of the directory structure (root_directory), and the output file's name (outputfile). The final syntax for the Ownedby command is

ownedby.cmd userid root_directory outputfile

For example, if I type

ownedby.cmd JDoe G:\users results.txt

the script outputs to results.txt all the files that user JDoe owns in the G:\users directory and its subdirectories.

When the script runs, it must determine the SID of the specified user ID, then store that SID in the sid variable for later use, as the code at callout A in Listing 1 shows. This code first clears the sid variable in case it was previously defined. The script then determines the user's SID by running Getsid in a For loop and filtering the output to display only entries that contain the string "S-", which is the prefix for a SID. The script retrieves the SID value from the seventh string ("tokens=7") of Getsid's output and stores this value in the sid variable. The %DC% value is the hard-coded name of the DC, which is defined earlier in the script. The %USERID% value contains the user ID that you specify in the command-line parameters when you run the script.

Next, as the code at callout B shows, the script runs the Dir command in a For loop to obtain a list of files that the script will compare against the user ID (%USERID%) value. Several switches tell the command to not include header or summary information for all subdirectories (/s), to output the results in bare format (/b), and, because you only care about files and not directories, to output only objects whose "attribute is not directory" (/a-d). The ROOTDIR variable contains the value of the directory that you want to search. Using the earlier example, the ROOTDIR variable would contain the string G:\users.

The script then executes the :checkowner section of the script for each file that the Dir command returns. The :checkowner section first runs Subinacl against the filename and stores the owner information in the owner variable. The script then determines whether the specified user owns the file by echoing the owner string retrieved from the Subinacl command, then piping the owner string to the Find command to check for a match between the specified user's SID and the SID of the file's owner. When ERRORLEVEL equals 0, a match has been made and the script sets the found variable to 1, as the code at callout D shows. Because the script clears the found variable before setting it, you can determine whether the found flag has been set by checking whether the script has defined the found variable. If the variable has been defined, a match was found and the script outputs the filename to the output file, as the code at callout E shows.

If you look at the script, you'll notice that it also checks the owner variable against the USERID parameter, as the code at callout C shows. This step needs to occur because if you execute the script against an NT 4.0 server, the Subinacl command will return an actual username rather than the cryptic SID. Because ownedby.cmd includes this extra bit of code, you can execute the script on newer Windows Server 2003 and Win2K systems as well as older NT 4.0 systems.

Getting the Script to Work
Although ownedby.cmd is relatively short and straightforward, it has some minor idiosyncrasies. I've summarized a few details that you need to address to execute the script as written:

  1. Make sure the subinacl.exe and the getsid.exe files are in the same directory as the script, or at least in a directory that's part of your PATH environment variable.
  2. In the Name of DC portion of the script, replace mydc with the name of one of your DCs to ensure that the Getsid command knows where to direct its query for SIDs.
  3. If you execute the script on Windows XP or Win2K, you'll see header text like the following at the command prompt for each file that Subinacl queries:
  4.                               Default Sam Server will be 0                              Default Sam Server will be 0
    These lines appear because Subinacl can't redirect this header text, so ownedby.cmd can't hide it from view to make the output cleaner.
  5. Ownedby.cmd can't determine which files an administrator account user ID owns. When an administrator account creates a file, Windows automatically assigns the ownership of the file to the Administrators group. As a result, you can't determine which administrator created the file unless you enable file auditing on your server.

I wrote and tested ownedby.cmd on Win2K Service Pack 4 (SP4) and SP3 and on NT 4.0 SP6a, but the script should run on other configurations as well. Although the script might appear to have limited applicability, I've run this report by request at least a dozen times for a variety of reasons—usually to audit files created by a user when the user isn't available to answer questions regarding the files. Ownedby.cmd gets the job done with very little effort on your part, and you'll look like a lifesaver each and every time.