· 15 мин чтения

Мониторинг SSL-сертификатов на PowerShell: скрипт инвентаризации и алертов

Меня зовут Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15 лет администрирования AD-инфраструктур я видел все варианты катастроф с SSL в Windows: от забытого сертификата на RDP Gateway до просроченного wildcard на Exchange, из-за чего полофиса не могло отправить почту. Чтобы такого не было, я всегда настраиваю ежедневный обход всех серверов PowerShell-скриптом. Покажу рабочий инструмент, который собирает данные, считает дни и шлёт алерты в Teams или Telegram.

Где живут сертификаты в Windows

LocationНазначениеКак смотреть
Cert:\LocalMachine\MyЛичные сертификаты компьютера (IIS, RDP, WinRM)ls Cert:\LocalMachine\My
Cert:\LocalMachine\WebHostingIIS 8+ с SNIls Cert:\LocalMachine\WebHosting
Cert:\LocalMachine\RootTrusted Root CAls Cert:\LocalMachine\Root
Cert:\LocalMachine\CAIntermediatels Cert:\LocalMachine\CA
Cert:\CurrentUser\MyПользовательскийls Cert:\CurrentUser\My

Скрипт инвентаризации локального хоста

# Get-LocalCertInventory.ps1
Get-ChildItem Cert:\LocalMachine\My, Cert:\LocalMachine\WebHosting |
  Where-Object { $_.NotAfter -gt (Get-Date).AddDays(-30) } |
  Select-Object `
    @{n='Host';      e={ $env:COMPUTERNAME }},
    @{n='Store';     e={ $_.PSParentPath -replace '.*::','' }},
    @{n='Subject';   e={ $_.Subject }},
    @{n='Issuer';    e={ $_.Issuer }},
    @{n='NotAfter';  e={ $_.NotAfter }},
    @{n='DaysLeft';  e={ [int]($_.NotAfter - (Get-Date)).TotalDays }},
    @{n='Thumbprint';e={ $_.Thumbprint }}

Запуск на одной машине даёт список всех сертификатов локально. Дальше надо это собрать со всего парка серверов.

Сбор по всему домену

$servers = (Get-ADComputer -Filter 'OperatingSystem -like "*Server*"' -SearchBase "OU=Servers,DC=corp,DC=local").Name

$all = Invoke-Command -ComputerName $servers -ScriptBlock {
  Get-ChildItem Cert:\LocalMachine\My, Cert:\LocalMachine\WebHosting |
    Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) } |
    Select-Object `
      @{n='Host';e={$env:COMPUTERNAME}},
      Subject, Issuer, NotAfter, Thumbprint,
      @{n='DaysLeft';e={[int]($_.NotAfter-(Get-Date)).TotalDays}}
}

$all | Export-Csv -Path "\\fileserver\IT\ssl-inventory-$(Get-Date -f yyyyMMdd).csv" -NoTypeInformation
$all | Sort-Object DaysLeft | Format-Table

Проверка внешних сертификатов

Внутренние — полдела. Обязательно мониторим внешние: корпоративный сайт, почта, VPN, CRM. Классическая функция через TcpClient:

function Test-Certificate {
    param([string]$Uri, [int]$Port = 443)

    $tcp  = [System.Net.Sockets.TcpClient]::new($Uri, $Port)
    $ssl  = [System.Net.Security.SslStream]::new($tcp.GetStream(), $false,
             { param($s,$c,$ch,$e) return $true })
    $ssl.AuthenticateAsClient($Uri)
    $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($ssl.RemoteCertificate)

    [PSCustomObject]@{
        Uri       = $Uri
        Subject   = $cert.Subject
        Issuer    = $cert.Issuer
        NotAfter  = $cert.NotAfter
        DaysLeft  = [int]($cert.NotAfter - (Get-Date)).TotalDays
    }
    $ssl.Dispose(); $tcp.Dispose()
}

$domains = 'itfresh.ru','mail.itfresh.ru','portal.corp.itfresh.ru'
$domains | ForEach-Object { Test-Certificate -Uri $_ } | Format-Table

Алерты в Microsoft Teams

$webhook = 'https://outlook.office.com/webhook/xxx/.../yyy'

