Системным администраторам часто приходится определять, какие программы установлены на компьютерах обслуживаемых ими сетей. Существует целый ряд обстоятельств, которые заставляют их регулярно проводить аудит приложений, установленных на корпоративных системах. Бывает, что администратор устанавливает программный пакет, забывает его задокументировать, а позднее оказывается, что в системе безопасности пакета имеются уязвимые места. Иногда пользователи наделяются административными правами, позволяющими им самостоятельно устанавливать программные продукты на свои компьютеры. Существует и такая опасность: пользователь, самовольно присвоивший полномочия администратора, может установить несанкционированное программное обеспечение, открывая тем самым возможность привлечения организации к судебной ответственности.

Независимые провайдеры предлагают средства для мониторинга установленного программного обеспечения, но у небольших предприятий может не оказаться ресурсов для реализации такого решения. В нашей компании не было программного средства аудита, поэтому я написал сценарий Windows PowerShell Get-InstalledApp.ps1 (см. листинг). Перед тем как приступить к описанию процедуры использования этого сценария и механизма его работы, мне бы хотелось остановиться на двух методах обнаружения установленного программного обеспечения с помощью сценария и пояснить, чем меня привлек тот метод, на котором я остановил выбор.

Класс Win32_Product или реестр

В инструментарии управления Windows Management Instrumentation (WMI) имеется класс Win32_Product, который позволяет регистрировать приложения, установленные на компьютере. В среде PowerShell эта возможность реализуется просто. Так, команда

Get-WmiObject Win32_Product
   | select name

генерирует список всех приложений, установленных на исследуемом в данный момент компьютере. Однако надо сказать, что классу Win32_Product присущ ряд ограничений.

  • Он считывает имена лишь тех приложений, которые были установлены с помощью службы Windows Installer. Имена приложений, установленных иными способами, с помощью данного класса не считываются. Это значит, что использование класса Win32_Product с целью осуществления общего аудита программных средств невозможно, кроме тех случаев, когда для установки пакетов используется исключительно установщик Windows, а это практически неосуществимо для большинства сетей.
  • Считывание экземпляров класса Win32_Product выполняется весьма медленно.
  • Не всегда возможно считывание экземпляров класса Win32_Product с удаленных компьютеров. Так, когда я пытаюсь выполнить подобную процедуру в своей сети, то получаю сообщение об ошибке Generic failure.

Из-за перечисленных проблем целесообразность применения класса WMI Win32_Product снижается. Однако имеется альтернативный метод получения информации об установленных приложениях — непосредственно из реестра. Данные об установленных на компьютере приложениях содержатся в разделе реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows\CurrentVersion\Uninstall. В каждом подразделе раздела Uninstall представлено установленное приложение, а значения в каждом подразделе отображают информацию о соответствующем приложении, как показано на экране. Таким образом, чтобы получить список приложений, можно «прочесать» раздел Uninstall и считать данные каждого подраздела, входящего в раздел Uninstall.

Экран. Один из подразделов раздела Uninstall

Провайдер реестра PowerShell дает возможность выполнять команду Get-ChildItem для получения списка имен приложений, установленных на исследуемом компьютере:

Get-ChildItem HKLM:\SOFTWARE\
   Microsoft\Windows\CurrentVersion\
   Uninstall |
ForEach-object {(Get-ItemProperty
   Microsoft.PowerShell.Core\Registry::$_).
   DisplayName}

Однако в среде PowerShell 1.0 команда Get-ChildItem не обеспечивает доступ к провайдеру реестра на удаленном компьютере, поэтому для достижения цели администратору приходится задействовать StdRegProv, класс управления реестром инструментария WMI. Класс StdRegProv предусматривает возможность использования полезного набора методов, облегчающих считывание реестра вне зависимости от того, с какой системой осуществляется взаимодействие, локальной или удаленной. Дополнительную информацию о классе StdRegProv можно найти на странице StdRegProv Class на сайте MSDN (http://msdn.microsoft.com/en-us/library/aa393664.aspx).

Как работать со сценарием Get-InstalledApp.ps1

Сценарий Get-InstalledApp.ps1 считывает информацию об установленных приложениях из реестра, после чего возвращает либо список всех установленных приложений, либо список приложений, отвечающих заданным критериям. В сценарии используется следующий синтаксис командной строки:

Get-InstalledApp [-computername]
[-appID] [-appname]
[-publisher] [-version] [-matchall]

Параметр computername используется для выведения списка, состоящего из одного имени компьютера или из списка имен компьютеров. Параметр computername является первым по счету, поэтому для сокращения команды вы можете опустить ключ -computername и просто включить в команду имя компьютера. Если же полностью исключить этот параметр, сценарий будет исходить из того, что вы хотите получить данные по локальному компьютеру.

Параметр -appID используется для поиска приложений по их идентификаторам (application ID). Идентификатор приложения — это подраздел реестра, относящийся к разделу Uninstall. На экране идентификатор приложения выделен в левой панели. Для приложений, установленных с помощью установщика Windows, идентификактор приложения эквивалентен глобальному уникальному идентификатору (идентификатору GUID) ID продукта соответствующего приложения. Использование параметра -appID — лучший способ обнаружения конкретных приложений, установленных с помощью службы Windows Installer.

Параметр -appname применяется для поиска приложений по их отображаемому имени. Отображаемое имя — это имя приложения в том виде, в каком оно появляется в списке Add/Remove Programs или значение DisplayName в подразделе соответствующего приложения. Так, на экране выделенное приложение имеет отображаемое имя OpenOffice.org 2.4.

Параметр -publisher используется для поиска приложений по издателю. Если у вас нет точных сведений о том, кто является издателем приложения, вы можете найти их в записи Publisher подраздела реестра для этого приложения. Так, издателем выделенного приложения на экране является OpenOffice.org.

Параметр -version используется для поиска приложений по их версиям. Если вы не знаете наверняка, какова версия того или иного приложения, можете считать эти данные из параметра DisplayVersion подраздела этого приложения. К примеру, приложение, выделенное на экране, имеет версию 2.4.9286.16.

Если для файла Get-InstalledApp.ps1 указать ключ -matchall, сценарий будет выводить все соответствующие заданному критерию приложения, вместо того чтобы прекратить поиск после обнаружения первого соответствия. Так, команда

Get-InstalledApp -publisher
   "Microsoft Corporation" -matchall

возвратит список всех приложений Microsoft, установленных на исследуемом компьютере. Если же вы опустите ключ -matchall, сценарий прекратит поиск после обнаружения первого соответствия.

Все перечисленные ключи (-appID, -appname, -publisher и -version) — поддерживают поиск с использованием символов шаблонов; аргументы ключей нечувствительны к регистру. Так, команда

Get-InstalledApp -appname
   "*office*" -matchall

выводит список всех установленных на исследуемом компьютере приложений, в именах которых есть слово office. Кроме того, для указания более детализированных критериев поиска можно указывать параметры -appID, -appname, -publisher и -version в любых сочетаниях. Так, команда

Get-InstalledApp sales01-appname
   "*.NET Framework*" -version "2*"

выводит первое приложение, содержащее слова .NET Framework в отображаемом имени и имеющее версию, номер которой начинается с цифры 2. Если приложения, отвечающие указанным критериям, не обнаружены, сценарий не возвращает каких-либо значений. Для получения дополнительных сведений о шаблонных символах в среде PowerShell, выполните команду

Get-Help about_wildcard

Сценарий Get-InstalledApp.ps1 возвращает объекты, содержащие свойства ComputerName, AppID, AppName, Publisher и Version, поэтому вы можете использовать команды PowerShell и форматировать выходные данные в соответствии со своими потребностями. Так, команда

Get-InstalledApp | Select-Object AppName,
   Version |
Sort-Object AppName

выводит список приложений и версию каждого приложения, отсортированные по именам приложений. Чтобы с помощью значений, разделенных запятыми, создать отчет по всем программным продуктам, установленным на всех компьютерах, которые перечислены в файле Computers.txt, нужно использовать команду

Get-InstalledApp (Get-Content
   Computers.txt) |
Export-CSV Report.csv -notypeinformation

Параметр -NoTypeInformation команды Export-CSV блокирует передачу информации во выходной файл CSV. Команда

Get-InstalledApp (Get-Content
   Computers.txt)
-appID "{7131646D-CD3C-40F4-97B9-
   CD9E4E6262EF}" |
Select-Object ComputerName

формирует список упомянутых в файле Computers.txt компьютеров, на которых установлена библиотека .NET Framework 2.0. Если вы хотите получить упорядоченный список установленных на исследуемой системе приложений Microsoft и их версии, отсортированные по именам приложений, воспользуйтесь командой

Get-InstalledApp -publisher
   "Microsoft Corporation" -matchall |
Select-Object AppName, Version |
   Sort-Object AppName

Внутренние механизмы сценария Get-InstalledApp.ps1

Теперь, когда вы знаете, как пользоваться сценарием Get-InstalledApp.ps1, давайте посмотрим, каким образом он функционирует. Сначала с помощью команды param сценарий объявляет параметры командной строки; затем он создает две глобальные переменные для использования в дальнейшем при работе с классом StdRegProv. После этого сценарий объявляет функцию usage и главную функцию. Последняя строка сценария вызывает главную функцию.

Функция main содержит основное тело сценария. Прежде всего, она выясняет, имеется ли в командной строке параметр -help. Если да, то функция main вызывает функцию usage, которая генерирует сообщение об использовании и завершает выполнение сценария.

Далее основная функция создает пустую хеш-таблицу и сохраняет ее в переменной $propertyList, как показано во фрагменте А листинга.

Если в командной строке имеется хотя бы один из параметров (-appID, -appname, -publisher или -version), функция добавляет к хеш-таблице ключ, соответствующий имени параметра, и задает значение этого ключа аргументу параметра. Так, команда

Get-InstalledApp -appname
   "Windows Support Tools"

предписывает главной функции заполнить хеш-таблицу с помощью ключа AppName, который имеет значение Windows Support Tools.

Затем функция main выясняет, является ли пустым параметр computername. Если параметр пуст, функция использует имя локального компьютера, которое она получает из переменной среды COMPUTERNAME. Если же параметр не является пустым, функция перебирает массив имен компьютеров с помощью цикла foreach. Цикл выполняется только раз, если аргумент computername параметра представляет собой имя одного компьютера.

Внутри цикла foreach функция main объявляет переменную $err и задает ее значение равным $NULL. После этого с помощью специального блока сценария (trap scriptblock) функция выявляет ошибки WMI, как показано во фрагменте B листинга. При возникновении ошибки «ловушечный» блок сценария обновляет переменную ошибки до уровня текущей записи ошибки (то есть задает ей значение $ERROR [0]) и продолжает выполнение команды, следующей за ошибкой.

Далее основная функция подключается к классу StdRegProv на исследуемом компьютере с помощью ускорителя типа [WMIClass] для класса System.Management.ManagementClass платформы Windows.NET Framework. В случае возникновения ошибки последняя выявляется с помощью специального блока сценария, представленного во фрагменте C листинга. Если значение переменной $err не равно $NULL, это означает, что был запущен «ловушечный» блок сценария. В этом случае функция main формирует запись об ошибке с помощью команды Write-Error, а затем использует команду continue, позволяющую пропустить оставшуюся часть цикла foreach и перейти к следующему имени компьютера в массиве.

После этого функция main использует метод EnumKey класса StdRegProv для перечисления подразделов раздела Uninstall. Результат применения метода EnumKey — массив имен подразделов — хранится в свойстве sNames. Далее функция перебирает массив имен подразделов с помощью другого цикла foreach.

При обработке каждого представленного в массиве имени подраздела функция main с помощью метода GetStringValue класса StdRegProv считывает свойство DisplayName соответствующего раздела реестра. Используя команду Join-Path, функция присоединяет имя подраздела к ключу Uninstall. Образующийся в результате маршрут к реестру используется в качестве аргумента для метода GetStringValue, который возвращает значение записи реестра. Это должно быть строковое значение. Данный метод принимает три аргумента: ветвь реестра, путь к разделу и имя параметра. Значение GetStringValue возвращает объект, содержащий два свойства: свойство ReturnValue, которое включает значение, указывающее на успешный или неудачный вызов метода, и свойство sValue, содержащее строковое значение. Функция main присваивает значение свойства sValue переменной $name.

Если значение переменной $name не равно $NULL, функция main создает настраиваемый выходной объект, содержащий четыре свойства: ComputerName, AppID, AppName, Publisher и Version. Она обновляет свойство объекта ComputerName именем исследуемого компьютера, свойство объекта AppID — именем текущего подраздела, а свойство объекта AppName — отображаемым именем текущего приложения. Далее функция дважды применяет метод GetStringValue с целью извлечения значений записей Publisher и DisplayVersion для обновления соответствующих свойств в настраиваемом объекте с помощью этих значений.

Итак, функция main завершила получение информации о текущем приложении и теперь проверяет хеш-таблицу $propertyList на наличие ключей; для этого проверке подвергается свойство Count коллекции Keys. Если таблица хеширования не содержит ключей (иначе говоря, если в командной строке не представлен ни один из параметров (-appID, -appname, -publisher и -version)), тогда функция просто генерирует настраиваемый объект, чтобы выяснить, аргументы какого параметра командной строки соответствуют значениям свойств настраиваемого объекта. Фрагмент D листинга демонстрирует, каким образом функция main решает эту задачу.

Сначала значение переменной $matches задается равным 0, затем код проходит по всем элементам коллекции Keys хеш-таблицы $propertyList. Для каждого ключа в таблице хеширования $propertyList функция применяет оператор среды PowerShell -like, чтобы выяснить, соответствует ли данное свойство настраиваемого объекта равносильному значению хеш-таблицы $propertyList hashtable. В случае соответствия значений указанных элементов функция увеличивает значение переменной $matches. Если число соответствий равно числу ключей в таблице хеширования, функция main формирует настраиваемый объект. Наконец, если в командной строке отсутствует параметр -matchall, функция использует команду break для выхода из цикла foreach, который применяется для перебора подразделов реестра.

Таким образом, сценарий Get-InstalledApp.ps1 позволяет без особых хлопот получить список приложений, установленных на одном или нескольких компьютерах. Более того, он может организовать поиск заданных приложений. Возможно, для мониторинга программных средств, установленных на компьютерах вашей сети, вполне можно будет обойтись данным сценарием.

Билл Стюарт (bill.stewart@frenchmortuary.com) — системный и сетевой администратор компании French Mortuary (Нью-Мехико)


Листинг. Сценарий Get-InstalledApp.ps1