feat: single deploy startup, stop mode, notification_body 1.2.31-SAC
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+25
-10
@@ -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."
|
||||
}
|
||||
|
||||
+74
-31
@@ -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 '</?b>', ''
|
||||
$text = $text -replace '</?i>', ''
|
||||
$text = $text -replace '</?code>', ''
|
||||
$text = $text -replace '</?pre>', ''
|
||||
$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 = "<b>🔄 Настройки монитора перечитаны</b>`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
|
||||
|
||||
+8
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.2.30-SAC
|
||||
1.2.31-SAC
|
||||
|
||||
Reference in New Issue
Block a user