Get the information Windows' disk-quota feature doesn't provide
Have you ever needed to know which files a user owns and how much disk space they take so that you can better manage your disk storage? You can't get this information from the Windows GUI or by using the Windows scripting runtime. I've written an ActiveX DLL and a VBScript script that output a list of files owned by a particular user and the files' sizes.
Disk Quota Limitations
Windows 2000 and later versions have a disk quota feature that you can use to track or limit disk usage for users of an NTFS volume. To view or configure a disk quota, right-click the desired drive letter in Windows Explorer, click Properties, and go to the Quota tab, which Web Figure 1 (http://www.windowsitpro.com/windowsscripting, InstantDoc ID 46487) shows.
Click Quota Entries to see a list of users and the amount of disk space each one is using. This usage information comes from NTFS file ownership information. The Quota Entries dialog box lets you override the default quota entries on a user-by-user basis, add and delete quota entries, and even import and export quota entries. Unfortunately, you can't configure quota entries based on groups, and the quota feature can be enabled or disabled only on a volume-by-volume basis. However, even with these limitations, the disk quota feature can be useful.
The Quota Entries window displays a list of file owners and the amount of disk space they use, but the OS doesn't provide a way to obtain a list of files owned by a user unless you right-click the user's entry and choose Delete, then Yes. If the user owns any files on the volume, Windows will then display a dialog box that lets you delete or take ownership of the user's files. This dialog box shows the files the user owns, but it doesn't display the amount of disk space they use. It's also not possible to export the file list to a file for reporting purposes. This limitation might be a problem when your users start running out of disk space.
To address the need for a listing of files and disk space by file owner, I decided to write a script. The Windows scripting runtime doesn't provide a way to retrieve a file's owner, and although it's possible to obtain a file's owner using Windows Management Instrumentation (WMI), I decided against it for performance reasons. Instead, I wrote an ActiveX DLL, GetOwner.dll, that retrieves a file's owner. I also wrote a VBScript script, OwnedBy.vbs, that uses the DLL to output a delimited list of files owned by a particular user, their sizes, and their containing folders. You can redirect this output to a file for later analysis and import it into a spreadsheet or database if needed.
You can download GetOwner.dll, OwnedBy.vbs, and the DLL's source code from the Windows Scripting Solutions Web site. Some readers might be unable to use a custom DLL even when the source code is available. For these readers, I wrote a version of OwnedBy.vbs that uses ADsSecurity.dll from the Active Directory Service Interfaces (ADSI) software development kit (SDK). For information about using ADsSecurity.dll, see the Web-exclusive sidebar "Using ADsSecurity.dll Instead of GetOwner.dll," InstantDoc ID 93406. However, if possible, I recommend using GetOwner.dll for performance reasons: It was almost nine times as fast as ADsSecurity.dll in my informal tests.
To run OwnedBy.vbs on Windows 2000, you must have VBScript 5.6 installed. This requirement is met if you've installed Microsoft Internet Explorer (IE) 6.0. Windows Server 2003 and Windows XP have VBScript 5.6 installed as part of the OS.
The script also requires that GetOwner.dll be registered on the local computer. To do this, copy GetOwner.dll to a folder (e.g., %systemroot%\system32) and type the following command at a command prompt:
regsvr32 \[/s\] path\getowner.dll
where path is the path to getowner.dll. The /s (silent) option suppresses the confirmation dialog box. To unregister the DLL, add the /u option after the regsvr32 command. To unregister it silently, use both the /u and /s options. You must be a member of the local Administrators group to register or unregister the DLL.
I used Visual Basic (VB) 6.0 to compile GetOwner.dll. The VB runtime (msvbvm60.dll) is installed by default with Windows 2000 and later.
Because OwnedBy.vbs uses the command window for its output, you must execute the script by using the CScript host. To set CScript as the default host for the current user, type the following command at a command prompt:
cscript //h:cscript //nologo //s OwnedBy.vbs uses the following command-line syntax: \[cscript\] ownedby.vbs folder(s) \[/s\] \[/d:char\] \[/o:owner\] \[/nh\] \[/ns\]
The cscript keyword is required only if CScript isn't the default script host. The other command-line options can appear in any order.
You must specify one or more folder names in place of folder(s). If a folder name contains spaces, enclose it in quotes. If you also want information for files in subfolders, specify the /s option. The script produces output in column format. By default, the columns in the output are separated by a tab character. To specify a different delimiter character, use the /d option followed by the character; for example, /d:; produces a semicolon-delimited list.
You can specify that you want to list only files owned by a particular user. To do so, use the /o option and replace owner with the username in domain\username format. If you don't specify a domain name, the domain you're logged on to is used. If you don't specify the /o option, OwnedBy.vbs lists all files owned by any and all users.
By default, OwnedBy.vbs outputs a header line that identifies each column (i.e., Owner, ParentFolder, Name, Size). The headings can be helpful when you're importing the report into a spreadsheet or database, but if you don't want the script to produce the header line, use the /nh option.
The script also produces a summary line after the output in the form
n byte(s) in x file(s)
where n represents the cumulative sizes of the files and x is the number of files found. If you want to suppress this summary line, use the /ns option.
You might want to save the script's output for later review; to do this, redirect the script's standard output to a file. For example, the command
ownedby.vbs d:\ /o:corp\smithj /s > d:\smithj.txt
lists every file on the D volume owned by corp\smithj and saves the report to the file d:\smithj.txt. If the script encounters an error reading a file or directory, it writes the error message to standard error, which by default is the command window. You can redirect standard error to a file; for example,
ownedby.vbs d:\ /o:corp\smithj /s > d:\smithj.txt 2> d:\errors.log
This command sends only error messages to the d:\errors.log file.
You can also redirect standard output and standard error to the same file; for example,
ownedby.vbs d:\ /o:corp\smithj /s > d:\smithj.txt 2>&1
This command saves both standard output and standard error to d:\smithj.txt. Note that if you run OwnedBy.vbs on an entire volume, it will probably report a slightly lower amount of disk space used for a user than the Quota Entries window will due to NTFS overhead.
Inside the Main Subroutine
OwnedBy.vbs's Main subroutine, which Listing 1 shows, first declares its variables and then checks for command-line arguments. If the /? option or no folder names are on the command line, the script calls the Usage subroutine, which displays a brief usage message and exits.
Next, the script calls the ScriptHost() function to determine which executable is running the script. If it's not CScript, the script calls the Die subroutine to end the script with an error message and a nonzero exit code.
If CScript is running OwnedBy.vbs, the Main subroutine attempts to create an instance of the ListByOwner object. Because the class definition for the ListByOwner object exists in the OwnedBy.vbs script file, the Main subroutine uses VBScript's New keyword rather than the WScript object's CreateObject method. If OwnedBy.vbs fails to create the ListByOwner object, the script exits with an error message.
If the ListByOwner object is created successfully, the script checks for the presence of the /o, /d, /s, /nh, and /ns command-line arguments. The /o, /d, and /s arguments correspond to the ListByOwner object's Owner, Delim, and Recurse properties (I'll discuss these shortly). The Boolean variables blnHeader and blnSummary are set to False if the /nh and /ns options, respectively, exist on the command line.
If the blnHeader variable is True (i.e., if the /nh option isn't present on the command line), the script executes the ListByOwner object's OutputHeader method. The script then disables the default VBScript error handler using the On Error Resume Next statement and proceeds to iterate the collection of unnamed command-line arguments (i.e., the folder names) using a For Each loop.
Inside the For Each loop, the script sets the ListByOwner object's Folder property equal to the command-line argument. If the ListByOwner object raises an error (e.g., if the folder doesn't exist), the script writes the error message to standard error; otherwise, it calls the ListByOwner object's Run method. The Main subroutine's final step is to test the blnSummary variable. If the value is True (i.e., if the /ns option isn't present on the command line), the script calls the ListByOwner object's OutputSummary method.
Understanding the ListByOwner Object
The actual work of the OwnedBy.vbs script is done by the ListByOwner object. The properties used by the ListByOwner object, as well as the private variables each property uses, are listed in Table 1. The ListByOwner object's methods are listed in Table 2.
When the script creates an instance of the ListByOwner object using VBScript's New keyword, the Class_Initialize event procedure is called automatically to initialize the object's private variables and perform other tasks that need to occur when the object is created.
The first thing the ListByOwner object attempts to do is create an instance of the GetOwner.Owner object, which will fail if the GetOwner.dll file isn't registered on the system. The event procedure code performs its own error handling—if the CreateObject method raises an error, the ListByOwner object raises its own error rather than aborting the entire script. The script's Main subroutine traps this error and exits gracefully.
Next, the Class_Initialize event procedure creates a private Scripting.FileSystemObject object reference and sets the default values for the properties. After the Class_Initialize procedure finishes, the Main subroutine's objLBO variable contains an initialized ListByOwner object.
Setting the Folder Property
Public Property Let statements, which are part of the VBScript language, define the property procedures that perform the ListByOwner object's property handling. Each of the property procedures sets a corresponding private variable as shown in Table 1. When setting the Folder property, the code performs its own error handling and raises an error if the FileSystem-Object object's GetFolder method fails. In this way, the Main subroutine can detect the error gracefully and continue on to the next folder specified on the command line.
Inside the ProcessFolder Subroutine
When the Main subroutine calls the ListByOwner object's Run method, it executes the ProcessFolder subroutine with the private objFolder variable as its argument. The ProcessFolder subroutine does the actual work of iterating through the collections of files and folders.
The ProcessFolder subroutine does its own error handling; it disables VBScript's default handler by using the On Error Resume Next statement. Next, if the private blnRecurse variable is True, the subroutine iterates through the SubFolders collection of the initial folder using a For Each loop and calls itself for each subfolder.
Next, the subroutine iterates the collection of files in the folder using a For Each loop. Each file's owner is retrieved by calling the objGetOwner object's GetOwner method, which returns a string containing the file's owner in domain\username format. If the ListByOwner object's Owner property is blank (e.g., a specific owner wasn't specified), or if it matches the current file's owner, then the subroutine outputs the file's information in the following format:
Owner ParentFolder Name Size
Last, the ProcessFolder subroutine updates the ListByOwner object's internal variables containing the total number of files and their cumulative sizes. Notice that the nonerror output is sent to standard output and the error output is sent to standard error.
Complementing Disk Quota
The Windows disk quota feature is useful but incomplete. The GetOwner.dll file, combined with the OwnedBy.vbs script, overcomes one of its limitations—allowing you to find all files in one or more folders owned by a user and report on their size. Even if you don't use quotas, this DLL and script will help you manage disk space more efficiently.