$expiring = $all | Where-Object { $_.DaysLeft -lt 21 }
if ($expiring) {
    $text = "SSL certificates expiring soon:\n\n" +
            ($expiring | ForEach-Object { "- $($_.Host): $($_.Subject) -> $($_.DaysLeft) дн." } | Out-String)
    $body = @{ '@type'='MessageCard'; 'title'='SSL Expiry Alert'; 'text'=$text } | ConvertTo-Json -Depth 5
    Invoke-RestMethod -Uri $webhook -Method Post -Body $body -ContentType 'application/json'
}

Алерты в Telegram

$token = '123456:AAAA...'
$chat  = '-1001234567890'
$text  = "SSL истекает:`n" + ($expiring | ForEach-Object { "$($_.Host) — $($_.DaysLeft) дн." } | Out-String)
Invoke-RestMethod -Uri "https://api.telegram.org/bot$token/sendMessage" `
  -Method Post -Body @{ chat_id = $chat; text = $text }

Запуск по расписанию

Я всегда оформляю это как Scheduled Task под сервисной учётной записью с правами на удалённый PowerShell. Запуск ежедневно в 8:30 — чтобы админ увидел алерт до 9 утра за кофе.

$action  = New-ScheduledTaskAction -Execute 'pwsh.exe' `
  -Argument '-NoProfile -File C:\Scripts\Monitor-SSL.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 8:30am
Register-ScheduledTask -TaskName 'SSL Monitor' -Action $action -Trigger $trigger `
  -User 'CORP\svc_ssl' -RunLevel Highest -Password $SecurePwd

Дашборд в Grafana

Для больших инфраструктур удобно выгружать результаты в InfluxDB или Prometheus Pushgateway и строить дашборд в Grafana. Одна строка на домен, цвет — по оставшимся дням: зелёный >30, жёлтый 10–30, красный <10.

foreach ($c in $all) {
  $line = "ssl_cert,host=$($c.Host),subject=""$($c.Subject)"" days_left=$($c.DaysLeft) $(Get-Date -UFormat %s)000000000"
  Invoke-RestMethod -Method Post `
    -Uri 'http://influx:8086/api/v2/write?org=itfresh&bucket=ssl' `
    -Headers @{ Authorization = 'Token xxx' } `
    -Body $line
}

Мини-кейс: розничная сеть, 17 Windows-серверов

Осенью 2025 года к нам пришла розничная сеть из 9 магазинов — 17 Windows-серверов, 24 домена внутри и снаружи, 3 RDP Gateway. За полгода до этого у них «прозевали» сертификат на Exchange — 2 часа не работала почта, потеряли заказ на 180 000 руб. За 1 рабочий день мы внедрили описанный скрипт: ежедневный обход, CSV-инвентарь на файл-сервер, алерты в корпоративный Teams за 30/14/7/3 дня до истечения, Grafana-дашборд для IT-директора. Скрипт крутится на Dell Xeon Platinum 8280 в дата-центре МТС. За 6 месяцев — ноль просрочек, три автоматически обновлённых сертификата через AD CS, два своевременно заменённых Let's Encrypt. Стоимость внедрения — 22 000 руб.

Типичные ошибки

Настроим SSL-мониторинг на PowerShell под ваш Windows-парк

Готовый скрипт, дашборд, алерты в Teams/Telegram, интеграция с AD CS. От 20 000 руб. за инфраструктуру до 30 серверов.

Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш

FAQ — частые вопросы о мониторинге SSL через PowerShell

Какой стор PowerShell читает для SSL-сертификатов?
Cert:\LocalMachine\My для компьютерного, Cert:\LocalMachine\WebHosting для IIS 8+, Cert:\CurrentUser\My — для пользователя.
Как проверить внешний сертификат по HTTPS?
Через класс System.Net.Sockets.TcpClient и System.Net.Security.SslStream.
Работает ли скрипт на Windows Server 2012 R2?
Да, но требуется PowerShell 5.1 (WMF 5.1).
Как отправить алерт в Microsoft Teams?
Через Incoming Webhook в канале Teams и Invoke-RestMethod с JSON-телом MessageCard.
Можно ли автоматически продлить сертификат Win-CA через PowerShell?
Да, Get-Certificate -Template WebServer -SubjectName CN=server.corp.ru -CertStoreLocation Cert:\LocalMachine\My.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.