Получение информации, не предоставляемой средством квотирования дисков в Windows

.

Ограничения функции квотирования диска

В Windows 2000 и более поздних версиях имеется функция квотирования диска, которую можно использовать для отслеживания или ограничения работы с диском NTFS. Для просмотра или настройки квотирования диска нужно щелкнуть правой кнопкой мыши на выбранном диске в Windows Explorer, выбрать пункт Properties, и перейти на закладку Quota.

Щелкните на кнопке Quota Entries, чтобы увидеть список пользователей и объем занимаемого каждым из них дискового пространства. Эта информация берется из сведений о владельце в файлах NTFS. Диалоговое окно Quota Entries позволяет переопределить записи квот, установленные по умолчанию, последовательно (один пользователь за другим), добавлять и удалять записи квот и даже импортировать и экспортировать их. К сожалению, настраивать записи квот на основе групп невозможно, а функция квотирования может быть разрешена или запрещена только для томов. Однако даже с этими ограничениями функция квотирования диска может быть полезной.

Окно Quota Entries отображает список владельцев файлов и объем используемого ими дискового пространства, но операционная система не предоставляет способ получения списка файлов, принадлежащих пользователю, если только не щелкнуть правой кнопкой мыши на записи пользователя, не выбрать пункт Delete, а потом Yes. Если пользователь имеет файлы на данном томе, Windows отобразит диалоговое окно, которое позволит удалить или присвоить себе право владения файлами пользователя. В этом окне отображаются файлы, принадлежащие пользователю, но не показано занимаемое ими дисковое пространство. Также нет возможности экспортировать список файлов в новый файл для отчетности. Это ограничение может вызвать проблемы, если пользователи начнут превышать объем имеющегося дискового пространства.

Чтобы решить задачу построения списка файлов и дискового пространства для данного владельца файлов, я решил написать сценарий. Сценарии Windows не позволяют получить имя владельца файла, и хотя существует возможность узнать имя владельца файла с помощью средств Windows Management Instrumentation (WMI), я решил отказаться от нее из соображений сохранения производительности. Вместо этого я создал библиотеку ActiveX DLL GetOwner.dll, которая возвращает имя владельца файла. Еще я написал сценарий OwnedBy.vbs на VBScript, использующий библиотеку DLL для вывода неограниченного списка файлов, принадлежащих определенному пользователю, их размеров и содержащих их папок. Можно настроить вывод списка в файл для дальнейшего анализа и при необходимости импортировать его в электронную таблицу или базу данных.

Можно загрузить файлы GetOwner.dll, OwnedBy.vbs и исходный код библиотеки DLL с сайта Windows IT Pro/RE (ссылка на файл 46487.zip). Некоторые читатели не смогут воспользоваться библиотекой DLL, даже при наличии исходного кода. Для них я написал версию файла OwnedBy.vbs, которая использует файл ADsSecurity.dll из набора разработчика программного обеспечения (SDK) для Active Directory Service Interfaces (ADSI). Однако, по возможности, я рекомендую задействовать библиотеку GetOwner.dll, для сохранения производительности: судя по моим тестам, данный вариант работает почти в 9 раз быстрее.

Требования сценария

Для работы OwnedBy.vbs под Windows 2000 необходим пакет VBScript 5.6. Это требование можно удовлетворить, установив пакет Microsoft Internet Explorer (IE) 6.0. В системах Windows Server 2003 и Windows XP пакет VBScript 5.6 является частью операционной системы.

Сценарий также требует регистрации библиотеки GetOwner.dll на локальном компьютере. Для этого нужно скопировать файл GetOwner.dll в папку (например, %systemroot%system32) и ввести в командной строке следующую команду:

regsvr32 [/s] pathgetowner.dll

где path - путь к файлу getowner.dll. Параметр /s (silent) отключает появление диалогового окна с подтверждением. Чтобы удалить библиотеку DLL из реестра, следует добавить параметр /u после команды regsvr32. Для удаления без подтверждения нужно одновременно использовать параметры /u и /s. Чтобы иметь возможность удалять и добавлять библиотеки DLL в реестр, пользователь должен быть членом локальной группы Administrators.

Для компиляции библиотеки GetOwner.dll я использовал пакет Visual Basic (VB) 6.0. Рабочий модуль VB (msvbvm60.dll) устанавливается по умолчанию вместе с системой Windows 2000 и более поздними версиями.

Использование сценария OwnedBy.vbs

Из-за того, что сценарий OwnedBy.vbs использует для вывода окно команд, он должен исполняться с применением хоста CScript. Для настройки CScript в качестве хоста по умолчанию для текущего пользователя нужно ввести в командную строку следующую команду:

