PowerShell · 15.11.2022

Ваш сертификат всё…

Этот пост будет особо актуален, если в Ваших краях пользователи авторизуются по сертификатам. Например, с помощью смарт-карт.

В какой-то момент стало надоедать, что все планы на очередной рабочий день разбиваются в хлам о внезапно истекшие сертификаты пользователей, которые не могут попасть на терминальную ферму. Хотя упорно стараются.

Операционная система предупреждает пользователя об истёкшем сертификате на смарт-карте уже по факту — день в день. И, судя по всему, изменить данную политику оповещения нельзя.

Решил посмотреть в сторону PowerShell и попробовать опросить Active Directory на наличие сертификатов и отсортировать их по дате истечения (NotAfter), чтобы организовать хоть какую-то рассылку.

Изначально получился вот такой скрипт. Он собирает данные из Active Directory о сертификатах пользователей, которые истекают в ближайшие 14 дней, формирует из них приятную табличку и отправляет письмом куда прикажут.

## Установка периода ##
$date = Get-Date
$days = 14

## Оформление вывода в HTML ##
$header = @"
<style>
  TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
  TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #87cefa;} 
  TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@
$precontent = "В течение $days дней истекут сертификаты на смарт-картах следующих пользователей:<br><br>"
$postcontent = "<br><br>По поводу продления смарт-карты обращайтесь в отдел АСУ и ИБ.<br><br>Эта информация не касается пользователей, которые входят по паролю!<br><br>"

## Получение данных о сертификатах ##
$users=(Get-ADUser -Filter {Enabled -eq $true} -Properties 'Certificates' | Where Certificates).Certificates |`
 ForEach {[System.Security.Cryptography.X509Certificates.X509Certificate2]$_} |`
 Where {$_.NotAfter -le ($date).AddDays($days) -AND $_.NotAfter -gt ($date)} |`
 select @{Label="ФИО"; Expression={$_.DnsNameList}}, @{Label="Сертификат истекает"; Expression={$_.NotAfter}} |`
 sort "ФИО" | ConvertTo-HTML -Head $header -PreContent $precontent -PostContent $postcontent

## Настройки отправки ##
$smtpServer = "192.168.0.1" # Указать IP почтового сервера
$smtpFrom = "admin@mailserver.ru" # Адрес, с которого будет отправляться письмо
$smtpTo = "mainadmin@mailserver.ru, user@mailserver.ru" # Адреса на которые будет отправляться письмо
$messageSubject = "Истекающие сертификаты пользователей"
$message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
$message.Subject = $messageSubject
$message.IsBodyHTML = $true
$message.Body = $users
$smtp = New-Object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($message)

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

Кроме того, хотелось бы сделать более короткий период отбора при формировании скрипта. Думаю, семи дней будет вполне достаточно.

В итоговом варианте, администрирование списка адресатов было реализовано с помощью группы безопасности «Рассылка сроков истечения сертификатов смарт-карт«. Скрипт получает список членов группы и проводит рассылку в цикле.

## Установка дней ##
$date = Get-Date
$days = 7

## Импорт модуля Active Directory ##
Import-module activedirectory

## Оформление вывода в HTML ##
$header = @"
<style>
  TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
  TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #87cefa;} 
  TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@
$precontent = "В течение $days дней истекут сертификаты на смарт-картах следующих пользователей:<br><br>"
$postcontent = "<br><br>По поводу продления смарт-карты обращайтесь в отдел АСУ и ИБ.<br><br>Эта информация не касается пользователей, которые проходят авторизацию по паролю!"

## Получение данных о сертификатах ##
$users=(Get-ADUser -Filter {Enabled -eq $true} -Properties 'Certificates' | Where Certificates).Certificates |`
 ForEach {[System.Security.Cryptography.X509Certificates.X509Certificate2]$_} |`
 Where {$_.NotAfter -le ($date).AddDays($days) -AND $_.NotAfter -gt ($date)} |`
 select @{Label="ФИО"; Expression={$_.DnsNameList}}, @{Label="Сертификат истекает"; Expression={$_.NotAfter}} |`
 sort "ФИО" | ConvertTo-HTML -Head $header -PreContent $precontent -PostContent $postcontent

