Мониторинг 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\WebHosting | IIS 8+ с SNI | ls Cert:\LocalMachine\WebHosting |
| Cert:\LocalMachine\Root | Trusted Root CA | ls Cert:\LocalMachine\Root |
| Cert:\LocalMachine\CA | Intermediate | ls 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 руб.
Типичные ошибки
- Скрипт не смотрит WebHosting. IIS 8+ многие сертификаты хранит там — без этого стора половина не видна.
- Invoke-Command без авторизации. Под простой учёткой без WinRM-прав не сработает — нужен доменный аккаунт с admin на целевых.
- TLS 1.0 для внешних проверок.
[Net.ServicePointManager]::SecurityProtocol = 'Tls12'в начале скрипта. - Одиночный email получатель. Отпуск админа — просрочка. Отправляйте в групповой чат/канал.
- Нет фильтра по HasPrivateKey. В сторе куча интермедиатов без ключа — они не актуальны, засоряют отчёт.
Настроим 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.