cscript //h:cscript //nologo //s

Сценарий OwnedBy.vbs использует следующий синтаксис командной строки:

[cscript] ownedby.vbs 
[/s] [/d:char]
[/o:owner] [/nh] [/ns]

Ключевое слово cscript требуется только в том случае, если узел CScript не является хостом сценария по умолчанию. Другие параметры командной строки могут присутствовать всегда.

Необходимо указать одно или несколько имен папок вместо параметра . Если имя папки содержит пробелы, его следует заключить в кавычки. Если требуется получить информацию по файлам в подкаталогах, добавьте параметр /s. Сценарий выдает выходные данные в форме столбцов. По умолчанию в выходных данных столбцы отделяются друг от друга символом табуляции. Чтобы указать другой разделительный символ, используйте параметр /d, сопровождаемый символом «;», например ключ /d:; позволяет вывести список, разделяемый символом «;».

Можно указать, что в списке должны отображаться только файлы, принадлежащие определенному пользователю. Для этого нужно ввести ключ /o и заменить параметр owner именем пользователя в формате «доменимя пользователя». Если не указать имя домена, используется текущий домен (тот, в котором вы регистрировались). Если не указать ключ /o, сценарий OwnedBy.vbs включает в список файлы, принадлежащие каждому из пользователей.

По умолчанию сценарий OwnedBy.vbs выводит строку заголовка, описывающую каждый столбец (то есть Owner, ParentFolder, Name, Size). Надписи заголовка могут пригодиться при импортировании отчета в электронную таблицу или базу данных, но если вы не хотите, чтобы сценарий выводил строку заголовка, используйте ключ /nh.

В конце сценарий выводит итоговую строку вида

n byte(s) in x file(s)

где n - суммарный размер файлов, x - число найденных файлов. Чтобы отключить вывод итоговой строки, используйте ключ /ns.

Возможно, вы захотите сохранить результаты работы сценария для просмотра в дальнейшем. Для этого нужно перенаправить стандартный вывод сценария в файл. Например, команда

ownedby.vbs d: /o:corpsmithj
/s > d:smithj.txt

добавляет в список все файлы на диске D, принадлежащие пользователю corpsmithj, и сохраняет отчет в файле d:smithj.txt. Если сценарий сталкивается с ошибкой чтения файла или каталога, он прописывает сведения об ошибке в стандартное сообщение, определенное по умолчанию в окне команд. Можно перенаправить вывод стандартного сообщения об ошибке в файл, например

ownedby.vbs d: /o:corpsmithj
/s > d:smithj.txt
2> d:errors.log

Эта команда пересылает только сообщения об ошибках в файл d:errors.log.

Вы также можете направить стандартный вывод и вывод стандартного сообщения об ошибке в один и тот же файл, например

ownedby.vbs d: /o:corpsmithj
/s > d:smithj.txt 2>&1

Эта команда сохраняет и стандартный вывод, и стандартные сообщения об ошибках в файле d:smithj.txt. Нужно иметь в виду, что если запустить сценарий OwnedBy.vbs для целого тома, он, возможно, выдаст объем используемого дискового пространства, несколько заниженный по сравнению с результатами в окне Quota Entries. Это происходит в силу завышения объемов файловой системой NTFS.

Подпрограмма Main

Подпрограмма Main сценария OwnedBy.vbs, показанная в Листинге 1, сначала объявляет свои переменные, а затем проверяет наличие аргументов командной строки. Если в командной строке присутствует параметр /? или отсутствуют имена папок, сценарий вызывает подпрограмму Usage, которая отображает краткое сообщение об использовании сценария и заканчивает работу.

Затем сценарий вызывает функцию ScriptHost(), чтобы определить, какой исполнитель работает со сценарием. Если это не CScript, сценарий вызывает подпрограмму Die для завершения работы сценария с сообщением об ошибке и кодом выхода, отличным от нуля.

Если сценарий OwnedBy.vbs исполняет CScript, подпрограмма Main пытается создать экземпляр объекта ListByOwner. Так как описание класса для объекта ListByOwner существует в файле сценария OwnedBy.vbs, подпрограмма использует ключевое слово New оболочки VBScript вместо метода CreateObject оболочки WScript. Если сценарий OwnedBy.vbs не может создать объект ListByOwner, он заканчивает работу с сообщением об ошибке.