## Получение почты из целевой группы для рассылки ##
$groupname = "Рассылка сроков истечения сертификатов смарт-карт"
$smtpServer = "192.168.0.1" # Указать IP почтового сервера
$smtpFrom = "admin@mailserver.ru" # Адрес, с которого будет отправляться письмо
$messageSubject = "Истекающие сертификаты пользователей"
$mails = Get-ADGroupMember $groupname | select samaccountname | %{Get-ADUser $_.samaccountname -Properties mail} | %{write-output "$($_.mail)"}
foreach ($mail in $mails) {
   $smtpTo = $mail
   $message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
   $message.Subject = $messageSubject
   $message.IsBodyHTML = $true
   $message.Body = $users
   $smtp = New-Object Net.Mail.SmtpClient($smtpServer)
   $smtp.Send($message)
}

Рекомендую скормить этот скрипт планировщику заданий с периодичностью раз в неделю. Например, каждый понедельник в 7 часов утра.

Выполнить скрипт PS1 через планировщик заданий можно по этой шпаргалке:

powershell.exe -ExecutionPolicy Bypass -File "C:\папка\script.ps1"

Или скомпилировать скрипт в исполняемый файл с помощью PS2EXE.

Обновление поста

Добавил к скрипту важное условие по количеству истекающих сертификатов: если таковых не найдено — ничего не делать. В итоговом варианте скрипт выглядит так:

## Установка дней ##
$date = Get-Date
$days = 7

## Импорт модуля Active Directory ##
Import-module activedirectory

## Оформление вывода в HTML ##
$header = @"
<style>
  TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
  TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #87cefa;} 
  TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@
$precontent = "В течение $days дней истекут сертификаты на смарт-картах следующих пользователей:<br><br>"
$postcontent = "<br><br>По поводу продления смарт-карты обращайтесь в отдел АСУ и ИБ.<br><br>Эта информация не касается пользователей, которые входят по паролю!<br><br>"

## Получение сертификатов ##
$certs = (Get-ADUser -Filter {Enabled -eq $true} -Properties 'Certificates' | Where Certificates).Certificates |`
         ForEach {[System.Security.Cryptography.X509Certificates.X509Certificate2]$_} |`
         Where {$_.NotAfter -le ($date).AddDays($days) -AND $_.NotAfter -gt ($date)}

## Количество истёкших сертификатов ##
$query = (($certs).Thumbprint | measure).Count

## Условие ##
if ($query -eq $null) { exit }
else
{
    ## Получение данных о сертификатах ##
    $users = $certs |`
    select @{Label="ФИО"; Expression={$_.DnsNameList}}, @{Label="Сертификат истекает"; Expression={$_.NotAfter}} |`
    sort "ФИО" | ConvertTo-HTML -Head $header -PreContent $precontent -PostContent $postcontent

    ## Получение почты заведущих отделениями из группы и отправка письма ##
    $groupname = "Рассылка сроков истечения сертификатов смарт-карт"
    $smtpServer = "192.168.0.1" # Указать IP почтового сервера
    $smtpFrom = "admin@mailserver.ru" # Адрес, с которого будет отправляться письмо
    $messageSubject = "Истекающие сертификаты пользователей"
    $mails = Get-ADGroupMember $groupname | select samaccountname | %{Get-ADUser $_.samaccountname -Properties mail} | %{write-output "$($_.mail)"}
    foreach ($mail in $mails) {
         $smtpTo = $mail
         $message = New-Object System.Net.Mail.MailMessage $smtpfrom, $smtpto
         $message.Subject = $messageSubject
         $message.IsBodyHTML = $true
         $message.Body = $users
         $smtp = New-Object Net.Mail.SmtpClient($smtpServer)
         $smtp.Send($message) }
}