...

воскресенье, 25 сентября 2016 г.

Мониторим клиентские ПК в Microsoft AD с помощью Zabbix. Часть 1 — Автоустановка

Имеется несколько сотен клиентских машин на базе ОС windows 7 в домене microsoft AD, хочется их мониторить, и помимо обычных cpu, mem, disk и т. п. неплохо было бы получать информацию о состоянии smart дисков, информацию с usb ибп, при этом все должно устанавливаться автоматически, и в случае необходимости обновляться. Про шаблоны, настройки и alert скрипт на сервере я напишу позже, пока займемся подготовкой скрипта на powershell для установки агентов. На текущий момент актуальная версия zabbix — 3.x.

External tools


Вкратце опишу что я использую:
-smartmontools — мониторинг SMART
-Network UPS Tools (NUT) — мониторинг упсов
-libusb driver — для NUT
-awk, grep — для парсинга

К сожалению, NUT+libusb заработал не так прекрасно, как ожидалось, пришлось накидать костылей, но все равно про него напишу, на случай если кому понадобится.

Конфигурация инсталлятора и логирование


Со временем придется обновлять агента, либо его конфигурацию, либо захочется добавить что-то новое, поэтому надо завести табличку(mssql) конфигурации с версиями модулей, которые мы планируем обновлять.

На локальных машинах версии будут храниться в файлах «ver» в соответствующих директориях.

CREATE TABLE [dbo].[zabbix_cfg](
        [zabbix_bin] [int] NULL, -- бинарники агента zabbix
        [zabbix_cmd] [int] NULL, -- grep, awk, дополнительные скрипты и т. п.
        [zabbix_conf] [int] NULL, -- config файлы zabbix_agentd
        [zabbix_extra] [int] NULL, -- дополнительные бинарники, пока только smartctl
        [nut_bin] [int] NULL, -- bin  файлы NUT
        [nut_conf] [int] NULL -- conf файлы NUT
) ON [PRIMARY]

И нужно где-то централизованно хранить результаты установки, на случай, если потребуется диагностика.
CREATE TABLE [dbo].[zabbix_log](
        [machine] [nvarchar](100) NULL,
        [osname] [nvarchar](100) NULL,
        [osarch] [nvarchar](10) NULL,
        [lastrun] [int] NULL,
        [zab_inst_err] [int] NULL,
        [zab_inst_run] [int] NULL,
        [nut_inst_err] [int] NULL,
        [nut_inst_run] [int] NULL,
        [nut_upd_err] [int] NULL,
        [nut_upd_run] [int] NULL,
        [zab_upd_err] [int] NULL,
        [zab_upd_run] [int] NULL
) ON [PRIMARY]

lastrun — дата последнего запуска скрипта
*_run — дата последнего запуска той или иной функции
*_err — соответственно результат запуска

Таблицы созданы, надо выдать доступ на чтение cfg и запись в log группе, в которую будут включены необходимые компьютеры.

Скрипт


# Внимание!!
# Для хранения логов у меня используется абсолютный путь c:\temp, который уже создан на всех машинах другой политикой
# Для использования %TMP%(это обязательно надо учесть в конфигурации zabbix агента, насколько я помню, он переменные не понимает) 
# или создания с:\temp необходимо внести соответсвующие правки. 

# функция обращения к SQL-серверу
function Invoke-Sqlcmd($conn,$Query,[Int32]$QueryTimeout=30)
        {
        $cmd=new-object system.Data.SqlClient.SqlCommand($Query,$conn)
        $cmd.CommandTimeout=$QueryTimeout
        $ds=New-Object system.Data.DataSet
        $da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
        [void]$da.fill($ds)
        $ds.Tables[0]
        }
#--------------------------------------------------------------------------------------------
function report-mail($zv) 
        {
        #E-Mail
        $sender = 'ZabbixInstall@domain.local'
        $recipient = 'admin@domain.local'
        $SMTPserver = 'smtp'
        $subject = "At $HostName PS_version =  $CurrentPS_Version"
        if ($zv -eq 1) {$subject = "p***a #powershell install zabbix deployment aborted"} 
        $body = "$HostName`t$OSName`t$OSVersion`t$OSLang`t$OSArchitecture`t$Date"
        $msg = New-Object System.Net.Mail.MailMessage $sender, $recipient, $subject, $body
        $client = New-Object System.Net.Mail.SmtpClient $SMTPserver
        $client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
        $client.Send($msg)
        }
