diff --git a/Deploy-LoginMonitor.ps1 b/Deploy-LoginMonitor.ps1 index 422912e..bb0d522 100644 --- a/Deploy-LoginMonitor.ps1 +++ b/Deploy-LoginMonitor.ps1 @@ -58,7 +58,7 @@ $DeployLogPath = Join-Path $InstallRoot "Logs\deploy.log" $ScriptName = "Login_Monitor.ps1" $SacClientName = "Sac-Client.ps1" $VersionFileName = "version.txt" -$DeployBundleFiles = @($ScriptName, $SacClientName, 'Diagnose-RdpLoginMonitor.ps1') +$DeployBundleFiles = @($ScriptName, $SacClientName, 'Diagnose-RdpLoginMonitor.ps1', 'RdpMonitor-TaskQuery.ps1') $PsExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" $Utf8Bom = New-Object System.Text.UTF8Encoding $true @@ -971,35 +971,60 @@ function Stop-RdpLoginMonitorMainProcesses { } } +function Import-RdpMonitorDeployTaskQueryModule { + param([string]$ShareRoot = '') + + $candidates = [System.Collections.Generic.List[string]]::new() + $candidates.Add((Join-Path $InstallRoot 'RdpMonitor-TaskQuery.ps1')) | Out-Null + if (-not [string]::IsNullOrWhiteSpace($ShareRoot)) { + $candidates.Add((Join-Path $ShareRoot 'RdpMonitor-TaskQuery.ps1')) | Out-Null + } else { + try { + $candidates.Add((Join-Path (Resolve-SourceShareRoot) 'RdpMonitor-TaskQuery.ps1')) | Out-Null + } catch { } + } + + foreach ($candidate in @($candidates)) { + if (Test-Path -LiteralPath $candidate) { + . $candidate + return $true + } + } + return $false +} + 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 + if (-not (Get-Command Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit -ErrorAction SilentlyContinue)) { + if (-not (Import-RdpMonitorDeployTaskQueryModule)) { return $true } } + return (Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit -TaskName $TaskName) } 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 '(задача не найдена)' + if (-not (Get-Command Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel -ErrorAction SilentlyContinue)) { + if (-not (Import-RdpMonitorDeployTaskQueryModule)) { return '(модуль TaskQuery недоступен)' } } + return (Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel -TaskName $TaskName) } function Write-RdpMonitorDeployScheduledTaskVerification { param([string]$TaskName = 'RDP-Login-Monitor') - if (Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime -TaskName $TaskName) { - $label = Get-RdpMonitorDeployMainTaskExecutionTimeLimitLabel -TaskName $TaskName + if (-not (Get-Command Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit -ErrorAction SilentlyContinue)) { + if (-not (Import-RdpMonitorDeployTaskQueryModule)) { + Write-DeployLog "ПРЕДУПРЕЖДЕНИЕ: RdpMonitor-TaskQuery.ps1 не найден — проверка ExecutionTimeLimit пропущена." + return $false + } + } + + $resolved = Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved -TaskName $TaskName + if ($resolved.Source -eq 'schtasks-xml') { + Write-DeployLog "Задача ${TaskName}: ExecutionTimeLimit проверен через schtasks /XML (Get-ScheduledTask недоступен)." + } + + if (Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit -TaskName $TaskName) { + $label = Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel -TaskName $TaskName Write-DeployLog "ПРЕДУПРЕЖДЕНИЕ: $TaskName ExecutionTimeLimit=$label — ожидался PT0S (без лимита). Проверьте InstallTasks и права администратора." return $false } @@ -1007,6 +1032,42 @@ function Write-RdpMonitorDeployScheduledTaskVerification { return $true } +function Invoke-RdpMonitorDeploySacVersionNotice { + if (-not (Test-Path -LiteralPath $LocalScript)) { + Write-DeployLog "SAC deploy notice: Login_Monitor.ps1 не найден — пропуск." + return + } + $settingsLocal = Join-Path $InstallRoot 'login_monitor.settings.ps1' + if (-not (Test-Path -LiteralPath $settingsLocal)) { + Write-DeployLog "SAC deploy notice: login_monitor.settings.ps1 отсутствует — пропуск." + return + } + + $outLog = Join-Path $InstallRoot 'Logs\deploy_sac_notice_stdout.log' + $errLog = Join-Path $InstallRoot 'Logs\deploy_sac_notice_stderr.log' + $noticeArgs = @( + '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript, '-SendDeploySacNotice' + ) + $p = Start-Process -FilePath $PsExe -ArgumentList $noticeArgs -Wait -PassThru -WindowStyle Hidden ` + -RedirectStandardOutput $outLog -RedirectStandardError $errLog + if ($p.ExitCode -eq 0) { + Write-DeployLog "SAC: версия агента передана сразу после деплоя (SendDeploySacNotice)." + return + } + + Write-DeployLog "Предупреждение: SendDeploySacNotice завершился с кодом $($p.ExitCode) — SAC обновится при старте монитора (agent.lifecycle)." + foreach ($pair in @(@($outLog, 'stdout'), @($errLog, 'stderr'))) { + $lp = $pair[0] + $lbl = $pair[1] + if (Test-Path -LiteralPath $lp) { + $tail = Get-Content -LiteralPath $lp -Tail 20 -ErrorAction SilentlyContinue + if ($tail) { + Write-DeployLog "SendDeploySacNotice $lbl (хвост): $($tail -join ' | ')" + } + } + } +} + # --- main --- try { $shareRoot = Resolve-SourceShareRoot @@ -1040,6 +1101,8 @@ try { Write-DeployLog "ОШИБКА: на шаре отсутствует $SacClientName — выполните update-rdp-monitor.ps1 на сервере публикации." } + [void](Import-RdpMonitorDeployTaskQueryModule -ShareRoot $shareRoot) + $needsSettingsBootstrap = Test-RdpMonitorSettingsNeedsSacBootstrap -SettingsPath $settingsLocal $needsDisplayNameHint = Test-RdpMonitorSettingsNeedsServerDisplayNameHint -SettingsPath $settingsLocal $needsDailyReportHint = Test-RdpMonitorSettingsNeedsDailyReportHint -SettingsPath $settingsLocal @@ -1171,6 +1234,8 @@ try { [System.IO.File]::WriteAllText($DeployUpdateMarkerPath, "$updMarker`r`n", $Utf8Bom) Write-DeployLog "Записана метка обновления: $DeployUpdateMarkerPath (Version=$shareVerRaw; UpdatedAt=$updStamp)." + Invoke-RdpMonitorDeploySacVersionNotice + if (-not $SkipStartMonitorAfterUpdate) { $canonical = [System.IO.Path]::GetFullPath($LocalScript) if (Test-RdpMonitorMainProcessRunning -CanonicalScript $canonical) { diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index e0c5ef3..8c4e439 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -38,6 +38,7 @@ param( [switch]$SkipImmediateMainRun, [switch]$SkipScheduledTaskMaintenance, [switch]$CheckSac, + [switch]$SendDeploySacNotice, [switch]$RequestRestart, [switch]$Recycle ) @@ -89,7 +90,7 @@ $script:SkipLogDetailLimit = 15 # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "2.0.27-SAC" +$ScriptVersion = "2.0.28-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" @@ -480,31 +481,13 @@ 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 '(задача не найдена)' +foreach ($taskQueryPath in @( + (Join-Path $PSScriptRoot 'RdpMonitor-TaskQuery.ps1'), + (Join-Path $script:InstallRoot 'RdpMonitor-TaskQuery.ps1') + )) { + if (Test-Path -LiteralPath $taskQueryPath) { + . $taskQueryPath + break } } @@ -537,9 +520,18 @@ function Test-RdpMonitorScheduledTaskMatches { return $false } return $true - } catch { + } catch { } + + if (-not (Get-Command Test-RdpMonitorScheduledTaskActionMatchesViaSchtasks -ErrorAction SilentlyContinue)) { return $false } + if (-not (Test-RdpMonitorScheduledTaskActionMatchesViaSchtasks -TaskName $TaskName -ExpectedExe $ExpectedExe -ExpectedArguments $ExpectedArguments)) { + return $false + } + if ($RequireUnlimitedExecutionTime -and -not (Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimited -TaskName $TaskName)) { + return $false + } + return $true } function Register-RdpMonitorScheduledTasksCore { @@ -647,7 +639,7 @@ function Ensure-RdpMonitorScheduledTasks { } if ($needWd) { Write-Log "Требуется обновить или создать задачу: $($script:ScheduledTaskNameWatchdog)" } - Register-RdpMonitorScheduledTasksCore + Register-RdpMonitorScheduledTasksCore -SkipImmediateMainRun } function Start-RdpMonitorWatchdogMain { @@ -1143,6 +1135,61 @@ if ($CheckSac) { exit $code } +if ($SendDeploySacNotice) { + if (-not $script:SacClientLoaded) { + Write-Log "SendDeploySacNotice: Sac-Client.ps1 не найден." + exit 1 + } + if ((Get-SacNormalizedMode) -eq 'off') { + Write-Log "SendDeploySacNotice: UseSAC=off — уведомление SAC пропущено." + exit 0 + } + if (-not (Test-SacConfigured)) { + Write-Log "SendDeploySacNotice: SAC не настроен (SacUrl / SacApiKey)." + exit 1 + } + try { + [System.Net.ServicePointManager]::SecurityProtocol = + [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 + } catch { } + + $hostLabel = $env:COMPUTERNAME + if (-not [string]::IsNullOrWhiteSpace($ServerDisplayName)) { + $hostLabel = [string]$ServerDisplayName + } + $updatedAt = $null + if (Test-Path -LiteralPath $DeployUpdateMarkerFile) { + try { + foreach ($ln in (Get-Content -LiteralPath $DeployUpdateMarkerFile -ErrorAction Stop)) { + if ($ln -match '^\s*UpdatedAt\s*=\s*(.+)\s*$') { + $updatedAt = $Matches[1].Trim() + break + } + } + } catch { } + } + + $summary = "RDP login monitor deployed, version $ScriptVersion on $hostLabel" + if (-not [string]::IsNullOrWhiteSpace($updatedAt)) { + $summary += " ($updatedAt)" + } + $details = @{ + lifecycle = 'updated' + trigger = 'deploy' + version = [string]$ScriptVersion + } + $ok = Send-SacEvent -EventType 'agent.lifecycle' -Severity 'info' ` + -Title "RDP agent updated to $ScriptVersion" ` + -Summary $summary ` + -Details $details + if ($ok) { + Write-Log "SendDeploySacNotice: SAC agent.lifecycle отправлен (version=$ScriptVersion)." + exit 0 + } + Write-Log "SendDeploySacNotice: не удалось отправить в SAC (см. sac_client.log / spool)." + exit 1 +} + Invoke-RdpMonitorProcessMigrationAndRelaunch Lock-RdpMonitorSingleInstance Ensure-RdpMonitorScheduledTasks diff --git a/RdpMonitor-TaskQuery.ps1 b/RdpMonitor-TaskQuery.ps1 new file mode 100644 index 0000000..bdb1904 --- /dev/null +++ b/RdpMonitor-TaskQuery.ps1 @@ -0,0 +1,176 @@ +<# +.SYNOPSIS + Запрос задач планировщика RDP-login-monitor через schtasks /Query /XML (fallback для Get-ScheduledTask). +#> + +function Get-RdpMonitorSchtasksExe { + return Join-Path $env:SystemRoot 'System32\schtasks.exe' +} + +function Get-RdpMonitorScheduledTaskXmlDocument { + param( + [Parameter(Mandatory = $true)][string]$TaskName + ) + + $exe = Get-RdpMonitorSchtasksExe + $prevEa = $ErrorActionPreference + try { + $ErrorActionPreference = 'SilentlyContinue' + $raw = & $exe /Query /TN $TaskName /XML 2>&1 + if ($LASTEXITCODE -ne 0) { return $null } + $text = ($raw | Out-String).Trim() + if ([string]::IsNullOrWhiteSpace($text)) { return $null } + if ($text -notmatch '(?s)