Если объект ListByOwner успешно создается, сценарий проверяет наличие аргументов командной строки /o, /d, /s, /nh и /ns. Аргументы /o, /d и /s соответствуют свойствам объекта ListByOwner - Owner, Delim и Recurse. Я вкратце остановлюсь на них. Булевы переменные blnHeader и blnSummary устанавливаются в значение False, если параметры /nh и /ns, соответственно, присутствуют в командной строке.

Если переменная blnHeader имеет значение True (то есть ключ /nh отсутствует в командной строке), сценарий выполняет метод OutputHeader объекта ListByOwner. Далее сценарий отключает установленный по умолчанию обработчик ошибок VBScript с помощью выражения On Error Resume Next и переходит к перебору аргументов командной строки (то есть имен папок) с помощью цикла For Each.

В теле цикла For Each сценарий устанавливает свойство Folder объекта ListByOwner в соответствии с аргументом командной строки. Если объект ListByOwner вызывает ошибку (например, если папка не существует), сценарий прописывает сообщение об ошибке в стандартное сообщение об ошибке; если нет - сценарий вызывает метод Run объекта ListByOwner. Последним шагом подпрограммы Main является проверка переменной blnSummary. Если значение - True (то есть параметр /ns отсутствует в командной строке), сценарий вызывает метод OutputSummary объекта ListByOwner.

Работа с объектом ListByOwner

Работа сценария OwnedBy.vbs выполняется с помощью объекта ListByOwner. Свойства, используемые объектом ListByOwner, как и частные переменные, применяемые каждым свойством, приведены в Таблице 1. Методы объекта ListByOwner приведены в Таблице 2.

Когда сценарий создает экземпляр объекта ListByOwner с помощью ключевого слова New среды VBScript, автоматически вызывается процедура события Class_Initialize, для инициализации частных переменных объекта и выполнения других задач, возникающих при создании объекта.

Первое, что пытается сделать объект ListByOwner, это создать экземпляр объекта GetOwner.Owner. Это действие не может быть выполнено, если файл библиотеки GetOwner.dll не зарегистрирован в системе. Код процедуры события выполняет собственную обработку ошибки - если метод CreateObject вызывает ошибку, объект ListByOwner вызывает собственную ошибку, вместо отмены выполнения всего сценария. Подпрограмма Main перехватывает эту ошибку и заканчивает работу в штатном режиме.

Затем процедура события Class_Initialize создает частный указатель на объект Scripting.FileSystemObject и устанавливает для свойств значения по умолчанию. После завершения процедуры Class_Initialize переменная objLBO подпрограммы Main содержит инициализированный объект ListByOwner.

Установка свойства Folder

Выражения Public Property Let, которые являются частью языка VBScript, определяют процедуры свойств, которые выполняют управление свойствами объекта ListByOwner. Каждая из процедур свойств устанавливает соответствующую частную переменную, как показано в Таблице 1. При установке свойства Folder код выполняет собственную обработку ошибки и вызывает ошибку, если метод GetFolder объекта FileSystem-Object не может быть выполнен. В этом случае подпрограмма Main может обнаружить ошибку в штатном режиме и перейти к следующей папке, указанной в командной строке.

Подпрограмма ProcessFolder

Когда подпрограмма Main вызывает метод Run объекта ListByOwner, он выполняет подпрограмму ProcessFolder с частной переменной objFolder в качестве аргумента. Подпрограмма ProcessFolder выполняет работу по перебору множеств файлов и папок.

Подпрограмма ProcessFolder выполняет собственную обработку ошибок. Она отключает установленный по умолчанию обработчик ошибок VBScript с помощью выражения On Error Resume Next. Потом, если частная переменная blnRecurse имеет значение True, подпрограмма перебирает множество подкаталогов исходной папки с помощью цикла For Each и вызывает сама себя для каждого подкаталога.

Далее подпрограмма перебирает множество файлов и папок с помощью цикла For Each. Имя владельца каждого файла мы получаем с помощью вызова метода GetOwner объекта objGetOwner, который возвращает строку, содержащую имя владельца файла в виде доменимя пользователя. Если свойство Owner объекта ListByOwner содержит пустую строку (например, конкретный пользователь не указан) или совпадает с именем владельца текущего файла, тогда подпрограмма выводит информацию о файле в следующем виде:

Owner ParentFolder Name Size

В конце подпрограмма ProcessFolder обновляет внутренние переменные объекта ListByOwner, содержащие общее количество файлов и их суммарный размер. Нужно иметь в виду, что вывод без ошибок направляется на стандартный вывод, а вывод с ошибками - на стандартный вывод сообщений об ошибках.

Дополнение к средству квотирования диска

