From 1d30ca22849623bf6b5951e4a07f25d2b9ffee11 Mon Sep 17 00:00:00 2001 From: PTah Date: Mon, 8 Jun 2026 11:47:46 +1000 Subject: [PATCH] fix: RDP-Login-Monitor scheduled task unlimited ExecutionTimeLimit (2.0.24-SAC) Default PT72H caused Task Scheduler to stop the monitor after 72 hours. Register main task with PT0S, re-register on deploy and Ensure when limit is set. Co-authored-by: Cursor --- Deploy-LoginMonitor.ps1 | 44 ++++++++++++++++++++++- Install-ScheduledTasks.ps1 | 3 +- Login_Monitor.ps1 | 71 +++++++++++++++++++++++++++++++++----- version.txt | 2 +- 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/Deploy-LoginMonitor.ps1 b/Deploy-LoginMonitor.ps1 index bf50d20..566ccda 100644 --- a/Deploy-LoginMonitor.ps1 +++ b/Deploy-LoginMonitor.ps1 @@ -929,6 +929,42 @@ function Stop-RdpLoginMonitorMainProcesses { } } +function Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime { + param([string]$TaskName = 'RDP-Login-Monitor') + try { + $limit = (Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop | Select-Object -First 1).Settings.ExecutionTimeLimit + if ($null -eq $limit) { return $true } + if ($limit.Ticks -le 0) { return $false } + if ($limit.TotalDays -ge 999) { return $false } + return $true + } catch { + return $true + } +} + +function Get-RdpMonitorDeployMainTaskExecutionTimeLimitLabel { + param([string]$TaskName = 'RDP-Login-Monitor') + try { + $limit = (Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop | Select-Object -First 1).Settings.ExecutionTimeLimit + if ($null -eq $limit) { return '(null)' } + if ($limit.Ticks -le 0) { return 'PT0S (без лимита)' } + return $limit.ToString() + } catch { + return '(задача не найдена)' + } +} + +function Write-RdpMonitorDeployScheduledTaskVerification { + param([string]$TaskName = 'RDP-Login-Monitor') + if (Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime -TaskName $TaskName) { + $label = Get-RdpMonitorDeployMainTaskExecutionTimeLimitLabel -TaskName $TaskName + Write-DeployLog "ПРЕДУПРЕЖДЕНИЕ: $TaskName ExecutionTimeLimit=$label — ожидался PT0S (без лимита). Проверьте InstallTasks и права администратора." + return $false + } + Write-DeployLog "Задача $TaskName: ExecutionTimeLimit=PT0S (без лимита) — OK." + return $true +} + # --- main --- try { $shareRoot = Resolve-SourceShareRoot @@ -969,7 +1005,8 @@ try { $needsExchangeNoisePatch = Test-RdpMonitorSettingsNeedsExchangeNoisePatch -SettingsPath $settingsLocal $needsWinRmInboundBlock = Test-RdpMonitorSettingsNeedsWinRmInboundBlock -SettingsPath $settingsLocal $needsBundleSync = Test-RdpMonitorDeployBundleNeedsSync -ShareRoot $shareRoot - $needsSacBootstrap = $needsSettingsBootstrap -or $needsBundleSync -or $needsDisplayNameHint -or $needsDailyReportHint -or $needsDailyReportRepair -or $needsExchangeNoisePatch -or $needsWinRmInboundBlock + $needsTaskExecutionLimitFix = Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime + $needsSacBootstrap = $needsSettingsBootstrap -or $needsBundleSync -or $needsDisplayNameHint -or $needsDailyReportHint -or $needsDailyReportRepair -or $needsExchangeNoisePatch -or $needsWinRmInboundBlock -or $needsTaskExecutionLimitFix if (Test-RdpMonitorExchangeServerRole) { Write-DeployLog "Обнаружена роль Exchange — при необходимости допишем WinRM/4624 noise settings в login_monitor.settings.ps1." @@ -1006,6 +1043,9 @@ try { Write-DeployLog "Версия совпадает ($shareVerRaw), но на Exchange не хватает noise settings (WinRM/4624) — продолжаем деплой." } elseif ($needsWinRmInboundBlock) { Write-DeployLog "Версия совпадает ($shareVerRaw), но в settings отсутствует обязательный блок WinRM inbound — продолжаем деплой." + } elseif ($needsTaskExecutionLimitFix) { + $limitLabel = Get-RdpMonitorDeployMainTaskExecutionTimeLimitLabel + Write-DeployLog "Версия совпадает ($shareVerRaw), но RDP-Login-Monitor имеет ExecutionTimeLimit=$limitLabel — перерегистрируем задачи (InstallTasks)." } } if ($cmp -lt 0 -and -not $AllowDowngrade) { @@ -1072,6 +1112,8 @@ try { Write-DeployLog "InstallTasks выполнен (код 0)." } + [void](Write-RdpMonitorDeployScheduledTaskVerification) + [System.IO.File]::WriteAllText($VersionStampPath, "$shareVerRaw`r`n", $Utf8Bom) Write-DeployLog "Записана метка версии: $VersionStampPath" diff --git a/Install-ScheduledTasks.ps1 b/Install-ScheduledTasks.ps1 index 2135159..b0ea9b6 100644 --- a/Install-ScheduledTasks.ps1 +++ b/Install-ScheduledTasks.ps1 @@ -46,7 +46,8 @@ $mainSettings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` - -MultipleInstances IgnoreNew + -MultipleInstances IgnoreNew ` + -ExecutionTimeLimit ([TimeSpan]::Zero) Register-ScheduledTask ` -TaskName $MainTaskName ` diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index 68a6183..a36eb23 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -89,7 +89,7 @@ $script:SkipLogDetailLimit = 15 # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "2.0.23-SAC" +$ScriptVersion = "2.0.24-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" @@ -480,11 +480,49 @@ function Invoke-RdpMonitorReloadSettings { return $false } +function Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimited { + param( + [Parameter(Mandatory = $true)][string]$TaskName + ) + try { + $limit = (Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop | Select-Object -First 1).Settings.ExecutionTimeLimit + if ($null -eq $limit) { return $false } + if ($limit.Ticks -le 0) { return $true } + # Некоторые сборки Windows возвращают «безлимит» как очень большой интервал. + if ($limit.TotalDays -ge 999) { return $true } + return $false + } catch { + return $false + } +} + +function Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel { + param([Parameter(Mandatory = $true)][string]$TaskName) + try { + $limit = (Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop | Select-Object -First 1).Settings.ExecutionTimeLimit + if ($null -eq $limit) { return '(null)' } + if ($limit.Ticks -le 0) { return 'PT0S (без лимита)' } + return $limit.ToString() + } catch { + return '(задача не найдена)' + } +} + +function New-RdpMonitorMainScheduledTaskSettings { + # ExecutionTimeLimit по умолчанию у Register-ScheduledTask = PT72H; монитор должен работать месяцами. + New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries ` + -DontStopIfGoingOnBatteries ` + -StartWhenAvailable ` + -ExecutionTimeLimit ([TimeSpan]::Zero) +} + function Test-RdpMonitorScheduledTaskMatches { param( [Parameter(Mandatory = $true)][string]$TaskName, [Parameter(Mandatory = $true)][string]$ExpectedExe, - [Parameter(Mandatory = $true)][string]$ExpectedArguments + [Parameter(Mandatory = $true)][string]$ExpectedArguments, + [switch]$RequireUnlimitedExecutionTime ) try { $t = Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop | Select-Object -First 1 @@ -492,7 +530,13 @@ function Test-RdpMonitorScheduledTaskMatches { if ($null -eq $a) { return $false } $exe = [string]$a.Execute $arg = [string]$a.Arguments - return (($exe.Trim() -eq $ExpectedExe.Trim()) -and ($arg.Trim() -eq $ExpectedArguments.Trim())) + if (($exe.Trim() -ne $ExpectedExe.Trim()) -or ($arg.Trim() -ne $ExpectedArguments.Trim())) { + return $false + } + if ($RequireUnlimitedExecutionTime -and -not (Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimited -TaskName $TaskName)) { + return $false + } + return $true } catch { return $false } @@ -501,7 +545,7 @@ function Test-RdpMonitorScheduledTaskMatches { function Register-RdpMonitorScheduledTasksCore { param([switch]$SkipImmediateMainRun) - Write-Log "Register-RdpMonitorScheduledTasksCore: ветка v$ScriptVersion (watchdog через schtasks /SC MINUTE, без CIM RepetitionInterval)." + Write-Log "Register-RdpMonitorScheduledTasksCore: v$ScriptVersion (main ExecutionTimeLimit=PT0S; watchdog schtasks /SC MINUTE)." $psExe = Get-RdpMonitorPowerShellExe $canonicalScript = [System.IO.Path]::GetFullPath((Join-Path $script:InstallRoot $script:CanonicalScriptName)) if (-not (Test-Path -LiteralPath $canonicalScript)) { @@ -515,11 +559,11 @@ function Register-RdpMonitorScheduledTasksCore { $actionMain = New-ScheduledTaskAction -Execute $psExe -Argument $argMain $triggerBoot = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable + $settings = New-RdpMonitorMainScheduledTaskSettings Register-ScheduledTask -TaskName $script:ScheduledTaskNameMain -Action $actionMain -Trigger $triggerBoot ` -Principal $principal -Settings $settings -Force | Out-Null - Write-Log "Задача планировщика: $($script:ScheduledTaskNameMain) (запуск при старте ОС)." + Write-Log "Задача планировщика: $($script:ScheduledTaskNameMain) (запуск при старте ОС, ExecutionTimeLimit=PT0S)." # Watchdog только через schtasks. /Delete при отсутствии задачи пишет в stderr — при $ErrorActionPreference Stop раньше рвал скрипт. $schtasksExe = Join-Path $env:SystemRoot 'System32\schtasks.exe' @@ -583,15 +627,24 @@ function Ensure-RdpMonitorScheduledTasks { $argMain = "-NoProfile -ExecutionPolicy Bypass -File `"$canonicalScript`"" $argWd = "-NoProfile -ExecutionPolicy Bypass -File `"$canonicalScript`" -Watchdog" - $needMain = -not (Test-RdpMonitorScheduledTaskMatches -TaskName $script:ScheduledTaskNameMain -ExpectedExe $psExe -ExpectedArguments $argMain) + $mainPathsOk = Test-RdpMonitorScheduledTaskMatches -TaskName $script:ScheduledTaskNameMain -ExpectedExe $psExe -ExpectedArguments $argMain + $mainLimitOk = Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimited -TaskName $script:ScheduledTaskNameMain + $needMain = (-not $mainPathsOk) -or (-not $mainLimitOk) $needWd = -not (Test-RdpMonitorScheduledTaskMatches -TaskName $script:ScheduledTaskNameWatchdog -ExpectedExe $psExe -ExpectedArguments $argWd) if (-not $needMain -and -not $needWd) { - Write-Log "Задачи планировщика ($($script:ScheduledTaskNameMain), $($script:ScheduledTaskNameWatchdog)) соответствуют каноническим путям." + Write-Log "Задачи планировщика ($($script:ScheduledTaskNameMain), $($script:ScheduledTaskNameWatchdog)) соответствуют каноническим путям и лимитам." return } - if ($needMain) { Write-Log "Требуется обновить или создать задачу: $($script:ScheduledTaskNameMain)" } + if ($needMain) { + if (-not $mainPathsOk) { + Write-Log "Требуется обновить или создать задачу: $($script:ScheduledTaskNameMain)" + } elseif (-not $mainLimitOk) { + $limitLabel = Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel -TaskName $script:ScheduledTaskNameMain + Write-Log "Требуется обновить задачу: $($script:ScheduledTaskNameMain) (ExecutionTimeLimit=$limitLabel; нужен PT0S — иначе Task Scheduler останавливает монитор через 72 ч)." + } + } if ($needWd) { Write-Log "Требуется обновить или создать задачу: $($script:ScheduledTaskNameWatchdog)" } Register-RdpMonitorScheduledTasksCore diff --git a/version.txt b/version.txt index 91a10ae..f4f7291 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.23-SAC +2.0.24-SAC