Published on

March 30, 2020

Мониторинг служб SQL Server с помощью PowerShell

Как администратор SQL Server, важно знать о любых проблемах с вашими службами SQL Server до того, как они повлияют на ваших пользователей. В этой статье блога мы рассмотрим скрипт, который может помочь вам отслеживать состояние всех служб SQL Server на нескольких экземплярах и получать уведомления о любых службах, которые не работают.

Проблема

Когда службы SQL Server не работают, это может быть признаком сбоя или проблемы, которую необходимо решить. Однако не всегда легко вручную проверять состояние каждой службы на каждом экземпляре, особенно если у вас большое количество экземпляров для управления. Кроме того, если SQL Server Agent перестает работать, вы можете не заметить этого, пока не проверите службу вручную, что приведет к пропущенным запланированным заданиям.

Решение

Для решения этой проблемы мы можем использовать скрипт PowerShell, который проверяет состояние всех служб SQL Server на всех экземплярах и сообщает о любых службах, которые не работают. Скрипт может быть запущен по требованию или запланирован как регулярное задание для проактивного мониторинга служб.

Вот пример скрипта:

$server = "." # Замените на имя вашего сервера
$database = "Inventory" # Замените на имя вашей базы данных
$query = @"
SELECT DISTINCT [Environment], [ServerName]
FROM [Inventory]
WHERE [Cluster] = 0
"@
$query2 = @"
SELECT DISTINCT [Environment], [ClusterName]
FROM [Inventory]
WHERE [Cluster] = 1
"@
$Maxthreads = 20 # Количество параллельных заданий

# Импорт модулей
Import-Module SqlPs -DisableNameChecking
Import-Module FailoverClusters

# Получение списка экземпляров
$objects = @(Invoke-Sqlcmd -ServerInstance $server -Database $database -Query $query)
$clusters = @(Invoke-Sqlcmd -ServerInstance $server -Database $database -Query $query2)

# Получение узлов для кластеров и добавление их в список серверов
foreach ($cluster in $clusters) {
   $services = Get-ClusterGroup -Cluster $cluster.ClusterName
   foreach ($service in $services) {
      if (!($service.Name -eq "Cluster Group") -And !($service.Name -eq "Available Storage")) {
         $found = 0
         for ($i = 0; $i -lt $objects.length; $i++) {
            if ($objects[$i].ServerName.StartsWith($cluster.ClusterName)) {
               $found = 1
               if (!$objects[$i].ServerName.contains($service.OwnerNode.Name)) {
                  $objects[$i].ServerName = $objects[$i].ServerName + " " + $service.OwnerNode.Name
               }
               break
            }
         }
         if ($found -eq 0) {
            $obj = New-Object -TypeName PSObject
            $obj | Add-Member -MemberType NoteProperty -Name Environment -Value $cluster.Environment
            $obj | Add-Member -MemberType NoteProperty -Name ServerName -Value "$($cluster.ClusterName) $($service.OwnerNode.Name)"
            $objects += $obj
         }
      }
   }
}

# Настройка окружения
$output = @()
$errors = ""
$Jobs = @()
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.Open()

# Скрипт для проверки состояния служб
$script = {
   Param ([string]$environment, [string]$serverName)
   $ErrorActionPreference = "Stop"
   try {
      $firstSet = @()
      $final = @()
      if (!$serverName.contains(" ")) { # Один сервер
         $firstSet = Get-Service -computername $serverName | where {$_.DisplayName -like "*SQL*" -and $_.Status -eq "Stopped"} | select @{Name="Environment";Expression={$environment}}, @{Name="ServerName";Expression={$serverName}}, @{Name="Service";Expression={$_.DisplayName}}, Status
      }
      else {
         $parts = $serverName.split(" ")
         if ($parts.count -eq 2) { # Один активный узел в кластере
            $firstSet = Get-Service -computername $parts[1] | where {$_.DisplayName -like "*SQL*" -and $_.Status -eq "Stopped"} | select @{Name="Environment";Expression={$environment}}, @{Name="ServerName";Expression={$parts[1]}}, @{Name="Service";Expression={$_.DisplayName}}, Status
         }
         else { # Более одного активного узла в кластере
            for ($i = 1; $i -lt $parts.count; $i++) {
               $secondSet += Get-Service -computername $parts[$i] | where {$_.DisplayName -like "*SQL*"} | select @{Name="Environment";Expression={$environment}}, @{Name="ServerName";Expression={$parts[$i]}}, @{Name="Service";Expression={$_.DisplayName}}, Status
            }
            $thirdSet = $secondSet | where {$_.Status -ne "Running"}
            for ($i = 0; $i -lt $thirdSet.count; $i++) {
               $running = $secondSet | where {$_.Service -eq $thirdSet[$i].Service -and $_.Status -eq "Running"} | measure
               if ($running.count -eq 0) {
                  $firstSet += $thirdSet[$i];
               }
            }
         }
      }
      $m = $firstSet | measure
      if ($m.count -gt 0) {
         foreach ($row in $firstSet) {
            $props = Get-WmiObject win32_service -computer $row.ServerName -filter "Name='$($row.Service)'"
            if (!$props -or ($props.StartMode -ne "Disabled")) {
               $final += $row | select Environment, ServerName, Service, Status, @{Name="StartMode";Expression={$props.StartMode}}
            }
         }
      }
      "" # Указывает, что ошибок не было
      $final # Возвращаем результаты
   }
   catch {
      return $environment + " " + $serverName + ": " + $LastExitCode + " " + $_ | foreach { $_.ToString() } | Out-String # Ошибка Get-WmiObject, возможно, проблемы с разрешениями
   }
}

