diff --git a/Deploy-LoginMonitor.ps1 b/Deploy-LoginMonitor.ps1 index ffd60c6..a3e3e36 100644 --- a/Deploy-LoginMonitor.ps1 +++ b/Deploy-LoginMonitor.ps1 @@ -190,8 +190,8 @@ function Test-RdpMonitorMainProcessRunning { function Set-RdpMonitorRestartRequestFromDeploy { param( - [ValidateSet('settings', 'recycle')] - [string]$Mode = 'recycle', + [ValidateSet('settings', 'recycle', 'stop')] + [string]$Mode = 'stop', [string]$Reason = 'deploy' ) @@ -617,8 +617,8 @@ function Stop-RdpLoginMonitorMainProcesses { return } - Write-DeployLog "Graceful recycle: запись restart.request (mode=recycle), без дочернего PowerShell." - Set-RdpMonitorRestartRequestFromDeploy -Mode 'recycle' -Reason 'deploy' + Write-DeployLog "Graceful stop: запись restart.request (mode=stop), без дочернего PowerShell." + Set-RdpMonitorRestartRequestFromDeploy -Mode 'stop' -Reason 'deploy' $deadline = (Get-Date).AddSeconds($GracefulWaitSec) while ((Get-Date) -lt $deadline) { @@ -629,7 +629,7 @@ function Stop-RdpLoginMonitorMainProcesses { Start-Sleep -Seconds 1 } - Write-DeployLog "Таймаут graceful recycle — принудительная остановка оставшихся процессов монитора." + Write-DeployLog "Таймаут graceful stop — принудительная остановка оставшихся процессов монитора." try { $procs = Get-CimInstance Win32_Process -Filter "Name = 'powershell.exe' OR Name = 'pwsh.exe'" -ErrorAction Stop foreach ($proc in $procs) { @@ -751,7 +751,7 @@ try { Sync-RdpMonitorSettingsFromShare -ExampleOnShare $settingsExampleShare -LocalSettings $settingsLocal $installArgs = @( - '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript, '-InstallTasks' + '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript, '-InstallTasks', '-SkipImmediateMainRun' ) $instOut = Join-Path $InstallRoot "Logs\deploy_installtasks_stdout.log" $instErr = Join-Path $InstallRoot "Logs\deploy_installtasks_stderr.log" @@ -787,10 +787,25 @@ try { Write-DeployLog "Записана метка обновления: $DeployUpdateMarkerPath (Version=$shareVerRaw; UpdatedAt=$updStamp)." if (-not $SkipStartMonitorAfterUpdate) { - Start-Process -FilePath $PsExe -ArgumentList @( - '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript - ) -WindowStyle Hidden - Write-DeployLog "Запущен процесс монитора (новый файл)." + $canonical = [System.IO.Path]::GetFullPath($LocalScript) + if (Test-RdpMonitorMainProcessRunning -CanonicalScript $canonical) { + Write-DeployLog "Монитор уже запущен — повторный старт не выполняем." + } else { + $taskName = 'RDP-Login-Monitor' + $runEa = $ErrorActionPreference + try { + $ErrorActionPreference = 'SilentlyContinue' + $runOut = & schtasks.exe /Run /TN $taskName 2>&1 + foreach ($line in @($runOut)) { + if ($null -ne $line -and "$line".Trim().Length -gt 0) { + Write-DeployLog "schtasks /Run $taskName : $line" + } + } + } finally { + $ErrorActionPreference = $runEa + } + Write-DeployLog "Запуск монитора через schtasks /Run ($taskName)." + } } else { Write-DeployLog "Запуск монитора пропущен (-SkipStartMonitorAfterUpdate); поднимется при следующей загрузке или watchdog." } diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index c3a542c..dd32cb8 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -35,6 +35,7 @@ param( [string]$MailSmtpPasswordProtectedB64 = '', [switch]$Watchdog, [switch]$InstallTasks, + [switch]$SkipImmediateMainRun, [switch]$SkipScheduledTaskMaintenance, [switch]$CheckSac, [switch]$RequestRestart, @@ -74,13 +75,14 @@ $script:MonitorSingletonLockStream = $null $script:MonitorRestartRequestFile = Join-Path $script:InstallRoot 'restart.request' $script:MonitorRecycleRequested = $false $script:MonitorLoopInitialized = $false +$script:MonitorStopRequested = $false # Версия: пишется в лог и в Telegram. При доменном развёртывании через шару см. DEPLOY.md — # триггер обновления на клиентах даёт файл version.txt на шаре (его номер можно поднять и без смены # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "1.2.30-SAC" +$ScriptVersion = "1.2.31-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" @@ -415,7 +417,7 @@ function Get-RdpMonitorPowerShellExe { function Set-RdpMonitorRestartRequest { param( - [ValidateSet('settings', 'recycle')] + [ValidateSet('settings', 'recycle', 'stop')] [string]$Mode = 'settings', [string]$Reason = 'manual' ) @@ -446,7 +448,7 @@ function Get-RdpMonitorRestartRequest { try { Remove-Item -LiteralPath $script:MonitorRestartRequestFile -Force -ErrorAction SilentlyContinue } catch { } - if ($mode -ne 'recycle') { $mode = 'settings' } + if ($mode -notin @('recycle', 'stop')) { $mode = 'settings' } return [pscustomobject]@{ Mode = $mode; Reason = $reason } } @@ -485,6 +487,8 @@ function Test-RdpMonitorScheduledTaskMatches { } function Register-RdpMonitorScheduledTasksCore { + param([switch]$SkipImmediateMainRun) + Write-Log "Register-RdpMonitorScheduledTasksCore: ветка v$ScriptVersion (watchdog через schtasks /SC MINUTE, без CIM RepetitionInterval)." $psExe = Get-RdpMonitorPowerShellExe $canonicalScript = [System.IO.Path]::GetFullPath((Join-Path $script:InstallRoot $script:CanonicalScriptName)) @@ -522,35 +526,39 @@ function Register-RdpMonitorScheduledTasksCore { } Write-Log "Задача планировщика: $($script:ScheduledTaskNameWatchdog) (schtasks, каждые 5 минут, контроль процесса)." - # Не ждём перезагрузку: сразу запускаем основную задачу. - $runMainEa = $ErrorActionPreference - try { - $ErrorActionPreference = 'SilentlyContinue' - $runMainOut = & $schtasksExe /Run /TN $script:ScheduledTaskNameMain 2>&1 - foreach ($line in @($runMainOut)) { - if ($null -ne $line -and "$line".Trim().Length -gt 0) { - Write-Log "schtasks main /Run: $line" + if (-not $SkipImmediateMainRun) { + # Не ждём перезагрузку: сразу запускаем основную задачу. + $runMainEa = $ErrorActionPreference + try { + $ErrorActionPreference = 'SilentlyContinue' + $runMainOut = & $schtasksExe /Run /TN $script:ScheduledTaskNameMain 2>&1 + foreach ($line in @($runMainOut)) { + if ($null -ne $line -and "$line".Trim().Length -gt 0) { + Write-Log "schtasks main /Run: $line" + } } + } finally { + $ErrorActionPreference = $runMainEa } - } finally { - $ErrorActionPreference = $runMainEa - } - Write-Log "Немедленный прогон основной задачи запрошен (schtasks /Run)." + Write-Log "Немедленный прогон основной задачи запрошен (schtasks /Run)." - # Первый запуск watchdog по расписанию может быть почти через 5 мин — ставим одноразовый прогон в очередь. - $runEa = $ErrorActionPreference - try { - $ErrorActionPreference = 'SilentlyContinue' - $runOut = & $schtasksExe /Run /TN $script:ScheduledTaskNameWatchdog 2>&1 - foreach ($line in @($runOut)) { - if ($null -ne $line -and "$line".Trim().Length -gt 0) { - Write-Log "schtasks watchdog /Run: $line" + # Первый запуск watchdog по расписанию может быть почти через 5 мин — ставим одноразовый прогон в очередь. + $runEa = $ErrorActionPreference + try { + $ErrorActionPreference = 'SilentlyContinue' + $runOut = & $schtasksExe /Run /TN $script:ScheduledTaskNameWatchdog 2>&1 + foreach ($line in @($runOut)) { + if ($null -ne $line -and "$line".Trim().Length -gt 0) { + Write-Log "schtasks watchdog /Run: $line" + } } + } finally { + $ErrorActionPreference = $runEa } - } finally { - $ErrorActionPreference = $runEa + Write-Log "Немедленный прогон watchdog запрошен (schtasks /Run)." + } else { + Write-Log "Немедленный schtasks /Run пропущен (-SkipImmediateMainRun)." } - Write-Log "Немедленный прогон watchdog запрошен (schtasks /Run)." } function Ensure-RdpMonitorScheduledTasks { @@ -640,7 +648,7 @@ if ($InstallTasks) { Write-Log "InstallTasks: нужны права администратора." exit 1 } - Register-RdpMonitorScheduledTasksCore + Register-RdpMonitorScheduledTasksCore -SkipImmediateMainRun:$SkipImmediateMainRun Write-Log "InstallTasks: задачи планировщика обновлены." exit 0 } @@ -765,6 +773,18 @@ function ConvertTo-TelegramHtml { return [System.Net.WebUtility]::HtmlEncode([string]$Text) } +function Convert-TelegramHtmlToPlainBody { + param([string]$Html) + if ([string]::IsNullOrWhiteSpace($Html)) { return '' } + $text = [string]$Html + $text = $text -replace '', '' + $text = $text -replace '', '' + $text = $text -replace '', '' + $text = $text -replace '', '' + $text = [System.Net.WebUtility]::HtmlDecode($text) + return $text.Trim() +} + function Send-TelegramMessage { param([string]$Message) @@ -964,9 +984,17 @@ function Send-RdpMonitorLifecycleNotification { ) $title = if (-not [string]::IsNullOrWhiteSpace($SacTitle)) { $SacTitle } else { "RDP login monitor: $Lifecycle" } + $plainBody = Convert-TelegramHtmlToPlainBody -Html $TelegramHtmlMessage + $sacDetails = @{ + lifecycle = $Lifecycle + trigger = $Trigger + } + if (-not [string]::IsNullOrWhiteSpace($plainBody)) { + $sacDetails.notification_body = $plainBody + } Send-MonitorNotification -Message $TelegramHtmlMessage -EmailSubject $EmailSubject ` -SacEventType 'agent.lifecycle' -SacSeverity $SacSeverity -SacTitle $title -SacSummary $SacSummary ` - -SacDetails @{ lifecycle = $Lifecycle; trigger = $Trigger } | Out-Null + -SacDetails $sacDetails | Out-Null } function Test-Administrator { @@ -1779,7 +1807,7 @@ function Send-Heartbeat { param([switch]$IsStartup = $false) $timestamp = Get-Date -Format "dd.MM.yyyy HH:mm:ss" - $hHost = (ConvertTo-TelegramHtml (Get-MonitorServerLabel)) + $hHost = (ConvertTo-TelegramHtml (Get-MonitorServerLabelWithIp)) function Get-DeployUpdateMarker { $info = [pscustomobject]@{ @@ -1885,7 +1913,7 @@ function Send-Heartbeat { Send-RdpMonitorLifecycleNotification -Lifecycle 'started' -Trigger $lifecycleTrigger ` -TelegramHtmlMessage $message -EmailSubject 'RDP Login Monitor: запуск' ` -SacTitle 'RDP login monitor started' ` - -SacSummary "Мониторинг запущен на $(Get-MonitorServerLabel), версия $ScriptVersion" + -SacSummary "Мониторинг запущен на $(Get-MonitorServerLabelWithIp), версия $ScriptVersion" Write-Log "Отправлено уведомление о запуске скрипта (каналы: $notifyChain)" } else { Write-TextFileUtf8Bom -Path $HeartbeatFile -Text $timestamp @@ -3151,6 +3179,7 @@ function Start-LoginMonitor { do { $script:MonitorRecycleRequested = $false + $script:MonitorStopRequested = $false if (-not $script:MonitorLoopInitialized) { Cleanup-OldLogs @@ -3185,6 +3214,11 @@ function Start-LoginMonitor { $restartReq = Get-RdpMonitorRestartRequest if ($null -ne $restartReq) { $script:StopNotificationSent = $true + if ($restartReq.Mode -eq 'stop') { + Write-Log "Graceful stop: запрос '$($restartReq.Reason)' — выход без запуска нового процесса." + $script:MonitorStopRequested = $true + break + } if ($restartReq.Mode -eq 'recycle') { Write-Log "Graceful recycle: запрос '$($restartReq.Reason)' — выход для запуска нового процесса (обновлённый скрипт с диска)." $script:MonitorRecycleRequested = $true @@ -3192,7 +3226,7 @@ function Start-LoginMonitor { } Write-Log "Graceful restart (settings): запрос '$($restartReq.Reason)' — перечитываю настройки в этом же процессе PowerShell." Invoke-RdpMonitorReloadSettings | Out-Null - $hHost = ConvertTo-TelegramHtml (Get-MonitorServerLabel) + $hHost = ConvertTo-TelegramHtml (Get-MonitorServerLabelWithIp) $reloadMsg = "🔄 Настройки монитора перечитаны`r`n" $reloadMsg += "🖥️ Сервер: $hHost`r`n" $reloadMsg += "🏷️ Версия: $(ConvertTo-TelegramHtml $ScriptVersion)`r`n" @@ -3576,6 +3610,9 @@ function Start-LoginMonitor { if ($script:MonitorRecycleRequested) { return } + if ($script:MonitorStopRequested) { + return + } } while ($true) } @@ -3623,6 +3660,12 @@ try { } Start-LoginMonitor -MonitorInterval 5 -MonitorInteractiveOnly + if ($script:MonitorStopRequested) { + $script:StopNotificationSent = $true + Write-Log "Graceful stop: завершение без запуска нового процесса." + exit 0 + } + if ($script:MonitorRecycleRequested) { $script:StopNotificationSent = $true $canonicalScript = Join-Path $script:InstallRoot $script:CanonicalScriptName diff --git a/Sac-Client.ps1 b/Sac-Client.ps1 index c82c9fe..e49c2fd 100644 --- a/Sac-Client.ps1 +++ b/Sac-Client.ps1 @@ -253,12 +253,18 @@ function Get-SacHostBlock { hostname = $hostname os_family = 'windows' } - if (Get-Variable -Name ServerDisplayName -ErrorAction SilentlyContinue) { + $displayLabel = $hostname + if (Get-Command -Name Get-MonitorServerLabelWithIp -ErrorAction SilentlyContinue) { + $displayLabel = [string](Get-MonitorServerLabelWithIp) + } elseif (Get-Variable -Name ServerDisplayName -ErrorAction SilentlyContinue) { $label = (Get-Variable -Name ServerDisplayName -ValueOnly) if (-not [string]::IsNullOrWhiteSpace([string]$label)) { - $hostBlock.display_name = [string]$label.Trim() + $displayLabel = [string]$label.Trim() } } + if (-not [string]::IsNullOrWhiteSpace($displayLabel)) { + $hostBlock.display_name = $displayLabel + } $ipv4 = Get-SacHostIPv4 if (-not [string]::IsNullOrWhiteSpace($ipv4)) { $hostBlock.ipv4 = $ipv4 diff --git a/version.txt b/version.txt index 16f664c..91abfac 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.30-SAC +1.2.31-SAC