Имеется несколько сотен клиентских машин на базе ОС 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 "$globalpathdriverAPC-UPSInstallDriver.exe" -Destination "c:tempInstallDriver.exe" -Force -Verbose
Start-process "c:tempInstallDriver.exe" #Ставим драйвер для упсов. У нас везде используются упсы APC, поэтому и exe файл один.
$processid = (Start-process msiexec -args "/quiet /passive /norestart /log c:tempinst_nutlog.txt /I $globalpathnut.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 $globalpathnut_sbin* -Destination $pth3 -Force -Verbose
Copy-Item $globalpathnut_etc* -Destination $pth -Force -Verbose #конфиг
Copy-Item $globalpathnut_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 $globalpathnut_etc* -Destination $pth -Force -Verbose
Copy-Item $globalpathnut_bin* -Destination $pth2 -Force -Verbose
Copy-Item $globalpathnut_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 $globalpathZabbix -Destination "C:Program Files" -Force -Recurse -Verbose
Start-Sleep -Milliseconds 15000
if ($zbxbin -eq "win32") {Copy-Item $globalpathx86Zabbix -Destination "C:Program Files" -Force -Recurse -Verbose}
Start-Sleep -Milliseconds 11000
$zconf = '"C:Program FilesZabbixconfzabbix_agentd.conf"'
Start-Process -FilePath "C:Program FilesZabbixbin$zbxbinzabbix_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 $globalpathZabbix$part -Destination "C:Program FilesZabbix" -Force -Recurse -Verbose
Start-Sleep -Milliseconds 15000
if (($zbxbin -eq "win32") -and (($part -eq "conf") -or ($part -eq "cmd"))) {Copy-Item $globalpathx86Zabbix -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:tempzabbix_inst_debug.txt
$globalpath="\domain.localdfsZabbix_policyzabbixinst" #путь, где лежит все наше добро, не забудьте дать права группе, куда включены компьютеры.
$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)NUTetc"
$pnut2="c:Program Files (x86)NUTbin"
$pnut3="c:Program Files (x86)NUTsbin"
}
else
{
$zbxbin="win32"
$pnut="c:Program FilesNUTetc"
$pnut2="c:Program FilesNUTbin"
$pnut3="c:Program FilesNUTsbin"
}
#Подключение к 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 FilesZabbixbinver" -ErrorAction SilentlyContinue
[int]$zabbix_cmd = Get-Content -Path "C:Program FilesZabbixcmdver" -ErrorAction SilentlyContinue
[int]$zabbix_conf = Get-Content -Path "C:Program FilesZabbixconfver" -ErrorAction SilentlyContinue
[int]$zabbix_extra = Get-Content -Path "C:Program FilesZabbixextraver" -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 вместе с публикацией следующей части.
Автор: mike_Z