Однажды начальник вызывает вас к себе и сказал: "Бухгалтерия требует от нас к концу этой недели отчет по инвентаризации технических средств. И даже думать не смей просить денег на приобретение программы для этого." Так хорошо начинавшийся день вдруг резко изменился к худшему.
В данной ситуации можно поступить по-разному. Можно обойти все рабочие места компании и вручную собрать инвентаризационные данные по компьютерам, можно уволиться с работы или заявить, что поставленная задача не выполнима, а можно воспользоваться решением, построенным на базе двух сценариев, с помощью которого за рекордно короткое время можно собрать данные по инвентаризации аппаратных средств и сформировать отчет в формате 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