PowerShell · 10.04.2026

Падения и их причины

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

Вот только двигаемся в сторону системных логов мы далеко не из праздного любопытства. Как правило, то, что хорошо работает — особого внимания к себе и не требует. Ибо, как гласит самая главная мудрость:

Работает — не трогай!

Итак, сегодняшний пост не просто о вынужденном централизованном сборе событий выключения удалённого хоста. Этот пост больше про отчётность, которая частенько пригождается.

Мне требовалось написать скрипт, который бы проводил сбор нужных диагностических материалов с удалённого хоста и все собранные данные монтировал в html документ.

Для разработки ps1-скрипта я взял за основу 8 основных событий выключения компьютера. Велика вероятность, что какие-то события я совершенно незаслуженно обделил своим вниманием. Это достаточно легко откорректировать, но об этом чуть ниже.

Список событий, на которые я обратил внимание при написании кода:

  1. Событие 41 (Kernel-Power):
    • Это событие указывает на то, что система была выключена или перезагружена, не завершив работу должным образом. Это может происходить из-за сбоя питания, сбоя оборудования или преднамеренной перезагрузки без должного завершения работы. Событие 41 важно для диагностики проблем, связанных с блокировкой питания или некорректной работой системы.
  2. Событие 6008 (EventLog):
    • Это предупреждение регистрируется в случае, если система была неожиданно выключена. Оно может указывать на неожиданные выключения питания или на сбои системы. Это событие может помочь определить время и причины внезапного завершения работы.
  3. Событие 1074 (User32):
    • Это событие фиксирует обычное завершение работы или перезагрузку системы, инициированное приложением или самим пользователем. В событии содержится информация о том, кто инициировал завершение работы, а также причина и код завершения.
  4. Событие 6006 (EventLog):
    • Это событие указывает на то, что служба журналирования событий была нормальным образом остановлена. Обычно оно фиксируется перед завершением работы системы и может использоваться для анализа завершения работы системы.
  5. Событие 6005 (EventLog):
    • Это событие означает, что служба журналирования событий была нормально запущена. Оно фиксируется при запуске операционной системы и является полезным индикатором для определения времени и состояния системы при загрузке.
  6. Событие 6001 (EventLog):
    • Это событие может указывать на проблемы с журналом событий или на ошибочное состояние. Оно обычно фиксируется, когда система не может записывать события в журнал. Это событие может помочь при диагностике проблем с журналированием.
  7. Событие 6002 (EventLog):
    • Это также событие, связанное с работой службы журналирования событий. Оно указывает на то, что служба была успешно запущена, и журнал событий снова доступен для записи новых данных.
  8. Событие 63 (EventLog):
    • Событие 63 указывает на запись в журнале событий из-за проблем в работе системы. Это событие может быть связано с пользовательскими или системными действиями, о которых необходимо проинформировать администраторов.

Эти события я включил в массив $events. Массив можно с лёгкостью расширить или же наоборот оставить там только необходимые коды событий. Зависит от Ваших целей.

Код скрипта:

# Заголовок скрипта #
[System.Console]::Title = "Выгрузка событий выключения удалённого компьютера"

# Ввод имени хоста пользователем #
$remotehostname = Read-Host "Введите имя удалённого компьютера"

# Функция вывода сообщения об ошибке #
function ErrorMsg($msgstring)
{
    Write-Host
    Write-Host -ForegroundColor Red $msgstring
    Start-Sleep(10)
    Exit
}

# Обработка пустого значения параметра #
if ([string]::IsNullOrWhiteSpace($remotehostname)) 
{
    ErrorMsg("Для работы скрипта нужно указать имя удалённого компьютера!")
}

$ping = Test-Connection -ComputerName $remotehostname -Count 1 -Quiet
if ($ping)
{
    $events = @("41", "6008", "1074", "6006", "6005", "6001", "6002", "63")
    $endDate = Get-Date
    $startDate = $endDate.AddDays(-30)
    $outputFile = "$PSScriptRoot\$remotehostname.html"
    $results = @()
    $htmltitle="События выключения для $remotehostname"

    try
    {
        Write-Host -ForegroundColor Yellow "Начат сбор данных с $remotehostname за последние 30 дней..."

        foreach ($event in $events) 
        {
            Write-Host -ForegroundColor Yellow "Поиск события с кодом: $event"
            try 
            {
                # Получение результатов событий #
                $eventResults = Get-WinEvent -ComputerName $remotehostname -FilterHashtable @{logname='System'; id=$event; StartTime=$startDate; EndTime=$endDate} -ErrorAction Stop |
                    Select-Object TimeCreated, Id, Message
                
                # Проверка, получены ли результаты #
                if ($eventResults) 
                { $results += $eventResults }
                else 
                { Write-Host -ForegroundColor Yellow "Нет событий с кодом: $event в указанный период." }
            } 
            catch 
            {
                Write-Host -ForegroundColor Red "Ошибка при получении событий для кода: $event. $($_.Exception.Message)"
            }
        }

        # Проверка, есть ли результаты перед сортировкой и экспортом #
        if ($results.Count -gt 0) 
        {
            # Удаление переносов строк из поля Message #
            $results = $results | ForEach-Object {
                $_.Message = $_.Message -replace "`r`n", ' '
                $_.Message = $_.Message -replace "`n", ' '
                $_.Message = $_.Message -replace "`r", ' '
                $_ }

            # Сортировка результатов по времени создания #
            $sortedResults = $results | Sort-Object TimeCreated

            # Создание HTML
            $html = @"
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>$htmltitle</title>
    <style>
        body { font-family: 'Arial', 'Helvetica', sans-serif; font-size: 15px; margin: 20px; }
        h1 { color: #2c3e50; text-align: center; }
        h2 { color: #2980b9; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; }
        th, td { padding: 10px; border: 1px solid #ccc; text-align: center; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>$htmltitle</h1>
    <table>
        <tr>
            <th>Время создания</th>
            <th>Идентификатор события</th>
            <th>Сообщение</th>
        </tr>
"@

            foreach ($result in $sortedResults) {
                $html += "<tr><td>$($result.TimeCreated)</td><td>$($result.Id)</td><td>$($result.Message)</td></tr>"
            }

            $html += @"
        </table>
    </body>
</html>
"@
            Set-Content -Path $outputFile -Value $html -Encoding UTF8
            Write-Host -ForegroundColor Green "Результаты успешно экспортированы в $outputFile."
            Start-Sleep(5)
        }
        else 
        {
            Write-Host -ForegroundColor Yellow "Нет доступных результатов для экспорта."
            Start-Sleep(5)
        }
    }
    catch
    {
        ErrorMsg("Во время сбора данных с удалённого компьютера $remotehostname что-то пошло не так...")
    }
}
else
{
    ErrorMsg("Удалённый компьютер $remotehostname не доступен!")
}

Сразу после запуска скрипт интересуется именем компьютера, с которого нужно собрать диагностические данные. Если компьютер не доступен в сети будет выведена соответствующая ошибка.

После получения имени компьютера скрипт начнёт сбор данных и создание html-отчёта. Все данные будут упорядочены по возрастанию и свёрстаны в таблицу.

Сбор данных о событиях будет осуществляться только за последние 30 дней. Если нужно изменить диапазон — отредактируйте переменную $startDate:

$startDate = $endDate.AddDays(-30)

Здесь нужно заменить число 30 на необходимое количество дней, которые скрипт отнимет от текущей даты, сформируя нужный диапазон.