Функция квотирования дисков в Windows полезна, но она недостаточно мощная. Библиотека GetOwner.dll в связке со сценарием OwnedBy.vbs снимает ограничения квотирования, позволяя искать все файлы, принадлежащие пользователю, в одной или нескольких папках и получать сведения об их размерах. Даже если квотирование не используется, эта библиотека DLL и сценарий помогут администратору управлять дисковым пространством более эффективно.


Листинг 1. Подпрограмма Main
Sub Main
' BEGIN COMMENT
' Переменная objLBO содержит указатель на объект ListByOwner.
' END COMMENT
 Dim objLBO, blnHeader, blnSummary, strFolder
' BEGIN COMMENT
 ' Если в командной строке присутствует параметр /? или если отсутствуют имена папок,
 ' выполняется выход с сообщением об использовании.
' END COMMENT
 With WScript.Arguments
 If .Named.Exists(?) Or .Unnamed.Count = 0 Then Usage
 End With
' BEGIN COMMENT
 ' Завершение сценария с сообщением об ошибке, если он не выполняется хостом CScript.
' END COMMENT
 If ScriptHost() <> cscript.exe Then _
 Die You must run this script using the CScript host., 1
' BEGIN COMMENT
 ' Сценарий завершится с ошибкой, если в случае создания экземпляра объекта ListByOwner
 ' выявляется ошибка. Это ошибка часто связано с тем, что библиотека
 ADsSecurity.dll не зарегистрирована.
' END COMMENT
 On Error Resume Next
 Set objLBO = New ListByOwner
 If Err <> 0 Then Die [0x & Hex(Err) & ] & Err.Description, Err
 On Error GoTo 0
 With WScript.Arguments
' BEGIN COMMENT
 ' Получение имени владельца из параметра /O. Если пусто - вывод для всех владельцев.
' END COMMENT
 objLBO.Owner = .Named(O)
' BEGIN COMMENT
 ' Получение разделителя выходных данных.
' END COMMENT
 objLBO.Delim = .Named(D)
' BEGIN COMMENT
 ' Если присутствует параметр /S, выполняется рекурсия в каждом подкаталоге.
' END COMMENT
 objLBO.Recurse = .Named.Exists(S)
' BEGIN COMMENT
 ' Если присутствуют параметры /NH или /NS, выполняется пропуск строки
 заголовка и итоговой строки соответственно.
' END COMMENT
 blnHeader = Not .Named.Exists(NH)
 blnSummary = Not .Named.Exists(NS)
 End With
' BEGIN COMMENT
 ' Вывод строки заголовка (при необходимости).
' END COMMENT
 If blnHeader Then objLBO.OutputHeader
' BEGIN COMMENT
 ' Сценарий будет обрабатывать собственные ошибки в приведенном ниже цикле For Each.
' END COMMENT
 On Error Resume Next
 For Each strFolder In WScript.Arguments.Unnamed
 objLBO.Folder = strFolder
 If Err <> 0 Then
 ' The ListByOwner object raised an error.
 WScript.StdErr.WriteLine [0x & Hex(Err) & : _
 & Err.Description & ] & strFolder
 Else
 objLBO.Run
 End If
 Err.Clear
 Next
' BEGIN COMMENT
 ' Вывод итоговой строки (при необходимости).
' END COMMENT
 If blnSummary Then objLBO.OutputSummary
End Sub

Таблица 1. Свойства и частные переменные объекта ListByOwner
СвойствоОписаниеЧастная переменная
FolderСвойство Folder принимает значение, соответствующее имени каждой папки, указанной в командной строке. Если при доступе к папке возникает ошибка (например, папки не существует), объект вызовет ошибкуobjFolder
RecurseСвойство Recurse устанавливается в значение True, если объект должен обращаться к подкаталогам указанной папки. Значение по умолчанию - FalseblnRecurse
OwnerСвойство Owner принимает значение вида домен/имя пользователя, соответствующее имени пользователя, по которому должен осуществляться поиск. Если имя домена не указано, используется текущий домен. Если это свойство содержит пустую строку (по умолчанию), будут выведены данные по всем владельцамstrOwner
DelimСвойство Delim определяет символ, используемый для разделения столбцов на выходе. По умолчанию - символ табуляцииstrDelim

Таблица 2: Методы объекта ListByOwner
МетодОписание
OutputHeaderМетод OutputHeader выводит строку заголовка. Столбцы отделены друг от друга символом, указанным в свойстве Delim
RunМетод Run запускает процесс вывода для папки, указанной в свойстве Folder. Работа управляется частной подпрограммой ProcessFolder
OutputSummaryМетод OutputSummary выводит итоговую строку (общий размер в байтах и количество файлов)