АНДРЕЙ БИРЮКОВ
Как эффективно провести инвентаризацию установленного софта
Зачастую сбор информации об установленных на клиентских машинах приложениях является для системных администраторов настоящей пыткой, особенно в крупных сетях. Рассмотрим использование WSH-сценариев и групповых политик Active Directory для этой цели.
Как правило, в сетях большинства организаций у пользователей нет прав на установку приложений, и на все машины «залит» одинаковый образ операционной системы. Однако даже в таких строго построенных сетях рано или поздно возникает необходимость в установке отдельным пользователям программ, которые требуются им для работы. Так, например, бухгалтерам нужны различные конфигурации 1С, а менеджерам нужна определенная система управления проектами и так далее. Или же еще более сложная ситуация: ваша компания объединилась с другой, а у той организации корпоративным стандартом антивируса является, к примеру, Symantec, а в вашей – Trend Micro, и вам необходимо узнать, сколько лицензий того и другого программного продукта необходимо продлить в следующем году. Конечно, идеальным решением во всех описанных выше ситуациях было бы четкое документирование сисадмином каждого запроса на установку программного обеспечения. Однако в реальности все обстоит не так хорошо, и администратору, особенно в крупной сети, необходимо какое-либо программное средство для автоматического сбора информации о приложениях. В данной статье для решения поставленной задачи предлагается сценарий Windows Script Host, который с помощью групповой политики Active Directory будет запускаться на клиентских машинах, собирать в текстовый файл информацию об установленных приложениях и затем сохранять ее на разделяемом сетевом ресурсе.
Теоретическая часть
Как известно, информация об установленных в ОС Windows приложениях хранится в реестре. Точнее, в разделе HKEY_LOCAL_MACHINESoftware. Если зайти в эту ветвь реестра, то можно увидеть подразделы, которые содержат информацию об установленных приложениях, в частности, название каждого подраздела соответствует установленному приложению. Конечно, можно собирать информацию о приложениях отсюда, однако это не самый лучший вариант, так как в этой ветке реестра также содержится много служебной информации, которая может существенно осложнить сбор данных и в конечном итоге замедлить работу системы. Гораздо удобнее воспользоваться веткой HKLMSoftwareMicrosoftWindowsCurrentVersionUninstall. Как парадоксально это ни выглядит, но каждое приложение после установки прописывает свои данные в эту ветку реестра. Причем все данные содержат несколько обязательных полей, например поле DisplayName, с помощью которого мы и будем идентифицировать приложения, установленные в системе. На рабочей машине это может выглядеть, например, вот так (см. рис. 1).
Рисунок 1. Содержимое ветки реестра Uninstall
Таким образом, для того чтобы собрать информацию об установленных в системе пользовательских приложениях, необходимо обойти значения ветки реестра Uninstall и получить содержимое параметра DisplayName. Этот параметр является обязательным для записей об установленных приложениях данной ветки и содержит значение строкового типа, которое удобно сохранять в текстовом файле. Если параметр ветки Uninstall не содержит поля DisplayName, то значит это не установленное приложение, а какая-то служебная информация, и заносить эти данные в отчет не требуется. Таким вот довольно простым способом можно собрать необходимую нам информацию.
Итак, рассмотрев теоретические аспекты написания данного сценария, перейдем к практической части. Исходный текст сценария, который был взят за основу, можно найти по адресу http://www.computerperformance.co.uk/ezine/ezine63.htm.
Практическая часть
Для программной реализации поставленной задачи мы традиционно воспользуемся WSH-сценарием, написанным на языке VBScript. Все собранные данные будем сохранять в текстовом файле, название которого будет состоять из имени компьютера, на котором выполнялся сценарий. Так как сохранять файлы с результатами на локальных дисках клиентских машин, а потом забирать их оттуда вручную – решение не самое удачное, особенно в больших сетях, то мы должны подготовить на сервере сетевую папку, с правами на запись для администратора. А наш сценарий должен уметь подключать эту папку в качестве сетевого диска, сохранять туда файлы с результатами и затем отключать. Сделать эти простые действия можно с помощью следующих команд VBScript:
Листинг 1. Подключение сетевого диска
On Error Resume Next
Set objNetwork = CreateObject("WScript.Network")
Set CheckDrive = objNetwork.EnumNetworkDrives()
objNetwork.MapNetworkDrive "X:", "\\10.0.1.5\share"
А отключить сетевой диск можно с помощью команды:
Листинг 2. Отключение сетевого диска
WScript.Sleep 3000
objNetwork.RemoveNetworkDrive CheckDrive.Item(0), true
Конечно, если в вашей сети уже используются сетевые диски или после подключения сетевого ресурса вам не нужно отключать его (так как он может использоваться еще для каких либо целей), то вы можете не использовать данные команды и сразу перейти к написанию сценария, описанного ниже.
Ключевым моментом нашего сценария является цикл, обходящий ветку реестра Uninstall:
Листинг 3. Обходим ветку реестра и сохраняем данные в файл
unKeyPath = "Software\Microsoft\Windows\CurrentVersion\Uninstall"
unValueName = ("DisplayName")
objReg.EnumKey HKLM, unKeyPath, arrSubKeys
i=0
For Each Subkey in arrSubKeys
objDictionary2.Add i, unKeyPath & "\"& subkey & (Enter)
i=i+1
Next
………………………
For Each objItem in objDictionary2
strKeyPath = objDictionary2.Item(objItem)
objReg.GetStringValue HKLM ,strKeyPath, unValueName, strValue
'сохраняем нужное значение в файл
objTextFile2.WriteLine (strValue)
If Err Then
objDictionary2.Remove(objItem)
End If
Next
Теперь остается лишь оформить полученные данные должным образом (то есть добавить информацию об имени пользователя, имени компьютера и времени выполнения сценария) и сохранить в файле.
Для получения информации об имени пользователя и названии машины воспользуемся следующими двумя значениями параметров реестра:
Листинг 4. Ветки, содержащие информацию об имени пользователя и имени компьютера
pcName = "SYSTEMCurrentControlSetControlComputerNameActiveComputerName"
pcNameValueName = "ComputerName"
userPath = "SoftwareMicrosoftWindows NTCurrentVersionWinlogon"
userValueName = "DefaultUserName"
В результате работы описанных выше процедур мы получаем текстовый файл, в заголовке которого будут имя пользователя, название, дата и время создания.
Приведем полный текст сценария для сбора информации об установленных на компьютере приложениях:
Листинг 5. Полный текст сценария
' Объявляем константу HKEY_LOCAL_MACHINE
Const HKLM = &H80000002
Const REG_SZ = 1 '
Const ForReading = 1
Const ForWriting = 2
On Error Resume Next
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objDictionary2 = CreateObject("Scripting.Dictionary")
Dim objNetwork, objShell, CheckDrive
Dim strDriveLetter, strRemotePath, intDrive, bforce
' Данные для сетевого диска
strDriveLetter = UCase("x:" ) ' Имя диска
strRemotePath = "\\10.0.1.5\tmp" ' Сетевой путь
bforce = true
Set objShell = CreateObject("WScript.Shell")
Set objNetwork = CreateObject("WScript.Network")
Set CheckDrive = objNetwork.EnumNetworkDrives()
' А это уже путь, где будет создан файл с информацией о приложениях
strPath = "x:\"
strComputer = "localhost" ' Проверяем локальный компьютер
' Подключаем сетевой диск
objNetwork.MapNetworkDrive strDriveLetter, strRemotePath
' Запускаем процедуру сбора информации
GetInstalledSoftware()
' Отключаем сетевой диск
If CheckDrive.Item(intDrive) <> strDriveLetter Then objNetwork.RemoveNetworkDrive CheckDrive.Item(0), bforce
Sub GetInstalledSoftware()
On Error Resume next
Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\default:StdRegProv")
If Err <> "0" Then
Exit Sub
End If
' Путь к разделу реестра Uninstall, содержащему информацию об установленных компонентах
unKeyPath = "Software\Microsoft\Windows\CurrentVersion\Uninstall"
unValueName = ("DisplayName")
objReg.EnumKey HKLM, unKeyPath, arrSubKeys
i=0
For Each Subkey in arrSubKeys
objDictionary2.Add i, unKeyPath & "\"& subkey & (Enter)
i=i+1
Next
pcName = "SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName\"
pcNameValueName = "ComputerName"
userPath = "Software\Microsoft\Windows NT\CurrentVersion\Winlogon\"
userValueName = "DefaultUserName"
objReg.GetStringValue HKLM,pcName,pcNameValueName,pcValue
objReg.GetStringValue HKLM,userPath,userValueName,userValue
strFileName = UserValue & "_" & "On" & "_" & PCValue & "_"& "Software" _
& year(date()) & right("0" & month(date()),2) _
& right("0" & day(date()),2) & ".txt"
Set objTextFile2 = objFSO.OpenTextFile(strPath & strFileName, ForWriting, True)
objTextFile2.WriteLine(vbCRLF & _
"Current Installed Software " & vbCRLF & Time & vbCRLF & Date _
& vbCRLF & "Software For:" & "" & userValue & vbCRLF & "On System:" _
& "" & pcValue & vbCRLF)
For Each objItem in objDictionary2
strKeyPath = objDictionary2.Item(objItem)
objReg.GetStringValue HKLM ,strKeyPath, unValueName, strValue
objTextFile2.WriteLine (strValue)
If Err Then
objDictionary2.Remove(objItem)
End If
Next
End Sub
wscript.Quit
Сбор урожая
Если в вашей локальной сети менее десяти компьютеров, то запуск описанного выше сценария не составит особого труда. Можно воспользоваться столь любимой всеми системными администраторами программой Remote Admin и удаленно запустить сценарий на каждой клиентской машине. Однако все эти решения требуют от администраторов много ручной работы, а это не слишком хорошо. В оригинальной версии сценария [2] предлагалось использовать следующую процедуру, которая сначала собирает данные о машинах в домене, потом пытается запустить сценарий на каждом из этих компьютеров.
Листинг 6. Запуск сценария на удаленных машинах домена
Set objPCTXTFile = objFSO.OpenTextFile("c:\test.txt", ForWriting, True)
' Получаем информацию о домене
Set objDomain = GetObject("WinNT://" & strDomain)
' Интересует объект компьютеры
objDomain.Filter = Array("Computer")
For Each pcObject In objDomain
objPCTXTFile.WriteLine pcObject.Name
Next
objPCTXTFile.close
' Читаем компьютеры из созданного файла
Set readPCFile = objFSO.OpenTextFile(strPath & strPCsFile, ForReading)
i = 0
Do Until readPCFile.AtEndOfStream
strNextLine = readPCFile.Readline
objDictionary.Add i, strNextLine
i = i + 1
Loop
readPCFile.Close
' Запускаем процедуру сбора информации на каждом компьютере
For each DomainPC in objDictionary
strComputer = objDictionary.Item(DomainPC)
GetInstalledSoftware()
Next
Такая конструкция, конечно, имеет право на жизнь, несмотря на свою некоторую неуклюжесть. Однако для того чтобы с помощью такого цикла получить информацию со всех компьютеров сети, необходимо, чтобы в момент запуска сценария все машины были включены. Даже в небольшой компании добиться этого не просто. Всегда есть есть отсутствующие сотрудники, ноутбуки и удаленные филиалы, с которыми нет постоянного соединения.
Таким образом мы приходим к выводу, что вариант с одновременным опросом всех машин в сети нас не устраивает. К тому же с помощью такой реализации практически невозможно собрать информацию в сети, которая географически разнесена по разным регионам, когда в одном офисе рабочий день только начинается, а в другом – уже заканчивается.
Возможен еще другой вариант для запуска нашего сценария на пользовательских машинах – это Logon Script. Для доступа к нему необходимо в свойствах пользователя (или нескольких выбранных пользователей) в закладке Profile указать путь к файлу сценария (см. рис. 2).
Рисунок 2. Указываем Logon Script
Тогда наш сценарий будет выполняться при входе пользователя в домен. Такое решение уже более универсально, чем предыдущее, но все равно от администратора требуется вручную выделить всех пользователей, у которых должен быть запущен сценарий. А если таких пользователей сотни? К тому же если у нас несколько пользователей заходят на один и тот же компьютер, то мы получим несколько одинаковых файлов отчета, различающихся только именем пользователя. Думаю, разбирать потом все это будет не слишком удобно. Так что такой вариант для больших сетей тоже не подходит.
Очевидно, что для подобных случаев лучше всего выполнять сценарии на клиенте с помощью групповых политик Active Directory. Групповую политику можно рассматривать как набор правил для управления пользователями и компьютерами. Групповую политику разрешается применять в нескольких доменах и в подгруппах внутри домена или индивидуальных системах. Параметры групповой политики хранятся в объекте групповой политики (Group policy Object, GPO). К одному и тому же домену или подразделению можно применять несколько объектов GPO. Подробнее о взаимодействии политик, свойствах Block Inheritance и No Override можно прочитать в [1].
Итак, создадим свою групповую политику и используем ее для автоматизации развертывания нашего сценария. Для этого зайдем локально или удаленно на контроллер домена «Administrative Tools Active Directory Users and Computers Домен». В меню «Action New Organization Unit» (введем название), затем в свойствах созданной организационной единицы закладка «Group Policy», нажимаем «New», указываем имя новой политики и затем, нажав «Edit», попадаем в редактор объектов GPO. Для решения поставленной задачи развертывания сценария на машинах пользователей нам удобнее будет использовать раздел «Computer Management», конечно, если мы используем «Computer Management», ничего страшного не произойдет, но, как и в случае с Logon Script, если за компьютером работают несколько пользователей, то мы рискуем получить несколько одинаковых файлов отчетов, которые будут отличаться только именем пользователя в заголовке. В разделе «Computer Management» заходим в «Windows Settings», далее «Scripts» (см. рис. 3).
Рисунок 3. Объекты групповой политики
Здесь возможны два варианта «Startup» и «Shutdown». Логичнее будет использовать наш сценарий при запуске пользовательской машины.
Нажимаем «Add» и добавляем файл сценария без параметров. Все, теперь наш сценарий будет выполнен при следующем запуске компьютера. В случае если необходимо использовать раздел «User Configuration», соответствующие разделы будут «Windows Settings» и «Scripts (Logon/Logoff)». Теперь остается только перетащить машины, к которым будет применена данная групповая политика в созданную нами Organization Unit.
Следует сказать несколько слов о нагрузке на сеть, которую создаст наш сценарий в тот момент, когда пользователи, придя утром на работу, начнут массово входить в сеть. Что касается времени выполнения сценария и его нагрузки на компьютер пользователя, то на средней машине с 2030 установленными приложениями (хотя в тех организациях, где люди прежде всего работают, таких приложений, как правило, меньше) время работы сценария будет, не более трех секунд. По поводу трафика стоит отметить, что на той же машине с 30 установленными приложениями файл отчета займет около 3 килобайт, так что даже в моменты пиковой нагрузки речь может идти лишь о нескольких сотнях килобайт трафика.
Подведение итогов
Итак, мы собрали все необходимые нам сведения об установленных на пользовательских машинах приложениях и сохранили их в виде отдельных файлов на сервере. Но такой способ хранения полученной информации не слишком удобен, к тому же мы не решили одну из задач, поставленных в начале статьи, – сбор информации о количестве установленных приложений, необходимой для осуществления лицензирования. Таким образом, нам требуется, во-первых, собрать все данные в единый файл и, во-вторых, просуммировать все одинаковые приложения. Обе эти задачи мы решим с помощью Microsoft Excel. Напишем WSH-сценарий, который необходимо будет разместить в том же каталоге, что и файлы, которые мы получили. После запуска данный сценарий поочередно открывает каждый из файлов каталога и экспортирует данные о приложениях в таблицу Excel, затем производится сбор сведений о количестве установок, которые также экспортируются в Excel. В результате работы сценария мы получаем книгу Excel, содержащую два листа: На первом таблица, состоящая из двух столбцов, – имя компьютера и наименование приложений.
На втором листе список приложений и количество установок каждого из них.
Рисунок 4. Таблица Excel с результатами
Далее приводится фрагмент сценария, реализующего все эти действия.
Листинг 7. Создание документа Excel c результатами
…………….
Set s = oE.Sheets(1) ' Лист 1
s.Name = "Список"
Set s1 = oE.Sheets(2) ' Лист 2
s1.Name = "Сумма"
s.Rows(1).RowHeight = 2 * s.StandardHeight
s.Cells(1,1) = "Список установленных приложений"
s.Cells(2,1) = "Имя компьютера"
s.Cells(2,2) = "Приложения"
s.Columns("A:B").Columns.AutoFit
With s.Range("A1:B1")
.MergeCells = True
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
.Font.Size=14
End With
s1.Rows(1).RowHeight = 2 * s1.StandardHeight
s1.Cells(1,1) = "Количество установленных приложений"
s1.Cells(2,1) = "Приложение"
s1.Cells(2,2) = "Всего установок"
s1.Columns("A:B").Columns.AutoFit
With s1.Range("A1:B1")
.MergeCells = True
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
.Font.Size=14
End With
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set fso = CreateObject("Scripting.FileSystemObject")
' Получаем сведения о файлах, находящихся в данной папке
Set fld = fso.GetFolder(".")
Set objTextFile2 = objFSO.OpenTextFile("c:\temp\tmp", ForWriting, True)
For Each fi In fld.Files
If fi.name<>"excel.vbs" AND fi.name<>"result.xls" Then
' Файл сценария и файл отчета не открывать
Set readPCFile = objFSO.OpenTextFile(fi, ForReading)
i = 0
s.Cells(n, 1) = fi.name ' Имя файла – это имя компьютера
Do Until readPCFile.AtEndOfStream
strNextLine = readPCFile.Readline
i = i + 1
' Первые 6 строк в файлах отчетах – это шапка,
' экспортировать не нужно
If i>6 Then
s.Cells(n, 2) = strNextLine
objTextFile2.WriteLine (strNextLine)
n = n + 1
End If
Loop
readPCFile.Close
End If
Next
objTextFile2.close
f=3
Set objTextFile2 = objFSO.OpenTextFile("c:\temp\tmp", ForReading)
Do Until ObjTextFile2.AtEndOfStream
strNextLine = objTextFile2.Readline
' ищем вхождения имени приложения
Set fc = s1.Columns("A").Find(strNextLine)
If fc Is Nothing Then ' не нашли, тогда добавляем
s1.Cells(f,1)=strNextLine
Set fc = s.Columns("B").Find(strNextLine)
If Not fc Is Nothing Then
count=0
With s.Columns("B")
Set c = .Find(strNextLine)
If Not c Is Nothing Then
firstAddress = c.Address
Do
' считаем количество установок
count=count+1
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
s1.Cells(f,2)=Count ' сохраняем количество установок
End If
f=f+1
End If
Loop
Теперь поставленная перед нами задача по сбору сведений об установленных на клиентских машинах приложениях решена полностью. Мы получили таблицу Excel, которая содержит сведения о том, какие приложения установлены на каждой конкретной машине, а также сведения о том, сколько установок того или иного приложения имеется в сети. Лучше всего выполнять описанные выше сценарии регулярно, с частотой один раз в месяц, для того чтобы всегда иметь под рукой актуальную информацию об используемом программном обеспечении.
Литература:
- Windows Server 2003. Справочник администратора.
- http://www.computerperformance.co.uk – англоязычный сайт, содержащий множество примеров WSH сценариев, с комментариями.