Мониторинг сервисов Windows средствами PowerShell и Python

от автора

image

Предыстория:
Сам я работаю в техотделе одной брокерской компании в Торонто, Канаде. Так же у нас есть еще один офис в Калгари. Как-то после планового установления Windows обновлений на единственном доменном контроллере в удаленном офисе не запустился W32Time сервис, который отвечает за синхронизацию времени с внешним источником. Таким образом в течение около недели время на сервере сбилось приблизительно на 20 секунд. Наши рабочие станции на тот момент времени по умолчанию получали время с контроллера. Сами понимаете, что случилось. В торгах время очень важно, разница в секунды может решить многое. Первыми расхождение во времени, к сожалению, заметили наши брокеры. Наш отдел техподдержки, состоящий по сути из 3 человек за это распекли. Надо было срочно что-то делать. Решением было применение групповой политики, которая отсылала все машины к внутреннему NTP серверу, работающему на CentOS. Еще были проблемы с DC Barracuda Agent, сервисом, отвечающим за соединение контроллеров домена с нашим Веб фильтром, и еще парочка сервисов причиняла нам порой беспокойство. Тем не менее решили что-то придумать, чтобы следить за пару сервисами. Я немного погуглил и понял, что есть много решений, в основном коммерчиских для данной проблемы, но так как я хотел научиться какому-нибудь скриптовому языку, то вызвался написать скрипт на Питоне с помощью нашего местного линукс-гуру. В последствие это переросло в скрипт, который проверяет все сервисы, сравнивая их наличие и состояние со списком желаемых сервисов, которые к сожалению надо делать вручную отдельно для каждой машины.

Решение:

На одном из Windows серверов я создал PowerShell скрипт такого вида:

echo "Servername" > C:\Software\Services\Servername.txt get-date >> C:\Software\Services\Servername.txt Get-Service -ComputerName Servername | Format-Table -Property status, name >> C:\Software\Services\Servername.txt 

В моем случае таких кусков получилось 10 для каждого сервера

В Task Scheduler добавил следующий батник (мне это показлось легче, чем пытаться запусить оттуда PowerShell скрипт напрямую):

powershell.exe C:\Software\Services\cal01script.ps1 

Теперь каждый день я получал список со всеми сервисами в отдельном файле для каждого сервера в подобном формате:

Servername  Friday, October 26, 2012 1:24:03 PM                                   Status Name                                                                     ------ ----                                                                    Stopped Acronis VSS Provider                                                    Running AcronisAgent                                                            Running AcronisFS                                                               Running AcronisPXE                                                              Running AcrSch2Svc                                                              Running ADWS                                                                    Running AeLookupSvc                                                             Stopped ALG                                                                     Stopped AppIDSvc                                                                Running Appinfo                                                                 Running AppMgmt                                                                 Stopped aspnet_state                                                            Stopped AudioEndpointBuilder                                                    Stopped AudioSrv                                                                Running Barracuda DC Agent                                                      Running BFE                                                                     Stopped BITS                                                                    Stopped Browser                                                                 Running CertPropSvc                                                                   Running WinRM                                                                   Stopped wmiApSrv                                                                Stopped WPDBusEnum                                                              Running wuauserv                                                                Stopped wudfsvc     

Теперь самая главная часть. На отдельной машине с CentOS на борту я написал сей скрипт:

