diff --git a/Encrypt-DpapiForRdpMonitor.ps1 b/Encrypt-DpapiForRdpMonitor.ps1 index 4996797..b5030a8 100644 --- a/Encrypt-DpapiForRdpMonitor.ps1 +++ b/Encrypt-DpapiForRdpMonitor.ps1 @@ -1,5 +1,5 @@ # Запуск: от администратора на ТОМ ЖЕ компьютере, где будет Login_Monitor.ps1. -# Результат (Base64) вставьте в $TelegramBotTokenProtectedB64 / $TelegramChatIDProtectedB64. +# Результат (Base64) вставьте в $TelegramBotTokenProtectedB64 / $TelegramChatIDProtectedB64 / $MailSmtpPasswordProtectedB64. param( [Parameter(Mandatory = $true)][string]$PlainText ) diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index 36eabfc..ab9e5e6 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -3,6 +3,7 @@ Мониторинг логинов/попыток входа с уведомлениями в Telegram .DESCRIPTION Отслеживает события входа в систему (Security 4624/4625) и события RD Gateway (302/303), + на заданном КД — блокировки учётных записей (Security 4740) с IP из логов IIS ActiveSync, отправляет уведомления в Telegram, делает ротацию логов, heartbeat в файл и ежедневный отчет. .NOTES Требуется: PowerShell 5.0+, запуск от администратора. @@ -28,6 +29,7 @@ param( [string]$TelegramChatID = '', [string]$TelegramBotTokenProtectedB64 = "", [string]$TelegramChatIDProtectedB64 = "", + [string]$MailSmtpPasswordProtectedB64 = "", [switch]$Watchdog, [switch]$InstallTasks, [switch]$SkipScheduledTaskMaintenance @@ -69,7 +71,7 @@ $script:MonitorSingletonLockStream = $null # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "1.4.4" +$ScriptVersion = "1.5.2" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" @@ -148,6 +150,25 @@ $IgnoreAdvapiNetworkLogonSourceIps = @( ) $IgnoreAdvapiNetworkLogonProcessContains = "Advapi" +# Блокировка учётной записи AD (Security 4740) + IP клиента из логов IIS ActiveSync. +# Мониторинг включается только если скрипт запущен на узле, имя которого совпадает с $LockoutMonitorDomainController. +$LockoutMonitorDomainController = "" +$NetBiosDomainName = "" +$ExchangeIisLogPath = "" +$ExchangeServerHostForIisExclude = "" +$ExchangeIisLogTailLines = 5000 + +# Очередь оповещений: telegram, email (или tg, mail). Пусто = авто: настроенные каналы, порядок telegram → email. +$NotifyOrder = "" +$MailSmtpHost = "" +$MailSmtpPort = 587 +$MailSmtpUser = "" +$MailSmtpPassword = "" +$MailFrom = "" +$MailTo = "" +$MailSmtpStartTls = $true +$MailSmtpSsl = $false + # ============================================ # ИНИЦИАЛИЗАЦИЯ # ============================================ @@ -529,6 +550,70 @@ if (-not [string]::IsNullOrWhiteSpace($TelegramChatIDProtectedB64)) { if ($TelegramBotToken -eq '') { $TelegramBotToken = "" } if ($TelegramChatID -eq '') { $TelegramChatID = "" } +if (-not [string]::IsNullOrWhiteSpace($MailSmtpPasswordProtectedB64)) { + try { + $MailSmtpPassword = Unprotect-RdpMonitorDpapiB64 -Base64 $MailSmtpPasswordProtectedB64 + } catch { + Write-Host "Ошибка расшифровки MailSmtpPassword (DPAPI): $($_.Exception.Message)" + exit 1 + } +} + +function Test-NotifyTelegramConfigured { + return (-not [string]::IsNullOrWhiteSpace($TelegramBotToken)) -and + (-not [string]::IsNullOrWhiteSpace($TelegramChatID)) +} + +function Test-NotifyEmailConfigured { + return (-not [string]::IsNullOrWhiteSpace($MailSmtpHost)) -and + (-not [string]::IsNullOrWhiteSpace($MailFrom)) -and + (-not [string]::IsNullOrWhiteSpace($MailTo)) +} + +function Get-NotifyOrderChannels { + $configured = [System.Collections.Generic.List[string]]::new() + if (Test-NotifyTelegramConfigured) { $configured.Add('telegram') | Out-Null } + if (Test-NotifyEmailConfigured) { $configured.Add('email') | Out-Null } + + if ([string]::IsNullOrWhiteSpace($NotifyOrder)) { + return @($configured) + } + + $requested = [System.Collections.Generic.List[string]]::new() + foreach ($part in ($NotifyOrder -split '[,\s;]+')) { + $p = $part.Trim().ToLowerInvariant() + if ([string]::IsNullOrWhiteSpace($p)) { continue } + $channel = switch -Regex ($p) { + '^(tg|telegram)$' { 'telegram' } + '^(mail|email|e-mail)$' { 'email' } + default { + Write-Log "NotifyOrder: неизвестный канал '$part' (ожидается telegram/tg или email/mail)" + $null + } + } + if ($null -eq $channel) { continue } + if ($configured.Contains($channel) -and -not $requested.Contains($channel)) { + $requested.Add($channel) | Out-Null + } + } + return @($requested) +} + +function Get-NotifyChainHuman { + $channels = @(Get-NotifyOrderChannels) + if ($channels.Count -eq 0) { + return 'нет (ни Telegram, ни SMTP не настроены)' + } + $labels = foreach ($ch in $channels) { + switch ($ch) { + 'telegram' { 'Telegram' } + 'email' { 'Email (SMTP)' } + default { $ch } + } + } + return ($labels -join ' → ') +} + function ConvertTo-TelegramHtml { param([string]$Text) if ($null -eq $Text) { return '' } @@ -561,6 +646,10 @@ function Send-TelegramMessage { } function Test-TelegramConnection { + if (-not (Test-NotifyTelegramConfigured)) { + Write-Log "Telegram: канал не настроен, проверка пропущена." + return $false + } Write-Log "Проверка подключения к Telegram API..." try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 @@ -577,6 +666,105 @@ function Test-TelegramConnection { return $false } +function ConvertTo-EmailHtmlBody { + param([string]$TelegramHtmlMessage) + $inner = [string]$TelegramHtmlMessage + if ([string]::IsNullOrEmpty($inner)) { $inner = '' } + $inner = $inner -replace "`r`n", "
`r`n" + return @" + + +$inner + + +"@ +} + +function Send-EmailNotification { + param( + [string]$Message, + [string]$Subject = "RDP Login Monitor" + ) + + if (-not (Test-NotifyEmailConfigured)) { + Write-Log "Email: SMTP не настроен (нужны MailSmtpHost, MailFrom, MailTo)" + return $false + } + + try { + $toList = @($MailTo -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) + if ($toList.Count -eq 0) { + Write-Log "Email: MailTo пуст или некорректен" + return $false + } + + $mailParams = @{ + To = $toList + From = $MailFrom.Trim() + Subject = $Subject + Body = (ConvertTo-EmailHtmlBody -TelegramHtmlMessage $Message) + BodyAsHtml = $true + SmtpServer = $MailSmtpHost.Trim() + Port = [int]$MailSmtpPort + Encoding = [System.Text.Encoding]::UTF8 + ErrorAction = 'Stop' + } + if ($MailSmtpSsl -or $MailSmtpStartTls) { + $mailParams['UseSsl'] = $true + } + if (-not [string]::IsNullOrWhiteSpace($MailSmtpUser)) { + $securePass = if ([string]::IsNullOrWhiteSpace($MailSmtpPassword)) { + New-Object System.Security.SecureString + } else { + ConvertTo-SecureString $MailSmtpPassword -AsPlainText -Force + } + $mailParams['Credential'] = New-Object System.Management.Automation.PSCredential($MailSmtpUser.Trim(), $securePass) + } + + Send-MailMessage @mailParams + return $true + } catch { + Write-Log "Ошибка отправки Email: $($_.Exception.Message)" + return $false + } +} + +function Test-MailSmtpConnection { + if (-not (Test-NotifyEmailConfigured)) { + Write-Log "Email: канал не настроен, проверка пропущена." + return $false + } + Write-Log "SMTP: $($MailSmtpHost):$MailSmtpPort (STARTTLS=$MailSmtpStartTls, SSL=$MailSmtpSsl), From=$MailFrom, To=$MailTo" + if (-not [string]::IsNullOrWhiteSpace($MailSmtpUser) -and [string]::IsNullOrWhiteSpace($MailSmtpPassword)) { + Write-Log "SMTP: задан MailSmtpUser, но пароль пуст (возможна ошибка при отправке)." + } + return $true +} + +function Send-MonitorNotification { + param( + [string]$Message, + [string]$EmailSubject = "RDP Login Monitor" + ) + + $channels = @(Get-NotifyOrderChannels) + if ($channels.Count -eq 0) { + Write-Log "Оповещение не отправлено: нет настроенных каналов (Telegram и/или SMTP)" + return $false + } + + $anyOk = $false + foreach ($ch in $channels) { + $ok = switch ($ch) { + 'telegram' { Send-TelegramMessage -Message $Message } + 'email' { Send-EmailNotification -Message $Message -Subject $EmailSubject } + default { $false } + } + if ($ok) { $anyOk = $true } + } + return $anyOk +} + function Test-Administrator { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) @@ -963,6 +1151,8 @@ function Send-Heartbeat { } else { $message += "`r`n🚫 Игнорируются: не задано (ignore.lst отсутствует или пуст)." } + $notifyChain = Get-NotifyChainHuman + $message += "`r`n📢 Каналы уведомлений: $(ConvertTo-TelegramHtml $notifyChain)" if (Test-RDSDeploymentPresent) { $message += "`r`n🔐 RDS (хост сессий): обнаружены компоненты RDS помимо чистого шлюза — в мониторинг входят входы по RDP/RDS на этом узле (Security 4624/4625, типы входа по настройке скрипта)." } @@ -974,8 +1164,16 @@ function Send-Heartbeat { } } catch { } } - Send-TelegramMessage -Message $message | Out-Null - Write-Log "Отправлено уведомление о запуске скрипта" + if (Test-Lockout4740MonitoringActive) { + $message += "`r`n🔒 Блокировки AD: на этом КД отслеживается Security 4740 (блокировка учётной записи)." + if (-not [string]::IsNullOrWhiteSpace($ExchangeIisLogPath)) { + $message += " При событии — IP из логов IIS ActiveSync (ExchangeIisLogPath)." + } else { + $message += " IP из IIS не заданы (ExchangeIisLogPath пуст)." + } + } + Send-MonitorNotification -Message $message -EmailSubject "RDP Login Monitor: запуск" | Out-Null + Write-Log "Отправлено уведомление о запуске скрипта (каналы: $notifyChain)" } else { Write-TextFileUtf8Bom -Path $HeartbeatFile -Text $timestamp } @@ -992,7 +1190,7 @@ function Send-StopNotification { $message += "🕐 Время остановки: $timestamp`r`n" $message += "📋 Причина: $hReason" - Send-TelegramMessage -Message $message | Out-Null + Send-MonitorNotification -Message $message -EmailSubject "RDP Login Monitor: остановка" | Out-Null Write-Log "Уведомление об остановке отправлено: $Reason" } @@ -1602,7 +1800,7 @@ function Send-DailyReport { } else { $message += "`r`nСписок пользователей недоступен (quser пуст или недостаточно прав)." } - Send-TelegramMessage -Message $message | Out-Null + Send-MonitorNotification -Message $message -EmailSubject "RDP Login Monitor: ежедневный отчёт" | Out-Null Write-TextFileUtf8Bom -Path $LastReportFile -Text ((Get-Date).ToString("yyyy-MM-dd HH:mm:ss")) Write-Log "Ежедневный отчет отправлен" return $true @@ -1635,6 +1833,128 @@ function Check-AndSendDailyReport { return (Get-NextLocalSlotBoundary -Hour $DailyReportHour -Minute $DailyReportMinute) } +function Test-Lockout4740MonitoringActive { + if ([string]::IsNullOrWhiteSpace($LockoutMonitorDomainController)) { return $false } + $configured = ($LockoutMonitorDomainController -split '\.')[0].Trim() + $local = ($env:COMPUTERNAME -split '\.')[0].Trim() + return ($configured -ieq $local) +} + +function Get-Lockout4740EventInfo { + param($Event) + $info = [pscustomobject]@{ + TimeCreated = $Event.TimeCreated + Username = "" + Domain = "" + CallerComputer = "" + } + try { + $map = Get-EventDataMap -Event $Event + $info.Username = Get-FirstNonEmptyMapValue -DataMap $map -Keys @( + 'TargetUserName', 'SamAccountName', 'AccountName' + ) + $info.Domain = Get-FirstNonEmptyMapValue -DataMap $map -Keys @( + 'TargetDomainName', 'TargetAccountDomain', 'AccountDomain' + ) + $info.CallerComputer = Get-FirstNonEmptyMapValue -DataMap $map -Keys @( + 'CallerComputerName', 'WorkstationName', 'Workstation' + ) + } catch { + Write-Log "Ошибка разбора XML 4740: $($_.Exception.Message)" + } + if ([string]::IsNullOrWhiteSpace($info.Username) -and $Event.Properties.Count -ge 1) { + $info.Username = [string]$Event.Properties[0].Value + } + if ([string]::IsNullOrWhiteSpace($info.Domain) -and $Event.Properties.Count -ge 2) { + $info.Domain = [string]$Event.Properties[1].Value + } + if ([string]::IsNullOrWhiteSpace($info.CallerComputer) -and $Event.Properties.Count -ge 4) { + $info.CallerComputer = [string]$Event.Properties[3].Value + } + return $info +} + +function Get-ExchangeActiveSyncIpsFromIisLog { + param( + [Parameter(Mandatory = $true)][string]$SamAccountName, + [string]$DomainNetBios = "" + ) + if ([string]::IsNullOrWhiteSpace($ExchangeIisLogPath)) { return @() } + $logDir = $ExchangeIisLogPath.TrimEnd('\') + $logFile = Join-Path $logDir ("u_ex" + (Get-Date).ToUniversalTime().ToString("yyMMdd") + ".log") + if (-not (Test-Path -LiteralPath $logFile)) { + Write-Log "IIS: файл лога не найден: $logFile" + return @() + } + $domainPart = if ([string]::IsNullOrWhiteSpace($DomainNetBios)) { $NetBiosDomainName } else { $DomainNetBios } + $userPattern1 = if ([string]::IsNullOrWhiteSpace($domainPart)) { + $SamAccountName + } else { + "User=$domainPart%5C" + $SamAccountName + } + $userPattern2 = if ([string]::IsNullOrWhiteSpace($domainPart)) { + $SamAccountName + } else { + "$domainPart\" + $SamAccountName + } + $excludeHosts = @('127.0.0.1', '::1') + if (-not [string]::IsNullOrWhiteSpace($ExchangeServerHostForIisExclude)) { + $excludeHosts += $ExchangeServerHostForIisExclude.Trim() + } + $detected = [System.Collections.Generic.List[string]]::new() + try { + $lines = Get-Content -LiteralPath $logFile -Tail $ExchangeIisLogTailLines -ErrorAction Stop + foreach ($line in $lines) { + if ($line -notlike '*401 *' -or $line -notlike '*ActiveSync*') { continue } + if ($line -notlike "*$userPattern1*" -and $line -notlike "*$userPattern2*") { continue } + if ($line -notmatch '(?:\d{1,3}\.){3}\d{1,3}') { continue } + $ip = $Matches[0] + if ($excludeHosts -contains $ip) { continue } + if (-not $detected.Contains($ip)) { $detected.Add($ip) | Out-Null } + } + } catch { + Write-Log "IIS: ошибка чтения $logFile : $($_.Exception.Message)" + } + return @($detected) +} + +function Format-Lockout4740TelegramMessage { + param( + [string]$Username, + [string]$Domain, + [datetime]$TimeCreated, + [string[]]$IisClientIps = @() + ) + $domainLabel = if ([string]::IsNullOrWhiteSpace($Domain)) { $NetBiosDomainName } else { $Domain } + $accountDisplay = if ([string]::IsNullOrWhiteSpace($domainLabel)) { + $Username + } else { + "$domainLabel\$Username" + } + $hUser = ConvertTo-TelegramHtml $accountDisplay + $hTime = ConvertTo-TelegramHtml ($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss')) + + $message = "🔒 Блокировка учётной записи AD (4740)`r`n" + $message += "👤 Пользователь: $hUser`r`n" + $message += "🕐 Время: $hTime`r`n" + + if ($IisClientIps.Count -gt 0) { + $message += "`r`n🌐 IP (попытки ActiveSync, 401):`r`n" + foreach ($ip in $IisClientIps) { + $netType = if ($ip -like '192.168.*' -or $ip -like '10.*' -or $ip -like '172.1[6-9].*' -or $ip -like '172.2[0-9].*' -or $ip -like '172.3[0-1].*') { + 'внутренний' + } else { + 'внешний' + } + $message += ('• {0} ({1})' -f (ConvertTo-TelegramHtml $ip), $netType) + "`r`n" + } + } elseif (-not [string]::IsNullOrWhiteSpace($ExchangeIisLogPath)) { + $message += "`r`nIP в логах IIS ActiveSync для этого пользователя не найдены.`r`n" + } + + return $message +} + function Start-LoginMonitor { param( [int]$MonitorInterval = 5, @@ -1655,6 +1975,17 @@ function Start-LoginMonitor { Write-Log "Режим сервера: Security — LogonType 2, 3, 10" } Write-Log "========================================" + Write-Log "Каналы уведомлений: $(Get-NotifyChainHuman)" + + $lockout4740Enabled = Test-Lockout4740MonitoringActive + if ($lockout4740Enabled) { + Write-Log "Мониторинг блокировок AD (4740) включён на этом КД ($LockoutMonitorDomainController)." + if (-not [string]::IsNullOrWhiteSpace($ExchangeIisLogPath)) { + Write-Log "Обогащение: IIS ActiveSync — $ExchangeIisLogPath" + } + } elseif (-not [string]::IsNullOrWhiteSpace($LockoutMonitorDomainController)) { + Write-Log "Мониторинг 4740 задан для КД '$LockoutMonitorDomainController', но этот узел — $env:COMPUTERNAME (блокировки не отслеживаются)." + } Cleanup-OldLogs Send-Heartbeat -IsStartup @@ -1674,6 +2005,7 @@ function Start-LoginMonitor { $lastCheckTime = (Get-Date).AddSeconds(-10) $lastGatewayCheckTime = (Get-Date).AddSeconds(-10) $lastRcmCheckTime = (Get-Date).AddSeconds(-10) + $lastLockout4740CheckTime = (Get-Date).AddSeconds(-10) $monitorEvents = @(4624, 4625, 4648) while ($true) { @@ -1733,7 +2065,8 @@ function Start-LoginMonitor { -SecurityLogComputerName $event.MachineName Write-Log "Notify: ID=$($event.Id) User=$($eventInfo.Username) LT=$($eventInfo.LogonType) IP=$($eventInfo.SourceIP)" - Send-TelegramMessage -Message $formattedMessage | Out-Null + Send-MonitorNotification -Message $formattedMessage ` + -EmailSubject "RDP Login Monitor: вход (ID $($event.Id))" | Out-Null } } $lastCheckTime = ($events | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1) @@ -1759,7 +2092,8 @@ function Start-LoginMonitor { -ErrorCode $ei.ErrorCode ` -TimeCreated $ei.TimeCreated Write-Log "Notify RDG: ID=$($event.Id) User=$($ei.Username)" - Send-TelegramMessage -Message $msg | Out-Null + Send-MonitorNotification -Message $msg ` + -EmailSubject "RDP Login Monitor: RD Gateway ($($event.Id))" | Out-Null } $lastGatewayCheckTime = ($gatewayEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1) } @@ -1783,12 +2117,39 @@ function Start-LoginMonitor { $msg = Format-Rcm1149Event -Username $rcmInfo.Username -ClientIP $rcmInfo.ClientIP ` -TimeCreated $rcmInfo.TimeCreated -SecurityLogComputerName $event.MachineName Write-Log "Notify RCM 1149: User=$($rcmInfo.Username) IP=$($rcmInfo.ClientIP)" - Send-TelegramMessage -Message $msg | Out-Null + Send-MonitorNotification -Message $msg ` + -EmailSubject "RDP Login Monitor: RDP 1149" | Out-Null } $lastRcmCheckTime = ($rcmEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1) } } + if ($lockout4740Enabled) { + $lockoutEvents = Get-WinEvent -FilterHashtable @{ + LogName = 'Security' + ID = 4740 + StartTime = $lastLockout4740CheckTime + } -ErrorAction SilentlyContinue + + if ($lockoutEvents) { + foreach ($event in $lockoutEvents) { + if ($event.TimeCreated -le $lastLockout4740CheckTime) { continue } + $lo = Get-Lockout4740EventInfo -Event $event + if ([string]::IsNullOrWhiteSpace($lo.Username)) { continue } + + $domainForIis = if ([string]::IsNullOrWhiteSpace($lo.Domain)) { $NetBiosDomainName } else { $lo.Domain } + $iisIps = @(Get-ExchangeActiveSyncIpsFromIisLog -SamAccountName $lo.Username -DomainNetBios $domainForIis) + + $msg = Format-Lockout4740TelegramMessage -Username $lo.Username -Domain $lo.Domain ` + -TimeCreated $lo.TimeCreated -IisClientIps $iisIps + Write-Log "Notify 4740: User=$($lo.Username) IIS_IPs=$($iisIps -join ', ')" + Send-MonitorNotification -Message $msg ` + -EmailSubject "RDP Login Monitor: блокировка УЗ $($lo.Username)" | Out-Null + } + $lastLockout4740CheckTime = ($lockoutEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1) + } + } + $now = Get-Date if ($now -ge $nextHeartbeatTime) { Send-Heartbeat @@ -1810,7 +2171,17 @@ function Start-LoginMonitor { $script:StopNotificationSent = $false try { - Test-TelegramConnection | Out-Null + $notifyChannels = @(Get-NotifyOrderChannels) + if ($notifyChannels.Count -eq 0) { + Write-Log "ВНИМАНИЕ: не настроен ни один канал оповещений (Telegram и/или SMTP в конфигурации скрипта)." + } else { + foreach ($notifyCh in $notifyChannels) { + switch ($notifyCh) { + 'telegram' { Test-TelegramConnection | Out-Null } + 'email' { Test-MailSmtpConnection | Out-Null } + } + } + } Start-LoginMonitor -MonitorInterval 5 -MonitorInteractiveOnly } catch { if ($_.Exception -is [System.Management.Automation.PipelineStoppedException]) { diff --git a/README.md b/README.md index b8f3a8b..571ddbc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ PowerShell-набор для мониторинга входов в Windows с ## Актуальная схема (рекомендуется) - Базовый путь установки: **`C:\ProgramData\RDP-login-monitor\`**. -- Основной скрипт: **`Login_Monitor.ps1`** — журнал Security **`4624`/`4625`** (логика зависит от типа ОС: рабочая станция или сервер/КД), при наличии журнала — **Remote Connection Manager `1149`** (часто актуально для РС с RDP), при роли **RD Gateway** — **`302`/`303`**, **ежедневный отчёт** в Telegram (активные сессии через `quser`), **heartbeat**, **ротация логов**, уведомления в Telegram. +- Основной скрипт: **`Login_Monitor.ps1`** — журнал Security **`4624`/`4625`** (логика зависит от типа ОС: рабочая станция или сервер/КД), при наличии журнала — **Remote Connection Manager `1149`** (часто актуально для РС с RDP), при роли **RD Gateway** — **`302`/`303`**, на указанном **КД** — **`4740`** (блокировка УЗ + IP из IIS ActiveSync), **ежедневный отчёт** в Telegram (активные сессии через `quser`), **heartbeat**, **ротация логов**, уведомления в Telegram и/или Email. - Установка задач: запуск **`Login_Monitor.ps1 -InstallTasks`** создаёт: - `RDP-Login-Monitor` (основной монитор), - `RDP-Login-Monitor-Watchdog` (контроль процесса каждые 5 минут). @@ -27,13 +27,15 @@ PowerShell-набор для мониторинга входов в Windows с 2. Скопируйте в неё как минимум: - `Login_Monitor.ps1` - (для доменного развёртывания отдельно на шаре) `Deploy-LoginMonitor.ps1` и `version.txt`. -3. Откройте `Login_Monitor.ps1` и задайте токен/чат: - - `$TelegramBotToken` или `...ProtectedB64` - - `$TelegramChatID` или `...ProtectedB64` +3. Откройте `Login_Monitor.ps1` и задайте каналы оповещений: + - **Telegram:** `$TelegramBotToken` / `$TelegramChatID` или `...ProtectedB64` + - **Email (SMTP):** `$MailSmtpHost`, `$MailFrom`, `$MailTo`, при необходимости `$MailSmtpUser` / `$MailSmtpPassword` (или `$MailSmtpPasswordProtectedB64` через DPAPI) + - **Порядок:** `$NotifyOrder` — пусто = авто (Telegram → Email, только настроенные); иначе `telegram,email` или `email` и т.п. (допускаются `tg`, `mail`) 4. Запускайте с правами администратора (чтение `Security` журнала и регистрация задач). 5. Логи и служебные файлы будут в: - `C:\ProgramData\RDP-login-monitor\Logs\` 6. (Опционально) Подавление части алертов по списку — см. раздел **«7) ignore.lst»** ниже. +7. (Опционально) Мониторинг блокировок AD на КД — **`$LockoutMonitorDomainController`**, **`$NetBiosDomainName`**, **`$ExchangeIisLogPath`** (UNC к логам IIS ActiveSync), **`$ExchangeServerHostForIisExclude`** (IP сервера Exchange, не считать клиентским). В оповещении: пользователь из 4740 и IP из IIS (401 + ActiveSync). На других узлах блок 4740 не активен. ## 2) Ручной запуск @@ -67,6 +69,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-logi - Heartbeat: - `C:\ProgramData\RDP-login-monitor\Logs\last_heartbeat.txt` обновляется по интервалу **`$HeartbeatInterval`** (по умолчанию раз в час). - Ежедневный отчёт: после первого прохождения дневного слота (по умолчанию **09:00**, задаётся **`$DailyReportHour`** / **`$DailyReportMinute`** в `Login_Monitor.ps1`) в Telegram уходит сводка по **`quser`**; метка последнего отчёта — `Logs\last_daily_report.txt`. +- При старте в Telegram/Email: строка **«Каналы уведомлений»** (фактический порядок доставки), плюс режим RDS/4740 по конфигурации. - Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий». ## 5) Автоматический перезапуск при падении diff --git a/version.txt b/version.txt index 1c99cf0..4cda8f1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.4.4 +1.5.2