From 3d03d905a160637ea1f729e9b30c47f07fe2784f Mon Sep 17 00:00:00 2001 From: PTah Date: Mon, 27 Apr 2026 10:01:42 +1000 Subject: [PATCH] Fix scheduling checks and harden Telegram message formatting. Correct daily rotation/report trigger logic to run after the configured slot once per period, avoid duplicate stop notifications on fatal errors, and HTML-escape dynamic Telegram fields to prevent markup breakage. --- Login_Monitor.ps1 | 109 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index 2ecabb7..02247c1 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -136,6 +136,12 @@ function Write-Log { Write-Host ($logMessage.TrimEnd("`r`n")) } +function ConvertTo-TelegramHtml { + param([string]$Text) + if ($null -eq $Text) { return '' } + return [System.Net.WebUtility]::HtmlEncode([string]$Text) +} + try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 @@ -395,10 +401,11 @@ function Send-Heartbeat { param([switch]$IsStartup = $false) $timestamp = Get-Date -Format "dd.MM.yyyy HH:mm:ss" + $hHost = (ConvertTo-TelegramHtml $env:COMPUTERNAME) if ($IsStartup) { $message = "✅ Мониторинг логинов ЗАПУЩЕН`r`n" - $message += "🖥️ Сервер: $env:COMPUTERNAME`r`n" + $message += "🖥️ Сервер: $hHost`r`n" $message += "🕐 Время запуска: $timestamp" if (Test-RDSDeploymentPresent) { $message += "`r`n🔐 RDS (хост сессий): обнаружены компоненты RDS помимо чистого шлюза — в мониторинг входят входы по RDP/RDS на этом узле (Security 4624/4625, типы входа по настройке скрипта)." @@ -422,10 +429,12 @@ function Send-StopNotification { param([string]$Reason) $timestamp = Get-Date -Format "dd.MM.yyyy HH:mm:ss" + $hHost = (ConvertTo-TelegramHtml $env:COMPUTERNAME) + $hReason = (ConvertTo-TelegramHtml $Reason) $message = "⚠️ МОНИТОРИНГ ЛОГИНОВ ОСТАНОВЛЕН`r`n" - $message += "🖥️ Сервер: $env:COMPUTERNAME`r`n" + $message += "🖥️ Сервер: $hHost`r`n" $message += "🕐 Время остановки: $timestamp`r`n" - $message += "📋 Причина: $Reason" + $message += "📋 Причина: $hReason" Send-TelegramMessage -Message $message | Out-Null Write-Log "Уведомление об остановке отправлено: $Reason" @@ -459,6 +468,24 @@ function Rotate-LogFile { return $false } +function Get-NextLocalSlotBoundary { + param( + [int]$Hour, + [int]$Minute + ) + $now = Get-Date + $slotToday = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $Hour -Minute $Minute -Second 0 + if ($now -lt $slotToday) { return $slotToday } + return $slotToday.AddDays(1) +} + +function Get-MostRecentRotationSlot { + $now = Get-Date + $slotToday = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $LogRotationHour -Minute $LogRotationMinute -Second 0 + if ($now -ge $slotToday) { return $slotToday } + return $slotToday.AddDays(-1) +} + function Check-AndRotateLog { $lastRotationFile = Join-Path $LogBackupFolder "last_rotation.txt" $lastRotation = $null @@ -471,18 +498,15 @@ function Check-AndRotateLog { } $currentTime = Get-Date - $targetRotationTime = Get-Date -Year $currentTime.Year -Month $currentTime.Month -Day $currentTime.Day ` - -Hour $LogRotationHour -Minute $LogRotationMinute -Second 0 - if ($currentTime -ge $targetRotationTime) { $targetRotationTime = $targetRotationTime.AddDays(1) } - + $mostRecentSlot = Get-MostRecentRotationSlot $shouldRotate = $false - if ($lastRotation -eq $null) { $shouldRotate = $true } - elseif ($currentTime -ge $targetRotationTime) { $shouldRotate = $true } + if ($null -eq $lastRotation) { $shouldRotate = $true } + elseif ($lastRotation -lt $mostRecentSlot) { $shouldRotate = $true } if ($shouldRotate -and (Rotate-LogFile)) { - Write-TextFileUtf8Bom -Path $lastRotationFile -Text ($currentTime.ToString("yyyy-MM-dd HH:mm:ss")) + Write-TextFileUtf8Bom -Path $lastRotationFile -Text ($currentTime.ToString("yyyy-MM-dd HH:mm:ss")) } - return $targetRotationTime + return (Get-NextLocalSlotBoundary -Hour $LogRotationHour -Minute $LogRotationMinute) } function Cleanup-OldLogs { @@ -662,6 +686,13 @@ function Format-LoginEvent { $logHost = $SecurityLogComputerName if ([string]::IsNullOrWhiteSpace($logHost)) { $logHost = $env:COMPUTERNAME } + $hUser = (ConvertTo-TelegramHtml $Username) + $hLog = (ConvertTo-TelegramHtml $logHost) + $hWkst = (ConvertTo-TelegramHtml $ComputerName) + $hIp = (ConvertTo-TelegramHtml $SourceIP) + $hProc = (ConvertTo-TelegramHtml $ProcessName) + $hLtName = (ConvertTo-TelegramHtml $LogonTypeName) + $hTime = (ConvertTo-TelegramHtml ($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss'))) $message = "" if ($EventID -eq 4624) { $message += "✅ УСПЕШНЫЙ ВХОД" } @@ -669,13 +700,13 @@ function Format-LoginEvent { else { $message += "⚠️ СОБЫТИЕ" } $message += "`r`n" - $message += "👤 Пользователь: $Username`r`n" - $message += "🏢 Сервер (журнал Security): $logHost`r`n" - $message += "🖥️ Рабочая станция (клиент из события): $ComputerName`r`n" - $message += "🌐 IP адрес: $SourceIP`r`n" - $message += "⚙️ Процесс/Код: $ProcessName`r`n" - $message += "🔑 Тип входа: $LogonTypeName ($LogonType)`r`n" - $message += "🕐 Время: $($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss'))`r`n" + $message += "👤 Пользователь: $hUser`r`n" + $message += "🏢 Сервер (журнал Security): $hLog`r`n" + $message += "🖥️ Рабочая станция (клиент из события): $hWkst`r`n" + $message += "🌐 IP адрес: $hIp`r`n" + $message += "⚙️ Процесс/Код: $hProc`r`n" + $message += "🔑 Тип входа: $hLtName ($LogonType)`r`n" + $message += "🕐 Время: $hTime`r`n" $message += "🔢 Event ID: $EventID" return $message @@ -738,20 +769,26 @@ function Format-RDGatewayEvent { [datetime]$TimeCreated ) + $hUser = (ConvertTo-TelegramHtml $Username) + $hExt = (ConvertTo-TelegramHtml $ExternalIP) + $hInt = (ConvertTo-TelegramHtml $InternalIP) + $hProto = (ConvertTo-TelegramHtml $Protocol) + $hTime = (ConvertTo-TelegramHtml ($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss'))) + $message = "" if ($EventID -eq 302) { $message += "🖥️ УСПЕШНОЕ ПОДКЛЮЧЕНИЕ ЧЕРЕЗ RD GATEWAY" } elseif ($EventID -eq 303) { $message += "❌ НЕУДАЧНОЕ ПОДКЛЮЧЕНИЕ ЧЕРЕЗ RD GATEWAY" } else { $message += "⚠️ СОБЫТИЕ RD GATEWAY" } $message += "`r`n" - $message += "👤 Пользователь: $Username`r`n" - $message += "🌐 IP пользователя (внешний): $ExternalIP`r`n" - $message += "🖥️ IP внутренний: $InternalIP`r`n" - $message += "🔌 Протокол: $Protocol`r`n" + $message += "👤 Пользователь: $hUser`r`n" + $message += "🌐 IP пользователя (внешний): $hExt`r`n" + $message += "🖥️ IP внутренний: $hInt`r`n" + $message += "🔌 Протокол: $hProto`r`n" if ($EventID -eq 303 -and $ErrorCode -ne "0" -and $ErrorCode -ne "N/A") { - $message += "⚠️ Код ошибки: $ErrorCode`r`n" + $message += "⚠️ Код ошибки: $(ConvertTo-TelegramHtml $ErrorCode)`r`n" } - $message += "🕐 Время: $($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss'))`r`n" + $message += "🕐 Время: $hTime`r`n" $message += "🔢 Event ID: $EventID" return $message } @@ -777,7 +814,7 @@ function Send-DailyReport { # PS 5.1: без @() один логин даёт скаляр String (нет .Count); пустой список даёт $null. $uniqueUsers = @($usernames | Sort-Object -Unique) $message = "📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ`r`n" - $message += "🖥️ Сервер: $env:COMPUTERNAME`r`n" + $message += "🖥️ Сервер: $(ConvertTo-TelegramHtml $env:COMPUTERNAME)`r`n" $message += "🕐 Время отчета: $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')`r`n" $message += "👥 Активных сессий (quser): $count`r`n" if ($uniqueUsers.Count -gt 0) { @@ -807,15 +844,19 @@ function Check-AndSendDailyReport { } $now = Get-Date - $target = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $DailyReportHour -Minute $DailyReportMinute -Second 0 - if ($now -ge $target) { $target = $target.AddDays(1) } - + $reportSlotToday = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $DailyReportHour -Minute $DailyReportMinute -Second 0 $shouldSend = $false - if ($lastReport -eq $null) { $shouldSend = $true } - elseif ($now -ge $target) { $shouldSend = $true } + if ($now -ge $reportSlotToday) { + if ($null -eq $lastReport) { + $shouldSend = $true + } else { + $dLast = $lastReport.Date + if ($dLast -lt $now.Date) { $shouldSend = $true } + } + } if ($shouldSend) { Send-DailyReport | Out-Null } - return $target + return (Get-NextLocalSlotBoundary -Hour $DailyReportHour -Minute $DailyReportMinute) } function Start-LoginMonitor { @@ -944,14 +985,18 @@ function Start-LoginMonitor { } } +$script:StopNotificationSent = $false try { Test-TelegramConnection | Out-Null Start-LoginMonitor -MonitorInterval 5 -MonitorInteractiveOnly } catch { Write-Log "Критическая ошибка: $($_.Exception.Message)" Send-StopNotification -Reason "Критическая ошибка: $($_.Exception.Message)" + $script:StopNotificationSent = $true throw } finally { - Send-StopNotification -Reason "Скрипт завершил работу" + if (-not $script:StopNotificationSent) { + Send-StopNotification -Reason "Скрипт завершил работу" + } }