import sys import smtplib import string from sys import argv import os, time import optparse import glob  # function message that defines the email we get about the status def message(subjectMessage,msg):   SUBJECT = subjectMessage   FROM = "address@domain.com"   TO = 'address@domain.com'   BODY =  string.join((   "From: %s" % FROM,   "To: %s" % TO,   "Subject: %s" % SUBJECT ,   "",   msg   ), "\r\n")     s = smtplib.SMTP('mail.domain.com')   #s.set_debuglevel(True)   s.sendmail(FROM, TO, BODY)   s.quit()   sys.exit(0)  def processing(runningServicesFileName,desiredServicesFileName):    try:     desiredServicesFile=open(desiredServicesFileName,'r')   except (IOError,NameError,TypeError):     print "The list with the desired state of services either does not exist or the name has been typed incorrectly. Please check it again."     sys.exit(0)    try:     runningServicesFile=open(runningServicesFileName,'r')   except (IOError,NameError,TypeError):     print "The dump with services either does not exist or the name has been typed incorrectly. Please check it again."     sys.exit(0)   #Defining variables   readtxt = desiredServicesFile.readlines()   desiredServices = []   nLines = 0   nRunning = 0   nDesiredServices = len(readtxt)   faultyServices = []   missingServices = []   currentServices = []   serverName = ''   dumpdate=''   errorCount=0  # Trimming file in order to get a list of desired services. Just readlines did not work putting \n in the end of each line   for line in readtxt:     line = line.rstrip()     desiredServices.append(line)     # Finding the number of currently running services and those that failed to start   for line in runningServicesFile:     nLines+=1   # 1 is the line where I append the name of each server     if nLines==1:       serverName = line.rstrip()   # 3 is the line in the dump that contains date     if nLines==3:       dumpdate=line.rstrip()   # 7 is the first line that contains valueable date. It is just the way we get these dumps from Microsoft servers.     if nLines<7:       continue   # The last line in these dumps seems to have a blank character that we have to ignore while iterating.     if len(line)<3:       break     line = line.rstrip();     serviceStatusPair = line.split(None,1)     currentServices.append(serviceStatusPair[1])     if serviceStatusPair[1] in desiredServices and serviceStatusPair[0] == 'Running':       nRunning+=1     if serviceStatusPair[1] in desiredServices and serviceStatusPair[0] != 'Running':       faultyServices.append(serviceStatusPair[1])    if nLines==0:     statusText='Dumps are empty on %s' % (serverName)     detailsText='Dumps are empty'    # Checking if there are any missing services   for i in range(nDesiredServices):     if desiredServices[i] not in currentServices:        missingServices.append(desiredServices[i])   # Sending the email with results   if nRunning == nDesiredServices:     statusText='%s: OK' % (serverName)     detailsText='%s: OK\nEverything works correctly\nLast dump of running services was taken at:\n%s\nThe list of desired services:\n%s\n' % (serverName,dumpdate,'\n'.join(desiredServices))   else:     statusText='%s: Errors' % (serverName)     detailsText='%s: Errors\n%s out of %s services are running.\nServices failed to start:%s\nMissing services:%s\nLast dump of the running services was taken at:\n%s\n' % (serverName,nRunning,nDesiredServices,faultyServices,missingServices,dumpdate)     errorCount=errorCount+1   return (statusText,detailsText,errorCount) # Defining switches that can be passed to the script usage = "type -h or --help for help" parser = optparse.OptionParser(usage,add_help_option=False) parser.add_option("-h","--help",action="store_true", dest="help",default=False, help="this is help") parser.add_option("-d","--desired",action="store", dest="desiredServicesFileName", help="list of desired services") parser.add_option("-r","--running",action="store", dest="runningServicesFileName", help="dump of currently running services") parser.add_option("-c","--config",action="store", dest="configServicesDirectoryName", help="directory with desired services lists") (opts, args) = parser.parse_args() # Outputting a help message and exiting in case -h switch was passed if opts.help:   print """   This script checks all services on selected Windows machines and sends out a report.    checkServices.py [argument 1] [/argument 2] [/argument 3]    Arguments:      Description:    -c, --config - specifies the location of the directory with desired list of services and finds dumps automatically    -d, --desired - specifies the location of the file with the desired list of services.    -r, --running - specifies the location of the file with a dump of running services.   """   sys.exit(0)  statusMessage = [] detailsMessage = [] body = [] errorCheck=0 directory='%s/*' % opts.configServicesDirectoryName  if opts.configServicesDirectoryName:   check=glob.glob(directory)   check.sort()   if len(check)==0:     message('Server status check:Error','The directory has not been found. Please check its location and spelling.')     sys.exit(0)   for i in check:     desiredServicesFileName=i     runningServicesFileName=i.replace('desiredServices', 'runningServices')     #print runningServicesFileName     status,details,errors=processing(runningServicesFileName,desiredServicesFileName)     errorCheck=errorCheck+errors     statusMessage.append(status)     detailsMessage.append(details)   body='%s\n\n%s' % ('\n'.join(statusMessage),'\n'.join(detailsMessage))    if errorCheck==0:     message('Server status check:OK',body)   else:     message('Server status check:Errors',body)   if opts.desiredServicesFileName or opts.desiredServicesFileName:   status,details,errors=processing(opts.runningServicesFileName,opts.desiredServicesFileName)   message(status,details) 

Файлы дампов и списков с желаемыми сервисами должны иметь одинаковые имена. Список с сервисами, за которыми мы следим (desiredServices) должен быть вот такого вида:

Acronis VSS Provider AcronisAgent AcronisFS     AcrSch2Svc       

Скрипт будет проверять сервисы, а потом компоновать все это в одно email сообщение, которое в зависимости от результата будет говорить, что все в порядке в теме сообщения или, что есть ошибки, а в теле сообщения раскрывать, какие это ошибки. Для нас одной проверки в день достаточно, поэтому ранним утром мы получаем уведомление о состоянии наших Windows серверов. Чтобы скопировать файлы с Windows сервера на машину с линуксом, мой коллега помог мне со следующим баш скриптом:

#!/bin/bash  mkdir runningServices smbclient --user="user%password" "//ServerName.domain.com/software" -c "lcd runningServices; prompt; cd services; mget *.txt"  cd runningServices for X in `ls *.txt`; do   iconv -f utf16 -t ascii $X > $X.asc   mv $X.asc $X done 

Этот скрипт так же меняет кодировку, ибо на моей машине Linux не очень хотел работать с UTF16. Далее, чтобы отчищать папку от дампов с сервисами я добавил батник в Task Scheduler чтобы запускать PowerShell скрипт, который стирает дампы.
Батник:

powershell.exe C:\Software\Services\delete.ps1 

Poweshell скрипт:

remove-item C:\Software\Services\ServerName.txt 

Проект преследовал собой 2 цели — мониторинг сервисов и обучение Питону. Это мой первый пост на Хабре, поэтому я уже ожидаю наплыв критики в свой адрес. Если у Вас есть какие-либо замечания, особенно по улучшению данной системы, то милости прошу, поделитесь. Надеюсь, что это статья покажется кому-нибудь нужной, потому что подобного решения бесплатного и с уведомлением по email я не нашел. Может, что плохо искал.

ссылка на оригинал статьи http://habrahabr.ru/post/156597/