#--------------------------------------------------------------------------------------------
#функция завершения процесса:
#Kill-Process  "msiexec"
function Kill-Process([string[]]$ProcessNames) 
        {
        if ($ProcessNames -eq $null) 
                {
                Write-Error 'The parametre "ProcessNames" cannot be empty';
                break
                }
        else
                {
                $pr=(Get-Process $ProcessNames -ErrorAction SilentlyContinue)  
                $pr.kill()
                }
        }
#--------------------------------------------------------------------------------------------
#функция Установки NUT:
#NUT-Install  -pth $pnut -pth2 $pnut2 -globalpath $globalpath
function NUT-Install() 
        {
        Param([string]$pth,[string]$pth2,[string]$pth3,[string]$globalpath)  
        $ErrorActionPreference="SilentlyContinue"
        Copy-Item  "$globalpath\driver\APC-UPS\InstallDriver.exe" -Destination "c:\temp\InstallDriver.exe" -Force -Verbose
        Start-process "c:\temp\InstallDriver.exe" #Ставим драйвер для упсов. У нас везде используются упсы APC, поэтому и exe файл один.
        $processid = (Start-process msiexec -args "/quiet /passive /norestart /log c:\temp\inst_nutlog.txt /I $globalpath\nut.msi" -PassThru).id
        do 
                # при silent установке NUT вываливается интерактивный exe, прибиваем. 
                {
                Write-Host "target_pid" $processid
                [array]$msiexecid = Get-Process -Name msiexec | % {$_.id}
                [array]$xer = Get-Process -Name wdi-simple 
                if      ($xer.count -gt 0) 
                        {Kill-Process "wdi-simple"}
                Write-Host "rem_proc" $msiexecid
                Start-Sleep -Milliseconds 1000
                [array]$proccount+=1
                Write-Host "pcount" $proccount.count
                Start-Sleep -Milliseconds 1000
                }                       
                        
        while(($msiexecid -contains $processid)-and($proccount.count -lt "90")) 
        #в разных версиях NUT криво работают разные exe, поэтому берем только лучшее. Файлы позже выложу на git. 
        Copy-Item  $globalpath\nut_sbin\* -Destination $pth3 -Force -Verbose
        Copy-Item  $globalpath\nut_etc\* -Destination $pth -Force -Verbose #конфиг
        Copy-Item  $globalpath\nut_bin\* -Destination $pth2 -Force -Verbose
                Start-Sleep -Milliseconds 15000
        #Запускаем службу и пишем результат в лог. Все логи будем обновлять с помощью MERGE, т. к. километры логов никому не нужны. 
        $nut_inst_err=(Get-WmiObject Win32_Service -Filter "Name='Network UPS Tools'").InvokeMethod("StartService",$null)
        Invoke-Sqlcmd $conn "MERGE [zabbix].[dbo].[zabbix_log]
USING (values('$HostName','$nut_inst_err','$today')) AS foo (machine,nut_inst_err,nut_inst_run)
ON (zabbix.dbo.zabbix_log.machine = foo.machine)
WHEN MATCHED THEN
    UPDATE SET zabbix.dbo.zabbix_log.nut_inst_err = foo.nut_inst_err, zabbix.dbo.zabbix_log.nut_inst_run = foo.nut_inst_run;"
        }
#--------------------------------------------------------------------------------------------
#функция обновления NUT:
function NUT-Update() 
        {
        Param([string]$pth,[string]$pth2,[string]$pth3,[string]$globalpath)  
        (Get-WmiObject Win32_Service -Filter "Name='Network UPS Tools'").InvokeMethod("StopService",$null)
        Start-Sleep -Milliseconds 15000
        Copy-Item  $globalpath\nut_etc\* -Destination $pth -Force -Verbose
        Copy-Item  $globalpath\nut_bin\* -Destination $pth2 -Force -Verbose
        Copy-Item  $globalpath\nut_sbin\* -Destination $pth3 -Force -Verbose
        Start-Sleep -Milliseconds 15000
        $nut_upd_err=(Get-WmiObject Win32_Service -Filter "Name='Network UPS Tools'").InvokeMethod("StartService",$null)
        Invoke-Sqlcmd $conn "MERGE [zabbix].[dbo].[zabbix_log]
USING (values('$HostName','$nut_upd_err','$today')) AS foo (machine,nut_upd_err,nut_upd_run)
ON (zabbix.dbo.zabbix_log.machine = foo.machine)
WHEN MATCHED THEN
    UPDATE SET zabbix.dbo.zabbix_log.nut_upd_err = foo.nut_upd_err, zabbix.dbo.zabbix_log.nut_upd_run = foo.nut_upd_run;"
        }
