четверг, 3 февраля 2011 г.

Инвентаризация Часть 2 -Скрипт развертывания


Пишу с этого места, потому что нахожусь на этом этапе.
Суть в замечательной связке OCS Inventory + GLPI.
Обсуждалось много много раз:
Позже опишу свой взгляд на вещи и допишу начало.

Итак, приступим.
Мы имеем:
·                     Виртуальный сервер под управлением Ubuntu 10.04 для web-сервера OCS и GLPI, а так же MySQL.
·                     Распределенные географически филиалы, не во все из них есть оперативный доступ.
·                     В филиалах есть контроллеры домена.
·                     Домен один.
·                     Почти все клиентские машины в домене.
Нам надо:
·                     Описать автоматически более 100 клиентских машин.
·                     Создать надежный механизм с обратной связью для автоматического распространения ПО(Для начала для развертывания агента OCS).
·                     Реализовать возможность подключения к удаленому устройству(RDP, VNC, SSH, Web-интерфейс).
·                     Реализовать автоматическую генерацию карточек учета оборудования.
·                     Придумать как подкорректировать работу службы тех. поддержки в свете использования менеджера ИТ ресурсов компании – GLPI.
После чтения официального мануала по развертыванию агента инвентаризации в среде MS Windows сложилось двоякое ощущение.
Если мы имеем домен, все клиентские машины которого территориально расположены в одном месте, то этот вариант нам пойдет. Но все же непонятно, каким образом и кто будет указывать TAG, а ведь это основной элемент для сортировки описанных компьютеров.
Я пробовал раскидывать компьютеры домена по  OU(в качестве названия – наименование отдела), создавать для каждого OU свою групповую политику. Политики отличались между собой только параметром TAG(туда я писал название отдела). И в принципе оно работает J, но я думаю, в этом способе есть смысл если у нас мало таких административных единиц, а так же если извесно какой компьютер к какой единице относится. Для множества других случаев(в том числе и для нашего) этот способ не подойдет.
Еще очень сложно провести замену агентам, например после неудачного обновления, или переписать файл конфигурации агента, что бывает порой необходимо.
Бегать и руками все это делать - не наш метод :) Мы будем писать скрипт.
Из всех скриптовых языков был выбран Windows PowerShell, в качестве среды программирования PowerGUI.
Сразу скажу, что PowerShell я знаю плохо, но начинать когда то стоит.
Составим список необходимой функциональности скрипта:
  • Скрипт должен «уметь» определять в какой подсети он находится, чтобы загружать дистрибутив с ближайшего сервера.
  • Определять архитектуру ОС(в х86 архитектуре и х64 папки Program Files для 32бит программ отличаются).
  • Уметь «метить» компьютер, для того чтобы определять скрипт ли поставил туда ту или иную программу, версию установки и т.д.
  • Осуществлять определенные действия с программами(удаление\установка\переустановка).

Итак, начнем по порядку, для определения подсети используем WMI-объект Win32_NetworkAdapterConfiguration,выберем только активное соединение, возьмем адрес шлюза и вытащим из него третье слово. Получилось нечто вот такое:

$host_zone=((Get-WmiObject Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE | Select-Object -ExpandProperty DefaultIPGateway) -split "\s")  -split "\." | select -last 2 | select -first 1

Для адреса шлюза например, 192.168.20.1 переменная $host_zone будет иметь значение 20. Следует иметь в виду, если мы вместо основного шлюза будем использовать IP адрес активного подключения, то результат будет другим. Если включена поддержка IP v6 то результат команды:

Get-WmiObject Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE | Select-Object -ExpandProperty IPAddress

Будет массив, состоящий из 2х элементов – адреса IP v4 и IP v6. Это следует иметь в виду если вы хотите использовать этот способ.
Заодно определим TAG и адрес сервера с которого мы будем качать дистрибутив, в нашем случае это будет название офиса в котором находится компьютер:

if ($host_zone -eq '10')
{
      $tag='Office1'
      $server_adr="\\dc-1\NETLOGON\"
}
elseif ($host_zone -eq '20')
{
      $tag=' Office2'
      $server_adr="\\dc-2\NETLOGON\"
}

В данном случае у нас 2 офиса с подсетями 192.168.10.ххх и 192.168.20.ххх, и двумя серверами-контроллерами домена  dc-1 и dc-2 соответственно. Мы используем папку NETLOGON на сервере так как она доступна для чтения с любого доменного компьютера.
Архитектуру определим с помощью объекта Win32_ComputerSystem:

$host_arc=(Get-WmiObject Win32_ComputerSystem).systemtype -split "\-" | select -first 1

Результатом работы будет X86 или X64 в зависимости от архитектуры.  Следующим шагом будет определение папки с программой которая находится в Program Files для конкретной архитектуры.
Для того чтобы исключить ошибки в работе, например если системный раздел имеет метку отличную от С:\ , воспользуемся переменной среды $env:SystemDrive:

if ($host_arc -eq 'X86') {
      $app_dir=$env:SystemDrive, "Program Files\OCS Inventory Agent" -join "\"}
Elseif ($host_arc -eq 'X64') {
      $app_dir=$env:SystemDrive, "Program Files (x86)\OCS Inventory Agent" -join "\"}

Теперь самое интересное – метка. В качестве метки мы должны использовать  какие то статические, редко изменяющиеся параметры, иначе скрипт неверно будет реагировать и часто переустанавливать агента, а это может привести к появлению дубликатов.
Мы будем использовать результат MD5 функции от строки «MAC адрес + Имя хоста». Нам понадобится функция, которая считает хэш строки и возвращает его в строчной форме:

function Get-MD5-str([string]$Content)
{
      $cryptoServiceProvider = [System.Security.Cryptography.MD5CryptoServiceProvider];
      $hashAlgorithm = new-object $cryptoServiceProvider
      $bytes = [System.Text.Encoding]::Default.GetBytes($Content)
      $hashByteArray = $hashAlgorithm.ComputeHash($bytes);
      $formattedHash = [string]::join("",($hashByteArray | foreach {$_.tostring("X2")}))
      return $formattedHash;
}

Описывать подробно работу функции не буду, она взята отсюда , кому интересно зайдите и почитайте.
Теперь нам надо сформировать строку на вход.  Для этого воспользуемся объектами Win32_NetworkAdapterConfiguration и Win32_NetworkAdapterConfiguration:

 $host_name=Get-WmiObject Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE | Select-Object -ExpandProperty DNSHostName
$host_mac=Get-WmiObject Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE | Select-Object -ExpandProperty MACAddress

После этого вызовем функцию Get-MD5-str и передадим ей строку:

$host_id=Get-MD5-str($host_mac, $host_name -join " ")

На выходе мы получаем строку вида: «E3C0856EF80ED630CA5D101228FB300D» в дальнейшем мы будем использовать эту строку как основной идентефикатор компьютера.
Напишем условия для определения правильности установки. Мы будем определять правильность по трем признакам присваивая соответствующий статус. Первое условие  - наличие в папке с программой файла  uninst.exe, второе - наличие в реестре ключа “HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\OCS Inventory Agent
И,ноконец третье условие – наличие в папке с программой файла "VersionControl.txt" и совпадение первой его строчки с ID компьютера.
Для этого напишем функцию Verify-Install:

function Verify-Install
{
     $v_flag=0
     if (Test-Path ($app_dir, "uninst.exe" -join "\")){
          $v_flag=$v_flag + 1}
     if (Test-Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\OCS Inventory Agent'){
          $v_flag=$v_flag + 2}
     if (Test-Path ($app_dir, "VersionControl.txt" -join "\"))
     {
          if ((Get-MD5-str($host_mac, $host_name -join " ")) -eq (Get-Content($app_dir, "VersionControl.txt" -join "\") -TotalCount 1)){
                $v_flag=$v_flag + 10}
     }
     return $v_flag
}

Каждое условие может прибавить или не прибавить свой код в статус:
Статус
Условие uninst.exe
Условие строки реестра
Условие совпадения ID
Дальнейшие действия
0
-
-
-
Установка агента
1
+
-
-
Переустановка агента(удаление потом установка)
2
-
+
-
Установка агента
10
-
-
+
Установка агента
3
+
+
-
Переустановка агента
11
+
-
+
Ничего не делать
12
-
+
+
Установка агента
13
+
+
+
Ничего не делать

В зависимости от статуса скрипт будет выполнять или не выполнять различные действия.
Теперь напишем функцию удаления:

function Delete-App($path_uninstall, $str_uninstall)
{
     $tpid=[diagnostics.process]::start($path_uninstall, $str_uninstall)
     Wait-Event -Timeout 2
     Wait-process -name Au_ -timeout 20
}

И тут есть одна хитрость, как мы помним, файл деинсталляции называется uninst.exe, а процесс который он стартует называется Au_ , поэтому стандартная конструкция ожидания завершения процесса не будет работать.
Так же нам понадобится функция удаления каталогов и файлов:

function Delete-Path($del_path)
{
     if (Test-Path ($del_path)){
          Remove-Item $del_path -Recurse}
}

Функция создания временного каталога:

function Create-TmpF($tmpd)
{
     New-Item -Path $tmpd -ItemType "directory" -Force
}

Функция копирования дистрибутива во временный каталог:

Function Copy-Distr
{
     if (Test-Path ($temp_dir)){
          Delete-Path $temp_dir }
     Create-TmpF ($temp_dir)
     Copy-Item -Path ($server_adr, "OcsAgentSetup.exe" -join "\") -Destination    $temp_dir -Recurse -Force -Passthru
}

И, наконец, функция установки программы:

function install_app
{
     Copy-Distr
     [diagnostics.process]::start($path_install, $str_install).WaitForExit()
     add-content -Path ($app_dir, "VersionControl.txt" -join "\") -Value $host_id -passthru
     add-content -Path ($app_dir, "VersionControl.txt" -join "\") -Value "4061" -passthru
     add-content -Path ($app_dir, "VersionControl.txt" -join "\") -Value "0.1b" -passthru
     if (Test-Path ($temp_dir)){
          Delete-Path ($temp_dir)
}

Ее работу опишем подробно. В начале вызываем функцию Copy-Distr, происходит копирование дистрибутива, далее запускаем установку с ожиданием завершения процесса. После того как все действия выполнены, в папке с программой создаем файл-метку(VersionControl.txt) и записываем в него все необходимую служебную информацию(в нашем случае ID, версию агента и версию установки). В завершение установки чистим за собой – удаляем временный каталог.
Ну и на последок выкладываю скрипт одним файлом, пробуйте, пишите коменты.


Скрипт(Main-Client-0.1-RC1-pub)

вторник, 18 января 2011 г.