# Функция для создания потока
function CreateThread() {
   param ([string]$environment, [string]$serverName, [ref]$Jobs)
   $PowershellThread = [powershell]::Create().AddScript($script) # Скрипт для выполнения
   $PowershellThread.AddArgument($environment) | out-null
   $PowershellThread.AddArgument($serverName) | out-null
   $PowershellThread.RunspacePool = $RunspacePool
   $Handle = $PowershellThread.BeginInvoke()
   $Job = "" | select Handle, Thread, object
   $Job.Handle = $Handle; $Job.Thread = $PowershellThread
   $Jobs.value += $Job
}

$ResultTimer = Get-Date # Время начала

# Начало обработки первой задачи для каждого экземпляра
for ($i=0; $i -lt $objects.length; $i++) {
   CreateThread $objects[$i].Environment $objects[$i].ServerName ([ref]$Jobs)
}

while (@($Jobs | where {$_.Handle -ne $Null}).count -gt 0) {
   # Обновление завершенных заданий, получение ошибок и результатов, и их удаление
   foreach ($Job in @($Jobs | where {$_.Handle -ne $Null -and $_.Handle.IsCompleted -eq $True})) {
      $results = $Job.Thread.EndInvoke($Job.Handle)
      if ($results[0] -and $results[0] -ne "") {
         $errors += $results[0]
      }
      for ($i = 1; $i -lt $results.count; $i++) {
         $output += $results[$i]
      }
      # Завершение потока
      $Job.Thread.Dispose()
      $Job.Thread = $Null
      $Job.Handle = $Null
   }
   # Отображение прогресса
   $inProgress = @($Jobs | where {$_.Handle.IsCompleted -eq $False}).count
   Write-Progress `
      -Activity "Сбор данных" `
      -PercentComplete (($objects.length - $inProgress) * 100 / $objects.length) `
      -Status "$inProgress ожидают"
   # Выход при превышении времени ожидания
   $currentTime = Get-Date
   if (($currentTime - $ResultTimer).totalseconds -gt $MaxResultTime) {
      Write-Error "Скрипт потомка, похоже, замерз, попробуйте увеличить MaxResultTime"
      break
   }
   # Задержка
   Start-Sleep -Milliseconds $SleepTimer
}

# Удаление пулов потоков
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null

$errors # Возвращаем ошибки
$output | Format-Table -Auto # Возвращаем результаты

Перед запуском скрипта вам необходимо изменить переменные, такие как $server, $database, $query, $query2 и $Maxthreads, чтобы они соответствовали вашей среде.

Скрипт работает путем запроса таблицы базы данных с названием “Inventory”, которая содержит информацию о каждом экземпляре SQL Server. Затем он использует модуль FailoverClusters для определения узлов, на которых работают службы. Если есть активно-активный кластер, он проверяет, работают ли службы на другом узле, прежде чем сообщать о них как остановленных.

После выполнения скрипта вы получите список остановленных служб для каждого сервера, а также их состояние и режим запуска.

Вывод

Мониторинг состояния служб SQL Server является важным для поддержания здоровой среды SQL Server. С помощью скрипта PowerShell, предоставленного в этой статье блога, вы можете легко отслеживать состояние всех служб SQL Server на нескольких экземплярах и получать уведомления о любых проблемах до их влияния на ваших пользователей.

Click to rate this post!
[Total: 0 Average: 0]

Let's work together

Send us a message or book free introductory meeting with us using button below.