Однажды начальник вызывает вас к себе и сказал: "Бухгалтерия требует от нас к концу этой недели отчет по инвентаризации технических средств. И даже думать не смей просить денег на приобретение программы для этого." Так хорошо начинавшийся день вдруг резко изменился к худшему.
В данной ситуации можно поступить по-разному. Можно обойти все рабочие места компании и вручную собрать инвентаризационные данные по компьютерам, можно уволиться с работы или заявить, что поставленная задача не выполнима, а можно воспользоваться решением, построенным на базе двух сценариев, с помощью которого за рекордно короткое время можно собрать данные по инвентаризации аппаратных средств и сформировать отчет в формате Microsoft Excel. Для реализации данного решения требуется только наличие Windows Management Instrumentation (WMI), Windows Script Host (WSH) 5.6, а также Excel 2000 или более новая версия. С его помощью можно собирать расширенные данные инвентаризации компьютеров, работающих под управлением операционных систем Windows Server 2003, Windows XP, Windows 2000, Windows NT 4.0 (Service Pack 4—SP4— или более поздний), Windows Me и Windows 98.
Подготовка к проведению инвентаризации
Предлагаемое решение по инвентаризации технических средств состоит из двух сценариев. Первый из них, HrdWrInv.vbs, представляет собой собственно агент инвентаризации, собирающий данные об аппаратных средствах каждого компьютера сети. Второй компонент, BuildReport.vbs, служит для создания отчета в виде книги Microsoft Excel, содержащий данные об инвентаризации тех компьютеров, на которых успешно отработал сценарий HrdWrInv.vbs. Файлы обоих сценариев можно посмотреть в Листингах 1 и 2.
Когда на какой-либо системе запускается агент инвентаризации, то в результате создается небольшой текстовый файл (обычно его объем не превышает 6KB) с именем HrdWrInv_ComputerName.txt ,где ComputerName - имя компьютера для которого выполняется инвентаризация. Каждый из этих файлов сценарий HrdWrInv.vbs помещает в некоторый каталог, который я называю каталогом результатов инвентаризации. Путь к данному каталогу задается в переменной strInvFilePath, которая задается в строке 22 этого сценария. Убедитесь, что тот сетевой ресурс, путь к которому определяется в этой переменной является доступным для всех тех пользователей, компьютеры которых подлежат инвентаризации и не забудьте поставить в конце описания пути символ обратного слеша (). Также учтите, что в этом каталоге все пользователи должны иметь разрешения на чтение (Read) и запись (Write). По умолчанию в сценарии HrdWrInv.vbs указан путь sea-fs-02hw, что соответствует разделяемому каталогу hw на сервере sea-fs-02, соответственно, здесь нужно задать тот путь, который будет использоваться в вашем случае.
При указании пути можно использовать буквенные обозначения для сетевых дисков (например, G:inventoryhw), но это не самое лучшее решение, поскольку в данном случае необходимо, чтобы каждый пользователь подключил сетевой диск на эту букву, для того чтобы сценарий сохранял файлы инвентаризации в одном и том же месте. Если же путь указывается с использованием универсального соглашения об именах (UNC), то в этом случае не нужно контролировать правильность подключения пользователями соответствующих сетевых дисков.
В ходе своей работы агент инвентаризации использует компоненты WMI и WSH 5.6. В операционных системах Windows 2003, Windows XP, Windows 2000 и Windows ME компонент WMI устанавливается по умолчанию, поэтому вам потребуется установить компоненты WMI Core 1.5 (которые доступны для загрузки по адресу http://www.microsoft.com/downloads/details.aspx?familyid=afe41f46-e213-4cbf-9c5b-fbf236e0e875) только на системы с Windows NT4.0 SP4 и выше, а также на системы с Windows 98, на которых вы планируете запускать данный сценарий. Компонент WSH 5.6 по умолчанию установлен только на системах Windows 2003 и Windows XP, что же касается Windows 2000, Windows NT4.0, Windows ME и Windows 98, то на них данный компонент должен быть установлен дополнительно (WSH 5.6 можно загрузить по адресу http://msdn.microsoft.com/library/default.asp?url=/downloads/list/webdev.asp).
Итак, вы подготовили сам сценарий и компьютеры для запуска на них агента инвентаризации, теперь нужно сделать так, чтобы данный сценарий был доступен через сеть. Сценарий выполняется в "тихом" режиме, поэтому он может вызваться, например, в ходе процедуры обработки сценария регистрации. Если же на предприятии используется только Windows 2000 и более новые версии, то в этом случае можно запускать агент инвентаризации через механизмы групповых политик в процессе обработки сценариев регистрации или завершения работы из системы. Другой способ заключается в том, чтобы собирать инвентаризационную информацию из некоторой центральной точки, выполняя с нее удаленное подключение к клиентам через WMI.
Подтверждением того, что агент инвентаризации работает, является появление в каталоге результатов инвентаризации текстовых файлов, название которых начинается с префикса HrdWrInv_. Более подробное описание данного агента приведено во врезке "Как работает агент инвентаризации". Если агент инвентаризации будет вновь запущен на том же самом компьютере, то он определит, что для этого компьютера инвентаризация уже проводилась, после чего сценарий завершит работу. Если нужно повторно провести инвентаризацию данного компьютера, просто удалите из каталога результатов инвентаризации соответствующий файл. В этом случае при повторной инвентаризации компьютера агент создаст для него новый файл результатов.
Анализ полученных данных
По завершении сбора данных текстовые файлы инвентаризации можно просматривать индивидуально, например, с помощью Notepad. Каждый текстовый файл содержит разделы, например, ComputerSystem и IDEController, в которых содержатся соответствующие характеристики по конкретному компьютеру. Однако не во всех инвентаризационных файлах будут представлены все разделы. Например, если в каком-либо компьютере установлен контроллер SCSI, но при этом отсутствует контроллер IDE, тогда соответствующий файл инвентаризации будет содержать раздел SCSIController, а раздел IDEController в нем будет отсутствовать.
Также следует отметить, что объем данных, получаемых при инвентаризации аппаратных средств и отображаемых в файле результатов, будет зависеть от версии операционной системы компьютера. Например, в файлах результатов для систем с Windows 98 будет отсутствовать раздел PhysicalMedia, поскольку класс WMI Win32_PhysicalMedia поддерживается только в Windows 2003 и Windows XP.
Если посмотреть на содержимое разделов данных, то можно увидеть, что некоторые параметры имеют значение Not available. Это имеет место в тех случаях, когда производитель оборудования не обеспечил сохранение данных в соответствующем свойстве WMI, либо когда WMI не смог найти значение соответствующего свойства.
Анализ результатов инвентаризации показывает, что различия в версиях операционных систем и аппаратных компонентов компьютеров, а также различия в свойствах этих компонентов приводят к тому, что собранные по всем компьютерам данные не являются унифицированными. В результате, после выполнения данного сценария в вашем распоряжении оказывается полезная инвентаризационная информация, но в весьма неструктурированном виде.
Формирование отчета
Конечно, можно предложить сотрудникам бухгалтерии самим просмотреть содержимое 5000 небольших текстовых файлов в каталоге результатов инвентаризации и найти в них нужную информацию, но эта идея вряд ли придется по вкусу вашему начальству. Таким образом, следующим шагом должно стать формирование отчета по результатам инвентаризации. Итак, давайте познакомимся с нашим надежным агентом построения отчетов - сценарием BuildReport.vbs, см. Листинг 2. Этот сценарий считывает содержимое каждого файла инвентаризации, находит в нем разделы с инвентаризационными данными и размещает их на отдельных листах книги Microsoft Excel c именем InvReport.xls. Более подробно данный сценарий рассмотрен во врезке "Как работает агент построения отчетов".
Выбор Excel в качестве инструмента консолидирования данных инвентаризации обусловлен несколькими причинами. Во-первых, данное приложение весьма широко распространено. Во-вторых, с его помощью можно создавать отчеты с очень широкими функциональными возможностями, а также анализировать собранные данные. И последнее. Большинство из тех людей, которые будут работать с этими отчетами по инвентаризации, как правило, хорошо знакомы с таблицами Excel, умеют с ними работать и анализировать данные.
Перед запуском агента построения отчетов нужно внести небольшие изменения в строки 9 и 10 файла BuildReport.vbs. Строка 9 содержит переменную strInvFilePath, с помощью которой задается путь к каталогу результатов инвентаризации; в строке 10 находится переменная strReportPath, в которой указывается размещение создаваемого файла InvReport.xls. Внесите в эти строки значения, соответствующие требуемому размещению этих файлов.
Прежде чем запускать BuildReport.vbs, следует уяснить три основные особенности, связанные с его работой. Во-первых, данный сценарий использует функциональность объекта приложения Excel, поэтому убедитесь, что программа Excel установлена на том компьютере, на котором вы собираетесь запускать агент построения отчетов.
Второе. Я умышленно написал сценарий данного агента так, чтобы в процессе обработки каждого файла данных отображалось соответствующее сообщение, информирующее о том, что сценарий успешно обработал очередной файл данных инвентаризации. Когда этот агент запускался на компьютере с процессором Pentium III 1ГГц и 512Мб оперативной памяти, обработка одного файла данных занимала примерно 3 секунды. Поэтому при запуске сценария необходимо использовать сервер сценариев CScript.exe, чтобы сценарий автоматически продолжал выполнение по окончании обработки очередного файла данных. Если запуск сценария осуществляется через сервер Wscript.exe, то в этом случае для продолжения работы сценария придется нажимать OK всякий раз, когда он обработает очередной файл, что, согласитесь, довольно утомительно, особенно если вам предстоит обработать 10000 файлов инвентаризации. Для того чтобы заставить агента запускаться через CScript.exe можно назначить CScript.exe используемым в системе по умолчанию сервером обработки сценариев или открыть окно командной строки, перейти в каталог, в котором находится данный сценарий, после чего набрать следующую команду:
cscript buildreport.vbs
И, наконец, третье. Прежде чем запускать процесс, оцените то количество времени, которое потребуется сценарию для создания отчета. Если каждый файл инвентаризации обрабатывается 3 секунды, а всего таких файлов 10000, то это значит, что для создания полного отчета потребуется 30000 секунд, что соответствует 8,3 часа. Поэтому есть смысл рассмотреть возможность запуска процедуры формирования отчета в вечернее или ночное время, желательно на более мощном компьютере, не занятом выполнением каких-либо других задач.
Пример отчета, создаваемого агентом показан на Рисунке 1. Для того чтобы отобразить максимальное количество данных мне пришлось скрыть панели инструментов и уменьшить ширину ячеек. На рисунке показан лист Computer Systems, а вся книга содержит 14 листов, по одному на каждый раздел данных инвентаризации: Computer Systems, Page Files, RAM, SCSI Controllers, IDE Controllers, Disk SerNums, Removable Media, Fixed Disks, Processors, NICs, Monitors, Video Adapters, Motherboards и BIOS. При необходимости вы можете внести соответствующие изменения в текст сценариев агентов инвентаризации и построения отчетов и, таким образом, собирать большее количество данных по каждому компьютеру.
Инвентаризация в два счета
Теперь в вашем распоряжении есть легкое средство сбора данных инвентаризации и создания соответствующих отчетов, причем для этого вам не потребовалось ничего, кроме подписки на данную колонку новостей. Надеюсь, что и вы, и ваш начальник и бухгалтерия заинтригованы.
Как работает агент инвентаризации
Агент инвентаризации аппаратных средств, HrdWrInv.vbs, использует в ходе своей работы функциональность компонента Windows Management Instrumentation (WMI). Для создания и проверки существования файлов инвентаризации, а также для записи в них строк данных сценарий вызывает объект FileSystemObject (являющийся одним из компонентов библиотеки Scripting Runtime Library).
Первая строка сценария содержит инструкцию On Error Resume Next, и это принципиально, поскольку мы не хотим беспокоить пользователя какими-либо ошибками выполнения, которые может генерировать сценарий в ходе работы. Причинами подобных ошибок обычно являются некорректная настройка разрешений доступа к каталогу хранения результатов инвентаризации, а также отсутствие или некорректная работа компонентов WMI Core на данном компьютере.
После определения нескольких констант и глобальных переменных строка установки WMI-соединения осуществляет подключение к локальному компьютеру, а затем с помощью метода ExecQuery объекта SWbemServices запускается серия запросов. С целью повышения производительности сценария каждый запрос запускается в полусинхронном режиме -, результаты выполнения каждого запроса упаковываются в коллекцию SWbemObjectSet. В данной коллекции хранятся значения параметров аппаратных средств опрашиваемого компьютера, которые затем записываются в соответствующий файл результатов инвентаризации.
В сценарии инвентаризации большую часть работы выполняет процедура QueryInstances, код которой приведен на Листинге 3. Этой процедуре должны быть переданы три параметра: имя опрашиваемого класса WMI, свойства, которые запрос должен возвратить в результате выполнения и какие-либо критерии запроса. Например, в результате выполнения показанного ниже вызова процедуры QueryInstances в переменной strProperties будут возвращены свойства класса Win32_LogicalDisk (что используется при сборе данных о логических дисках):
strProperties = _ "DriveType,Description," & _ "DeviceID,CreationClassName" QueryInstances "Win32_LogicalDisk",strProperties, _ "DriveType!=3 AND DriveType!=4"
Обратите внимание на то, что в команде вызова процедуры указано, что результирующий набор не должен содержать данных по типам DriveType 3 и DriveType 4, что соответствует локальному диску и сетевому диску. Здесь не выполняется сбор данных о локальном диске, потому что далее в сценарии эти данные будут получены из класса Win32_DiskDrive. Что касается сетевых дисков, то сбор данных по ним отключен по той причине, что эта информация не представляет интереса, поскольку не отражает конфигурацию локального компьютера.
Остальные процедуры, используемые в этом сценарии, определяют единицы измерения для тех свойств, которые сохраняются как численные значения. Скажем, свойство Size класса Win32_DiskDrive будет сохранено в виде значения, измеряемого в байтах. Сначала с помощью функции GetUnits находится единица измерения, затем функция SizeFormat выполняет преобразование значения к более читабельному виду. Очевидно, что значение 17GB, соответствующее объему диска может оказаться более полезным с точки зрения наглядности, чем отображение этого же параметра в виде 18,202,544,640 байт.
Как работает агент построения отчетов
Агент построения отчетов, BuildReport.vbs, использует в ходе функционирования такие объекты как приложение Microsoft Excel, VBScript Regular Expression (RegExp), а также хорошо нам знакомый FileSystemObject. Объект - приложение Microsoft Excel, предоставляет богатые возможности программирования, которые могут быть задействованы из сценария.
В начале сценария BuildReport.vbs объявляется несколько констант и инициализируется ряд глобальных переменных. Затем объект Excel создает рабочую книгу. После этого сценарий вызывает процедуру PrepareWorkSheet. Данная процедура создает в книге 14 листов, присваивает каждому из них имя, создает заголовки столбцов, форматирует некоторые столбцы и сохраняет книгу под именем InvReport.xls.
Далее вызывается объект FileSystemObject, который использует коллекцию Files и циклический оператор For Each для обработки всех файлов, находящихся в каталоге результатов инвентаризации. Для сокращения количества файлов, которые должен обработать FileSystemObject, следите за тем, чтобы каталог результатов инвентаризации не содержал ничего кроме файлов, создаваемых агентом инвентаризации.
Внутри цикла For Each для каждого файла инвентаризации объект FileSystemObject с помощью метода OpenTextFile создает два текстовых потока. Необходимость создания двух текстовых потоков для одного и того же источника данных обусловлено спецификой работы с файлами инвентаризации в сценарии. Сценарий выполняет построчное чтение содержимого первого объекта текстового потока, который я назвал TextStream1 и записывает эти данные в таблицу. Когда сценарий открывает второй объект, который называется TextStream2, то он считывает его содержимое полностью, для чего используется метод ReadAll объекта FileSystemObject. Далее TextStream2 используется объектом Regular Expression (RegExp) для поиска конкретных разделов в содержимом инвентаризационного файла.
Затем вызывается процедура GenReport - один из наиболее часто встречающихся вызовов в данном сценарии. В ходе своего выполнения эта волшебная процедура опирается на несколько функций. Для работы GenReport необходимо задать имя заголовка раздела, имя объекта TextStream2, уникальную метку для поиска в инвентаризационном файле и тот массив значений, содержимое которого сценарий будет записывать в столбцы файла InvReport.xls.
Для определения местонахождения нужного заголовка раздела в текстовом файле GenReport вызывает другую процедуру MoveToSectionHead, которой передается название требуемого заголовка. Сначала MoveToSectionHead с помощью RegExp ищет нужный заголовок в TextStream2, а затем с помощью метода ReadLine объекта FileSystemObject перемещается на соответствующий раздел объекта TextStream1. Далее GenReport вызывает функцию FindValues, с помощью которой определяет, сколько уникальных параметров содержится в данном разделе файла инвентаризации и, соответственно, сколько строк нужно записать в соответствующий лист книги Excel.
Далее процедура InsertValues выполняет построчную запись данных, извлеченных из файла инвентаризации в листы таблицы. В InsertValues для определения того, какой лист нужно сделать активным, а также для записи данных и форматирования ячеек используется оператор Case. В каждом из пунктов оператора Case для записи данных в каждую ячейку листа запускается процедура FillCells.
Листинг 1. Сценарий HrdWrInv.vbs
'On Error Resume Next directive is specified so that users
'are not bothered by potential operational errors with the script.
'On Error Resume Next
'Using both of these flags of the WbemFlagEnum enumeration
'makes a semisynchronous WMI call.
See "Making a Semisynchronous Call"
'in the WMI SDK for more information.
Const wbemFlagReturnImmediately = 16
Const wbemFlagForwardOnly = 32
'WbemCimtypeEnum Enumerations used in the script
Const wbemCimtypeUint32 = 19
Const wbemCimtypeSint64 = 20
Const wbemCimtypeUint64 = 21
'Formatting and number conversion constants
Const HR = "----"
Const KB = 1024
Const MB = 1048576
Const GB = 1073741824
'Change this to the UNC path where inventory files should be created
'Anyone running this script must have read and write access to the
'path.
strInvFilePath = "sea-fs-02hw"
'If each user will run this inventory file locally, do not change the value
'of strComputer.
strComputer = "."
'Connect to WMI
Set objWMIService = GetObject("winmgmts:"
& strComputer & "
ootcimv2")
'Determine the OS because not all classes listed here are supported
'on all versions of the Windows operating systems
strProperties = "CreationClassName,Version,CSName,Caption"
Set objOS = objWMIService.ExecQuery _
("SELECT " & strProperties
& " FROM Win32_OperatingSystem",_
,wbemFlagReturnImmediately + wbemFlagForwardOnly)
For Each Setting in objOS
strCreationClass = Setting.CreationClassName
intVersion = Setting.Version
strCSName = Setting.CSName
strCaption = Setting.Caption
Next
'Check for a file named after the computer. If it doesn't
'exist, then create a file that contains the computer's
'hardware inventory.
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFileName = "HrdWrInv_"
& strCSName & ".txt"
strFullName = objFSO.BuildPath(strInvFilePath, strFileName)
If (objFSO.FileExists(strFullName)) Then WScript.Quit
Set objFile = objFSO.CreateTextFile(strFullName)
'Write the Operating System name and version but nothing more.
'The goal is to collect hardware inventory, not software inventory
'and configuration information.
objFile.WriteLine("OSInformation:")
objFile.WriteLine("CreationClassName: " & strCreationClass)
objFile.WriteLine("ComputerName: " & strCSName)
objFile.WriteLine("Caption: " & strCaption)
objFile.WriteLine("Version: " & intVersion)
'The name property is automatically returned (because it's a key value), so
'it's not absolutely necessary to specify it in strProperties
strProperties = _
"CreationClassName,Manufacturer,Model,Name,NumberofProcessors," & _
"SystemType,TotalPhysicalMemory"
QueryInstances "Win32_ComputerSystem",strProperties,"None"
'Page file information.
strProperties = _
"Name,MaximumSize"
QueryInstances "Win32_PageFileSetting",strProperties,"None"
'Physical memory information.
'Note, total memory is aggregated and listed with win32_computersystem so there's
'no need to query the memory classes, such as Win32_PhysicalMemoryArray or
'Win32_PhysicalMemory. However, if you need to find out how much memory
'a computer will hold, it's useful to query Win32_PhysicalMemoryArray as
'shown
strProperties = _
"MaxCapacity,CreationClassName"
QueryInstances "Win32_PhysicalMemoryArray"
,strProperties,"None"
'SCSI Disk controller information
strProperties = _
"Name,Manufacturer,Description,CreationClassName"
QueryInstances "Win32_SCSIController"
,strProperties,"None"
'IDE Disk controller information
strProperties = _
"Name,Manufacturer,Description,CreationClassName"
QueryInstances "Win32_IDEController"
,strProperties,"None"
'Phyiscal Media information
'Note, this class is only in XP (ver5) and the Windows
'Server 2003 family (ver5).
'Win98SE reports a version number of 4 (4.10.2222)
If Mid(intVersion,1,3) >= 5.1 Then
strProperties = _
"SerialNumber,CreationClassName"
QueryInstances "Win32_PhysicalMedia"
,strProperties,"None"
End If
'Logical Disk information
strProperties = _
"DriveType,Description,DeviceID,CreationClassName"
QueryInstances "Win32_LogicalDisk",strProperties,_
"DriveType!=3 AND DriveType !=4"
'Disk drive information
strProperties = _
"Caption,Description,DeviceID,InterfaceType," & _
"Manufacturer,MediaType,Model,
Partitions,Size,CreationClassName"
QueryInstances "Win32_DiskDrive"
,strProperties,"None"
'Processor information
strProperties = _
"Manufacturer,MaxClockSpeed,ExtClock,
ProcessorType," & _
"Revision,Version,CreationClassName"
QueryInstances "Win32_Processor"
,strProperties,"None"
'NIC information
strProperties = _
"Manufacturer,MACAddress,ProductName,
AdapterType,CreationClassName"
QueryInstances "Win32_NetworkAdapter",strProperties,_
"AdapterType='Ethernet 802.3' AND
ProductName !='Packet Scheduler Miniport'"
'Monitor information
strProperties = _
"Description,MonitorType,CreationClassName"
QueryInstances "Win32_DesktopMonitor"
,strProperties,"None"
'Video adapter information
strProperties = _
"Name,MaxRefreshRate,AdapterRAM,CreationClassName"
QueryInstances "Win32_VideoController"
,strProperties,"None"
'Motherboard information
strProperties = _
"Description,Manufacturer,Product,CreationClassName"
QueryInstances "Win32_BaseBoard"
,strProperties,"None"
'BIOS information
strProperties = _
"Manufacturer, Name,SoftwareElementID,
SMBIOSBIOSVersion," & _
"SMBIOSMajorVersion,SMBIOSMinorVersion,Version"
QueryInstances "Win32_BIOS"
,strProperties,"None"
objFile.Close
'*****Subroutines and Functions*********
Sub QueryInstances(objClass,Properties,Conditions)
If Conditions = "None" Then
strSelect = "Select " & Properties
& " From " & objClass
Else
strSelect = "Select " & Properties
& " From " & objClass & _
" Where " & Conditions
End If
Set objClassName = _
objWMIService.ExecQuery _
(strSelect,,wbemFlagReturnImmediately
+ wbemFlagForwardOnly)
intCounter = 0
' Can use the following to determine the # of items in the sWbemObjectSet but
' it's processor intensive and it means that you can't use a semi-synchronous
' call because the count property doesn't work with wbemFlagForwardOnly.
' WScript.Echo "# of items in collection: " & objClassName.Count
For Each objComponent in objClassName
If intCounter = 0 Then
objFile.WriteLine(HR)
objFile.WriteLine(Mid(objClass,7) & ":")
End If
For Each objProperty in objComponent.Properties_
'Start by verifying that there's a value in the property.
'If not, it isn't necessary to perform the evaluation in this loop.
If ISNull(objProperty.Value) Then
objFile.WriteLine(objProperty.Name & ": Not available")
Else
If objProperty.CIMType <> wbemCimtypeUint32 And _
objProperty.CIMType <> wbemCimtypeUint64 And _
objProperty.CIMType <> wbemCimtypeSint64 Then
objFile.WriteLine(objProperty.Name & ": " & _
objProperty.Value & " " & _
GetUnits(objClass,objProperty.Name))
Else
strUnits = GetUnits(objClass,objProperty.Name)
intValue = _
SizeFormat(objProperty.Name,objProperty.Value,strUnits)
objFile.WriteLine(objProperty.Name
& ": " & intValue)
End If
End If
intCounter = intCounter + 1
Next
If intCounter > 1 Then objFile.WriteLine(HR)
Next
objFile.WriteLine(HR)
objFile.WriteLine()
End Sub
Function GetUnits(ClassName,ClassProperty)
Set objSchemaClass = objWMIService.Get(ClassName)
For Each objClassProperty in objSchemaClass.Properties_
If objClassProperty.Name = ClassProperty Then
For Each objQualifier in objClassProperty.Qualifiers_
If LCase(objQualifier.Name) = "units" Then
GetUnits = LCase(objQualifier.Value)
End If
Next
End If
Next
End Function
Function SizeFormat(PropertyName,RawValue,Units)
Select Case LCase(Units)
Case "bytes"
If int(RawValue/GB) >= 1 Then
SizeFormat = Round((RawValue/GB),1) & " gigabyte(s)"
ElseIf int(RawValue/MB) >= 1 Then
SizeFormat = int(RawValue/MB) & " megabyte(s)"
Else
SizeFormat = RawValue & " byte(s)"
End If
Case "kilobytes"
If int(RawValue/MB) >= 1 Then
SizeFormat = Round((RawValue/MB),1) & " gigabyte(s)"
ElseIf int(RawValue/KB) >= 1 Then
SizeFormat = int(RawValue/1024) & " megabyte(s)"
Else
SizeFormat = RawValue & " kilobyte(s)"
End If
Case Else
SizeFormat = RawValue & " " & Units
End Select
End Function
Листинг 2. Сценарий BuildReport.vbs
'Constants used in this script
Const ForReading = 1
Const xlNormal = &HFFFFEFD1
Const xlWBATWorksheet = &HFFFFEFB9
Const xlRight = &HFFFFEFC8
Const xlOtherSessionChanges = 2
'Initialize global variables
strInvFilePath = "sea-fs-02hw"
strReportPath = "c:
eport"
'New sheet names. This is outside of a specific sub-routine
'because multiple subroutines use it.
arrWBNames = Array("Computer Systems",
"Page Files","RAM", _
"SCSI Controllers","IDE Controllers","Disk SerNums", _
"Removable Media","Fixed Disks","Processors", _
"NICs","Monitors","Video
Adapters","MotherBoards", _
"BIOS")
'Creat Excel workbook globally because it's used by several routines
Set objExcel = CreateObject("Excel.Application")
Set objWorkBook = objExcel.Workbooks.Add 'new
Call PrepareWorkSheet
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFso.GetFolder(strInvFilePath)
Set objFiles = objFolder.Files
'intRowPosition = 2 'Inventory data begins
to be written to row 2 of each worksheet
For Each File in objFiles
If Mid(File.Name,1,9) = "HrdWrInv_" Then
strFileName = File.Name
'Use the value of computer name for the first instance of each row of data.
strComputerName = Mid(strFileName,10,(Instr(strFileName,".") - 10))
WScript.Echo "Processing inventory for " & strComputerName
Set objTextFile = objFSO.OpenTextFile(strInvFilePath & strFileName,ForReading)
'Using a sepearate text file object because you don't want to put the
'cursor at the end of the file and the readall method will do this
'and then you can't use readline, etc. to move through the file's contents.
Set objFileForSearching = objFSO.OpenTextFile
(strInvFilePath & strFileName,ForReading)
'read the file and use this for determining if a particular section head is
'found anywhere in the file.
strTextFile = objFileForSearching.ReadAll
arrItems = Array("SkipLine",
"SkipLine","Caption","Version")
Call GenReport("OSInformation:",strTextFile, _
"CreationClassName: Win32_OperatingSystem",arrItems)
arrItems = Array("SkipLine",
"Manufacturer","Model", _
"SkipLine","NumberOfProcessors",
"SystemType","TotalPhysicalMemory")
Call GenReport("ComputerSystem:",strTextFile, _
"CreationClassName: Win32_ComputerSystem",arrItems)
arrItems = Array("MaximumSize","Name")
Call GenReport("PageFileSetting:",strTextFile, _
"MaximumSize:",arrItems)
arrItems = Array("SkipLine",
"MaxCapacity","Tag")
Call GenReport("PhysicalMemoryArray:",strTextFile, _
"CreationClassName: Win32_PhysicalMemoryArray",arrItems)
arrItems = Array("SkipLine",
"Description","SkipLine",
"Manufacturer","Name")
Call GenReport("SCSIController:",strTextFile, _
"CreationClassName: Win32_SCSIController",arrItems)
arrItems = Array("SkipLine",
"Description","SkipLine",
"Manufacturer","Name")
Call GenReport("IDEController:",strTextFile, _
"CreationClassName: Win32_IDEController",arrItems)
arrItems = Array("SkipLine",
"SerialNumber","SkipLine")
Call GenReport("PhysicalMedia:",strTextFile, _
"CreationClassName: Not available",arrItems)
arrItems = Array("SkipLine",
"Description","DeviceID","SkipLine")
Call GenReport("LogicalDisk:",strTextFile, _
"CreationClassName: Win32_LogicalDisk",arrItems)
arrItems = Array("Caption",
"SkipLine","Description","SkipLine", _
"InterfaceType","Manufacturer",
"MediaType","Model","Partitions", _
"Size")
Call GenReport("DiskDrive:",strTextFile, _
"CreationClassName: Win32_DiskDrive",arrItems)
arrItems = Array("SkipLine",
"DeviceID","ExtClock","Manufacturer", _
"MaxClockSpeed","SkipLine",
"Revision","Version")
Call GenReport("Processor:",strTextFile, _
"CreationClassName: Win32_Processor",arrItems)
arrItems = Array("AdapterType",
"SkipLine","SkipLine", _
"MACAddress","Manufacturer","ProductName")
Call GenReport("NetworkAdapter:",strTextFile, _
"CreationClassName: Win32_NetworkAdapter",arrItems)
arrItems = Array("SkipLine",
"Description","SkipLine","MonitorType")
Call GenReport("DesktopMonitor:",strTextFile, _
"CreationClassName: Win32_DesktopMonitor",arrItems)
arrItems = Array("AdapterRAM",
"SkipLine","SkipLine", _
"MaxRefreshRate","Name")
Call GenReport("VideoController:",strTextFile, _
"CreationClassName: Win32_VideoController",arrItems)
arrItems = Array("SkipLine",
"Description","Manufacturer", _
"Product","SkipLine")
Call GenReport("BaseBoard:",strTextFile, _
"CreationClassName: Win32_BaseBoard",arrItems)
arrItems = Array("Manufacturer",
"Name","SMBIOSBIOSVersion", _
"SMBIOSMajorVersion","SMBIOSMinorVersion",
"SoftwareElementID", _
"SkipLine","SkipLine","Version")
Call GenReport("BIOS:",strTextFile, _
"SoftwareElementID",arrItems)
End If 'End reading inventory file
Next
objWorkBook.Save
objWorkBook.Sheets("Computer Systems").Select
objExcel.Visible = True
'*****Subroutines and Functions*********
Sub GenReport(SectionHead,TextFile,UniqueLabel,ReportItemsArray)
Call MoveToSectionHead(SectionHead,TextFile)
intInstances = FindValues(UniqueLabel,TextFile)
If intInstances > 0 Then
For i = 1 to intInstances
'Determine the next available row in the current worksheet
intRow = NextRowCount(SectionHead)
'Move over to the fourth column if the SectionHead
is "ComputerSystem:"
'because this sheet first gets 3 cols of data from
the OSInformation section.
If SectionHead = "ComputerSystem:" Then
intColPosition = 4
Else
intColPosition = 2
End If
For Each Item in ReportItemsArray
If item = "SkipLine" Then
'Doing this because you don't want to advance to the next
'column for a skipped line.
intColPosition = intColPosition - 1
objTextFile.SkipLine
Else
strItem = ReadVal(Item)
If strItem = " " Then
strItem = "Not Listed"
End If
Call InsertValues(SectionHead,intRow,
intColPosition,strItem,i)
End If
intColPosition = intColPosition + 1
Next
objTextFile.SkipLine
Next
End If
End Sub
'Select the worksheet based on the section of data being read
Function SelectWorkSheet(SectionHead)
arrSectionNames = Array("OSInformation:
ComputerSystem:", _
"PageFileSetting:","PhysicalMemoryArray:",
"SCSIController:", _
"IDEController:","PhysicalMedia:",
"LogicalDisk:","DiskDrive:", _
"Processor:","NetworkAdapter:",
"DesktopMonitor:", _
"VideoController:","BaseBoard:",
"BIOS:")
If SectionHead = "OSInformation:" Or _
SectionHead = "ComputerSystem:" Then
SelectWorkSheet = 0
Else
i=0
Do Until arrSectionNames(i) = SectionHead
i=i + 1
Loop
SelectWorkSheet = i
End If
End Function
Function NextRowCount(SectionHead)
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
If SectionHead = "ComputerSystem:" Then
NextRowCount = objExcel.ActiveSheet.UsedRange.Rows.Count
Else
NextRowCount = objExcel.ActiveSheet.UsedRange.Rows.Count + 1
End If
End Function
'this is the routine that inserts values in the spreadsheet
Sub InsertValues(SectionHead,RowPosition,ColPosition,Item,Instances)
Select Case SectionHead
Case "OSInformation:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Case "ComputerSystem:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:H1")
Case "PageFileSetting:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:C1")
Case "PhysicalMemoryArray:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:C1")
Case "SCSIController:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:D1")
Case "IDEController:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:D1")
Case "PhysicalMedia:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:B1")
Case "LogicalDisk:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:C1")
Case "DiskDrive:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:I1")
Case "Processor:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:G1")
Case "NetworkAdapter:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:E1")
Case "DesktopMonitor:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:C1")
Case "VideoController:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:D1")
Case "BaseBoard:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:D1")
Case "BIOS:"
objWorkBook.Sheets(arrWBNames(SelectWorkSheet(SectionHead))).Select
Call FillCells(ColPosition,RowPosition,Item,Instances)
Call FormatWorkSheet("A1:H1")
End Select
End Sub
Sub FillCells(ColPosition,RowPosition,Item,Instances)
'An instance value of 0 means there's nothing to write
'the section exists in the text file but there's no data
'in it.
If Instances = 1 Then
objExcel.Cells(RowPosition,1).Value = strComputerName
objExcel.Cells(RowPosition,ColPosition).Value = Trim(Item)
ElseIf Instances > 1 Then
objExcel.Cells(RowPosition,ColPosition).Value = Trim(Item)
End If
End Sub
Sub MoveToSectionHead(HeadName,TextFile)
Set objRegExp = New RegExp
objRegExp.Pattern = HeadName 'Look for this value
objRegExp.IgnoreCase = False 'Look for value w/specific case
objRegExp.Global = False 'Find only the first match
Set arrSectionHead = objRegExp.Execute(TextFile)
If objRegExp.Test(TextFile) <> False Then
strText = objTextFile.ReadLine
objRegExp.Execute(strText)
Do Until objRegExp.Test(strText) = True
strText = objTextFile.ReadLine
objRegExp.Execute(strText)
Loop
End If
End Sub
'This tells you how many values you have
in a section of hardware inventory
'data. Knowing this then tells you what
you have to read from the section.
Function FindValues(Label,TextFile)
Set objRegExp2 = New RegExp
objRegExp2.Pattern = Label 'Look for this value
objRegExp2.IgnoreCase = False 'Look for value w/specific case
objRegExp2.Global = True 'Find all matches in this section
Set arrValues = objRegExp2.Execute(TextFile)
i = 0
For Each Value in arrValues
i = i + 1
Next
FindValues = i
End Function
Function ReadVal(Label)
ReadVal = Mid(objTextFile.ReadLine,Len(Label) + 3)
End Function
Sub PrepareWorkSheet()
objWorkBook.SaveAs strReportPath &
"InvReport.xls",xlNormal
objExcel.SheetsInNewWorkBook = 14
'Ensure that the worksheet has enough sheets to run
'correctly. If not, use the Add method to add worksheets.
If objWorkBook.Sheets.Count < 14 then
For i = objWorkBook.Sheets.Count to 14
objWorkBook.Sheets.Add()
Next
End if
'iterate the array of new sheet names and rename the sheets
i=1
For Each WBName in arrWBNames
RenameSheets "Sheet" & i,WBName
i = i + 1
Next
'Column naming Section
'Name the Computer Systems columns
arrColumns = Array("Caption","Version",
"Manufacturer","Model", _
"# Processors","System Type","RAM")
Call NameColumns(0,arrColumns)
'Name the Page Files columns
arrColumns = Array("Maximum Size","Name")
Call NameColumns(1,arrColumns)
'Name the RAM columns
arrColumns = Array("Maximum Capacity","Tag")
Call NameColumns(2,arrColumns)
'Name the SCSI controllers columns
arrColumns = Array("Description",
"Manufacturer","Name")
Call NameColumns(3,arrColumns)
'Name the IDE controllers columns
arrColumns = Array("Description",
"Manufacturer","Name")
Call NameColumns(4,arrColumns)
'Name the Disk SerNums columns
arrColumns = Array("Serial Number")
Call NameColumns(5,arrColumns)
'Have to do this because Excel interprets a serial
'number (w/only numbers) as a numeric value rather
'than a string and then
'clips the value and uses and exponent designation
objWorkbook.ActiveSheet.Columns("B:B").Select
objExcel.Cells.EntireColumn.NumberFormat = "@"
objExcel.Cells.HorizontalAlignment = xlRight
objExcel.Range("A1").Select
'Name the Removable Media columns
arrColumns = Array("Descripton",
"Device ID")
Call NameColumns(6,arrColumns)
'Name the Fixed Disk columns
arrcolumns = Array("Description",
"Device ID","Interface Type", _
"Manufacturer","Media Type",
"Model","# of Partitions","Size")
Call NameColumns(7,arrColumns)
'Name the Processors columns
arrColumns = Array("DeviceID"
,"External Clock","Manufacturer", _
"Maximum Speed","Revision","Version")
Call NameColumns(8,arrColumns)
'Name the NICs columns
arrColumns = Array("Adapter Type"
,"MAC Address", _
"Manufacturer","Product Name")
Call NameColumns(9,arrColumns)
'Name the Monitors columns
arrColumns = Array("Description"
,"MonitorType")
Call NameColumns(10,arrColumns)
'Name the Video Adapters columns
arrColumns = Array("Adapter Memory"
,"Maximum Refresh Rate","Name")
Call NameColumns(11,arrColumns)
'Name the Motherboards columns
arrColumns = Array("Description"
,"Manufacturer","Product")
Call NameColumns(12,arrColumns)
'Name the BIOS columns
arrColumns = Array("Manufacturer"
,"Name","BIOS Version", _
"Major Version","Minor
Version","Software Element ID", _
"Version")
Call NameColumns(13,arrColumns)
'objWorkBook.Sheets("Computer Systems").Select
objWorkBook.Save
End Sub
Sub NameColumns(SheetNum,ColumnsArray)
objWorkBook.Sheets(arrWBNames(SheetNum)).Select
i=2
For Each ColumnHead in ColumnsArray
'All sheets will have ComputerName as the first column name
'so hard code it here.
objExcel.Cells(1,1).Value = "Computer Name"
objExcel.Cells(1,i).Value = ColumnHead
i = i + 1
Next
End Sub
'Rename each sheet
Sub RenameSheets(CurrentName,NewName)
objWorkBook.Sheets(CurrentName).Select
objWorkbook.ActiveSheet.Name = NewName
End Sub
'This is for formatting each worksheet.
Sub FormatWorkSheet(HeadCellRange)
objExcel.Range(HeadCellRange).Font.Bold = True
objExcel.Cells.EntireColumn.AutoFit
objExcel.Cells.EntireRow.AutoFit
End Sub
Листинг 3. Процедура QueryInstances
Sub QueryInstances(objClass,Properties,Conditions) If Conditions = "None" Then strSelect = "Select " & Properties & " From " & objClass Else strSelect = "Select " & Properties & " From " & objClass & _ " Where " & Conditions End If Set objClassName = _ objWMIService.ExecQuery _ (strSelect,,wbemFlagReturnImmediately + wbemFlagForwardOnly) intCounter = 0 ' Данный код можно использовать для определения количества элементов ' в sWbemObjectSet, но при этом возрастет нагрузка на процессор, ' что приведет к невозможности использования полусинхронных (semisynchronous) вызовов ' поскольку свойство count не работает с ' wbemFlagForwardOnly. ' WScript.Echo "# of items in collection: " & objClassName.Count For Each objComponent in objClassName If intCounter = 0 Then objFile.WriteLine(HR) objFile.WriteLine(Mid(objClass,7) & ":") End If For Each objProperty in objComponent.Properties_ ' Проверяем, существует ли значение для данного свойства. ' Если нет, то нет необходимости определять его в этом цикле. If ISNull(objProperty.Value) Then objFile.WriteLine(objProperty.Name & ": Not available") Else If objProperty.CIMType <> wbemCimtypeUint32 And _ objProperty.CIMType <> wbemCimtypeUint64 And _ objProperty.CIMType <> wbemCimtypeSint64 Then objFile.WriteLine(objProperty. Name & ": " & _ objProperty.Value & " " & _ GetUnits(objClass, objProperty.Name)) Else strUnits = GetUnits(objClass, objProperty.Name) intValue = _ SizeFormat(objProperty.Name, objProperty.Value,strUnits) objFile.WriteLine(objProperty.Name & ": " & intValue) End If End If intCounter = intCounter + 1 Next If intCounter > 1 Then objFile.WriteLine(HR) Next objFile.WriteLine(HR) objFile.WriteLine() End Sub