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

в 15:26, , рубрики: powershell, python, windows, Серверное администрирование, системное администрирование, системное программирование, метки: , , ,

image

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

Решение:

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

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

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

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

powershell.exe C:SoftwareServicescal01script.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
  ), "rn")


  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: OKnEverything works correctlynLast dump of running services was taken at:n%snThe list of desired services:n%sn' % (serverName,dumpdate,'n'.join(desiredServices))
  else:
    statusText='%s: Errors' % (serverName)
    detailsText='%s: Errorsn%s out of %s services are running.nServices failed to start:%snMissing services:%snLast dump of the running services was taken at:n%sn' % (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='%snn%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:SoftwareServicesdelete.ps1

Poweshell скрипт:

remove-item C:SoftwareServicesServerName.txt

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

Автор: tant123

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js