#--------------------------------------------------------------------------------------------
#функция установки zabbix:
function zabbix-inst($globalpath,$zbxbin)
        {
        Copy-Item $globalpath\Zabbix -Destination "C:\Program Files\" -Force -Recurse -Verbose
        Start-Sleep -Milliseconds 15000
        if ($zbxbin -eq "win32") {Copy-Item $globalpath\x86\Zabbix -Destination "C:\Program Files\" -Force -Recurse -Verbose}
        Start-Sleep -Milliseconds 11000
        $zconf = '"C:\Program Files\Zabbix\conf\zabbix_agentd.conf"'
    Start-Process -FilePath "C:\Program Files\Zabbix\bin\$zbxbin\zabbix_agentd.exe" -args "--config $zconf -i"
        Start-Sleep -Milliseconds 15000
        $zab_inst_err=(Get-WmiObject Win32_Service -Filter "Name='Zabbix Agent'").InvokeMethod("StartService",$null)
        Invoke-Sqlcmd $conn "MERGE [zabbix].[dbo].[zabbix_log]
USING (values('$HostName','$zab_inst_err','$today')) AS foo (machine,zab_inst_err,zab_inst_run)
ON (zabbix.dbo.zabbix_log.machine = foo.machine)
WHEN MATCHED THEN
    UPDATE SET zabbix.dbo.zabbix_log.zab_inst_err = foo.zab_inst_err, zabbix.dbo.zabbix_log.zab_inst_run = foo.zab_inst_run;"
        }

#--------------------------------------------------------------------------------------------
#функция обновления zabbix:
function zabbix-update($globalpath,$zbxbin,$part)
        {
        (Get-WmiObject Win32_Service -Filter "Name='Zabbix Agent'").InvokeMethod("StopService",$null)
        Start-Sleep -Milliseconds 15000
        Copy-Item $globalpath\Zabbix\$part -Destination "C:\Program Files\Zabbix" -Force -Recurse -Verbose
        Start-Sleep -Milliseconds 15000
        if (($zbxbin -eq "win32") -and (($part -eq "conf") -or ($part -eq "cmd"))) {Copy-Item $globalpath\x86\Zabbix -Destination "C:\Program Files\" -Force -Recurse -Verbose}
        Start-Sleep -Milliseconds 10000 
        $zab_upd_err=(Get-WmiObject Win32_Service -Filter "Name='Zabbix Agent'").InvokeMethod("StartService",$null)
        Invoke-Sqlcmd $conn "MERGE [zabbix].[dbo].[zabbix_log]
USING (values('$HostName','$zab_upd_err','$today')) AS foo (machine,zab_upd_err,zab_upd_run)
ON (zabbix.dbo.zabbix_log.machine = foo.machine)
WHEN MATCHED THEN
    UPDATE SET zabbix.dbo.zabbix_log.zab_upd_err = foo.zab_upd_err, zabbix.dbo.zabbix_log.zab_upd_run = foo.zab_upd_run;"
        }

## START
Start-Transcript -path c:\temp\zabbix_inst_debug.txt

$globalpath="\\domain.local\dfs\Zabbix_policy\zabbixinst" #путь, где лежит все наше добро, не забудьте дать права группе, куда включены компьютеры. 
$today=  get-date -Format "yyyyMMdd"
#
#Arch
$OSArchitecture = (Get-WmiObject -Class Win32_OperatingSystem -Namespace root/cimv2).OSArchitecture
if ($OSArchitecture -match "64-bit")
        {
        $zbxbin="win64"
        $pnut="c:\Program Files (x86)\NUT\etc\"
        $pnut2="c:\Program Files (x86)\NUT\bin\"
        $pnut3="c:\Program Files (x86)\NUT\sbin\"
        }
else 
        {
        $zbxbin="win32"
        $pnut="c:\Program Files\NUT\etc\"
        $pnut2="c:\Program Files\NUT\bin\"
        $pnut3="c:\Program Files\NUT\sbin\"
        }

#Подключение к SQL
$conn=new-object System.Data.SqlClient.SQLConnection
$conn.ConnectionString="Server={0};Database={1};Integrated Security=True" -f "server","Zabbix" 
$conn.Open()



# определение имени хоста на котором запустился скрипт.
$HostName = $env:COMPUTERNAME;

# сбор сведений об ОС
$OperatingSystem = Get-WmiObject Win32_OperatingSystem;
# определение прочих параметров ОС
$OSName = $OperatingSystem.caption; # имя ОС
$OSVersion = $OperatingSystem.version; # версия ОС
$OSLang = $OperatingSystem.oslanguage; # язык ОС
# MinimalPS_Major_Version
$MinimalPS_Major_Version = 2
# Определение версии PowerShell
$CurrentPS_Version = $host.version.major

# Если версиия PS подходящая, выполняем скрипт дальше
If ($CurrentPS_Version -ge $MinimalPS_Major_Version)
{
#main

#log
Invoke-Sqlcmd $conn "MERGE [zabbix].[dbo].[zabbix_log]
USING (values('$HostName','$OSName','$zbxbin','$today')) AS foo (machine,osname,osarch,lastrun)
ON (zabbix.dbo.zabbix_log.machine = foo.machine)
WHEN MATCHED THEN
    UPDATE SET zabbix.dbo.zabbix_log.osname = foo.osname, zabbix.dbo.zabbix_log.osarch = foo.osarch, zabbix.dbo.zabbix_log.lastrun = foo.lastrun
WHEN NOT MATCHED BY TARGET THEN
    INSERT (machine,osname,osarch,lastrun)
    values (machine,osname,osarch,lastrun);"
#       

$Query=         
"SELECT * FROM  [zabbix].[dbo].[zabbix_cfg]"    

$result= Invoke-Sqlcmd $conn $Query
[array]$testresult= $result
if ($testresult.count -ne 1 ) { report-mail 1}

#получаем локальные версии
[int]$zabbix_bin = Get-Content -Path "C:\Program Files\Zabbix\bin\ver" -ErrorAction SilentlyContinue
[int]$zabbix_cmd = Get-Content -Path "C:\Program Files\Zabbix\cmd\ver" -ErrorAction SilentlyContinue
[int]$zabbix_conf = Get-Content -Path "C:\Program Files\Zabbix\conf\ver" -ErrorAction SilentlyContinue
[int]$zabbix_extra = Get-Content -Path "C:\Program Files\Zabbix\extra\ver" -ErrorAction SilentlyContinue
[int]$nut_bin = Get-Content -Path $pnut2"ver" -ErrorAction SilentlyContinue
[int]$nut_conf = Get-Content -Path $pnut"ver" -ErrorAction SilentlyContinue

#сверяем версии 
if (($zabbix_bin -lt 1) -and  ($zabbix_cmd -lt 1)  -and ($zabbix_conf -lt 1)  -and ($zabbix_extra -lt 1) -and ($nut_bin -lt 1) -and  ($nut_conf -lt 1) ) 
        {
        NUT-Install  -pth $pnut -pth2 $pnut2 -pth3 $pnut3 -globalpath $globalpath
        zabbix-inst $globalpath $zbxbin
        }
else
        {
        if ($zabbix_bin -lt $result.zabbix_bin) {zabbix-update $globalpath $zbxbin "bin"}
        if ($zabbix_cmd -lt $result.zabbix_cmd) {zabbix-update $globalpath $zbxbin "cmd"}
        if ($zabbix_conf -lt $result.zabbix_conf) {zabbix-update $globalpath $zbxbin "conf"}
        if ($zabbix_extra -lt $result.zabbix_extra) {zabbix-update $globalpath $zbxbin "extra"}
        if ($nut_bin -lt $result.nut_bin) {NUT-Install  -pth $pnut -pth2 $pnut2 -pth3 $pnut3 -globalpath $globalpath}
        elseif ($nut_conf -lt $result.nut_conf) {NUT-Update  -pth $pnut -pth2 $pnut2 -pth3 $pnut3 -globalpath $globalpath}
        }

}
else {
report-mail
}
#end main

#close connect  
$conn.Close()
Stop-Transcript

Постскриптум


У меня скрипт размещен в dfs и запускается заданием, которое создается с помощью групповой политики.

Все используемые файлы будут выложены на git вместе с публикацией следующей части.

Комментарии (0)

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий