From 5e9939724cdb33bbd5f094d3152529ec0f025852 Mon Sep 17 00:00:00 2001 From: PTah Date: Thu, 11 Jun 2026 15:15:42 +1000 Subject: [PATCH] fix: deploy TaskQuery script scope and internal autotests (2.0.32-SAC) Publish TaskQuery helpers to script scope after import so deploy checks work from nested callers; add kalinamall-only smoke tests and RDP_DEPLOY_FUNCTIONS_ONLY hook. Co-authored-by: Cursor --- Deploy-LoginMonitor.ps1 | 40 +++++++++++-- Login_Monitor.ps1 | 2 +- tools/Push-KalinamallOnly.ps1 | 20 +++++++ tools/Run-RdpMonitorTests.ps1 | 45 ++++++++++++++ tools/tests/INTERNAL.md | 24 ++++++++ tools/tests/Test-DeployTaskLimit.ps1 | 22 +++++++ tools/tests/Test-ScriptSyntaxAll.ps1 | 22 +++++++ tools/tests/Test-SendDeploySacNotice.ps1 | 75 ++++++++++++++++++++++++ tools/tests/Test-TaskQueryModule.ps1 | 15 +++++ tools/tests/_DeployFunctionsLoader.ps1 | 16 +++++ tools/tests/_TestLib.ps1 | 39 ++++++++++++ version.txt | 2 +- 12 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 tools/Push-KalinamallOnly.ps1 create mode 100644 tools/Run-RdpMonitorTests.ps1 create mode 100644 tools/tests/INTERNAL.md create mode 100644 tools/tests/Test-DeployTaskLimit.ps1 create mode 100644 tools/tests/Test-ScriptSyntaxAll.ps1 create mode 100644 tools/tests/Test-SendDeploySacNotice.ps1 create mode 100644 tools/tests/Test-TaskQueryModule.ps1 create mode 100644 tools/tests/_DeployFunctionsLoader.ps1 create mode 100644 tools/tests/_TestLib.ps1 diff --git a/Deploy-LoginMonitor.ps1 b/Deploy-LoginMonitor.ps1 index 09c68e5..f2d9486 100644 --- a/Deploy-LoginMonitor.ps1 +++ b/Deploy-LoginMonitor.ps1 @@ -997,6 +997,28 @@ function Test-RdpMonitorDeployTaskQueryReady { return [bool](Get-Command Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved -ErrorAction SilentlyContinue) } +function Publish-RdpMonitorDeployTaskQueryFunctionsToScriptScope { + foreach ($name in @( + 'Get-RdpMonitorSchtasksExe', + 'Get-RdpMonitorScheduledTaskXmlDocument', + 'Test-RdpMonitorScheduledTaskExistsViaSchtasks', + 'Convert-RdpMonitorScheduledTaskExecutionTimeLimitText', + 'Get-RdpMonitorScheduledTaskExecutionTimeLimitFromDocument', + 'Get-RdpMonitorScheduledTaskActionFromDocument', + 'Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimitedValue', + 'Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved', + 'Test-RdpMonitorScheduledTaskExecutionTimeLimitUnlimited', + 'Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit', + 'Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel', + 'Test-RdpMonitorScheduledTaskActionMatchesViaSchtasks' + )) { + $cmd = Get-Command -Name $name -ErrorAction SilentlyContinue + if ($null -ne $cmd -and $cmd.CommandType -eq 'Function') { + Set-Item -Path "function:script:$name" -Value $cmd.ScriptBlock + } + } +} + function Import-RdpMonitorDeployTaskQueryModule { param([string]$ShareRoot = '') @@ -1020,6 +1042,7 @@ function Import-RdpMonitorDeployTaskQueryModule { continue } if (Test-RdpMonitorDeployTaskQueryReady) { + Publish-RdpMonitorDeployTaskQueryFunctionsToScriptScope return $true } Write-DeployLog "Предупреждение: $candidate загружен, но функции TaskQuery недоступны — пробуем следующий источник." @@ -1033,13 +1056,20 @@ function Initialize-RdpMonitorDeployTaskQuery { if (Test-RdpMonitorDeployTaskQueryReady) { return $true } + + $loaded = $false if (Import-RdpMonitorDeployTaskQueryModule -ShareRoot $ShareRoot) { - return $true + $loaded = $true + } else { + Write-DeployLog "RdpMonitor-TaskQuery.ps1 недоступен — deploy использует встроенную проверку schtasks /XML." + . $script:RdpMonitorDeployTaskQueryInlineScript + $loaded = (Test-RdpMonitorDeployTaskQueryReady) } - Write-DeployLog "RdpMonitor-TaskQuery.ps1 недоступен — deploy использует встроенную проверку schtasks /XML." - . $script:RdpMonitorDeployTaskQueryInlineScript - return (Test-RdpMonitorDeployTaskQueryReady) + if ($loaded) { + Publish-RdpMonitorDeployTaskQueryFunctionsToScriptScope + } + return $loaded } $script:RdpMonitorDeployTaskQueryInlineScript = { @@ -1212,6 +1242,8 @@ function Invoke-RdpMonitorDeploySacVersionNotice { } # --- main --- +if ($env:RDP_DEPLOY_FUNCTIONS_ONLY -eq '1') { return } + try { $shareRoot = Resolve-SourceShareRoot $sourceScript = Join-Path $shareRoot $ScriptName diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1 index aa5bbb6..6f1e9cf 100644 --- a/Login_Monitor.ps1 +++ b/Login_Monitor.ps1 @@ -90,7 +90,7 @@ $script:SkipLogDetailLimit = 15 # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). -$ScriptVersion = "2.0.31-SAC" +$ScriptVersion = "2.0.32-SAC" # Логи (все под InstallRoot) $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" diff --git a/tools/Push-KalinamallOnly.ps1 b/tools/Push-KalinamallOnly.ps1 new file mode 100644 index 0000000..04420fe --- /dev/null +++ b/tools/Push-KalinamallOnly.ps1 @@ -0,0 +1,20 @@ +<# +.SYNOPSIS + Push main to kalinamall only (skip public GitHub origin). +#> +[CmdletBinding()] +param( + [string]$Branch = 'main' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = Split-Path -Parent $PSScriptRoot +Push-Location -LiteralPath $repoRoot +try { + git push kalinamall $Branch + Write-Host "Pushed to kalinamall/$Branch (origin/GitHub skipped intentionally)." +} finally { + Pop-Location +} diff --git a/tools/Run-RdpMonitorTests.ps1 b/tools/Run-RdpMonitorTests.ps1 new file mode 100644 index 0000000..bf60066 --- /dev/null +++ b/tools/Run-RdpMonitorTests.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS + Smoke/autotests for RDP-login-monitor deploy and SAC paths (kalinamall internal). +.EXAMPLE + powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\Run-RdpMonitorTests.ps1 +#> +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$testsDir = Join-Path $PSScriptRoot 'tests' +$suites = @( + 'Test-ScriptSyntaxAll.ps1', + 'Test-TaskQueryModule.ps1', + 'Test-DeployTaskLimit.ps1', + 'Test-SendDeploySacNotice.ps1' +) + +Write-Host '=== RDP-login-monitor autotests ===' +$failed = 0 +foreach ($suite in $suites) { + $path = Join-Path $testsDir $suite + if (-not (Test-Path -LiteralPath $path)) { + Write-Host "FAIL: missing suite $path" + $failed++ + continue + } + Write-Host "--- $suite ---" + try { + & $path + } catch { + Write-Host "SUITE FAILED: $suite - $($_.Exception.Message)" + $failed++ + } +} + +if ($failed -gt 0) { + Write-Host ('=== FAILED ({0} suite(s)) ===' -f $failed) + exit 1 +} + +Write-Host '=== ALL PASSED ===' +exit 0 diff --git a/tools/tests/INTERNAL.md b/tools/tests/INTERNAL.md new file mode 100644 index 0000000..ccc2795 --- /dev/null +++ b/tools/tests/INTERNAL.md @@ -0,0 +1,24 @@ +# Autotests (kalinamall only) + +Каталог `tools/tests/` и `tools/Run-RdpMonitorTests.ps1` — **внутренние** smoke-тесты. + +## Запуск + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\Run-RdpMonitorTests.ps1 +``` + +Перед push правок `Deploy-LoginMonitor.ps1` / `Login_Monitor.ps1` / `RdpMonitor-TaskQuery.ps1`. + +## Git remotes + +- **kalinamall** — публиковать можно (`git push kalinamall main`) +- **GitHub (origin)** — **не пушить** коммиты с этими тестами на публичный remote + +После работы с тестами: + +```powershell +git push kalinamall main +``` + +На `origin` (GitHub) не выполнять push, если в коммите есть `tools/tests/` или `tools/Run-RdpMonitorTests.ps1`. diff --git a/tools/tests/Test-DeployTaskLimit.ps1 b/tools/tests/Test-DeployTaskLimit.ps1 new file mode 100644 index 0000000..235a4fd --- /dev/null +++ b/tools/tests/Test-DeployTaskLimit.ps1 @@ -0,0 +1,22 @@ +. (Join-Path $PSScriptRoot '_TestLib.ps1') +. (Join-Path $PSScriptRoot '_DeployFunctionsLoader.ps1') +$repo = Get-RdpMonitorRepoRoot + +Invoke-RdpMonitorTestCase -Name 'Deploy functions load (RDP_DEPLOY_FUNCTIONS_ONLY)' -Script { + Assert-CommandExists -Name 'Initialize-RdpMonitorDeployTaskQuery' + Assert-CommandExists -Name 'Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime' + Assert-CommandExists -Name 'Write-RdpMonitorDeployScheduledTaskVerification' + Assert-CommandExists -Name 'Get-RdpMonitorDeployTaskExecutionTimeLimitLabelFromResolved' +} + +Invoke-RdpMonitorTestCase -Name 'Deploy pre-check task limit (no throw on early path)' -Script { + $needsFix = Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime -ShareRoot $repo -TaskName 'RDP-Login-Monitor-UnitTest-Missing' + Assert-True -Condition ($needsFix -is [bool]) -Message 'Test-RdpMonitorDeployMainTaskNeedsUnlimitedExecutionTime must return bool' +} + +Invoke-RdpMonitorTestCase -Name 'Deploy verification after Initialize (no missing command)' -Script { + [void](Initialize-RdpMonitorDeployTaskQuery -ShareRoot $repo) + Assert-CommandExists -Name 'Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved' + $ok = Write-RdpMonitorDeployScheduledTaskVerification -ShareRoot $repo -TaskName 'RDP-Login-Monitor-UnitTest-Missing' + Assert-True -Condition ($ok -is [bool]) -Message 'Write-RdpMonitorDeployScheduledTaskVerification must return bool' +} diff --git a/tools/tests/Test-ScriptSyntaxAll.ps1 b/tools/tests/Test-ScriptSyntaxAll.ps1 new file mode 100644 index 0000000..9c75e8a --- /dev/null +++ b/tools/tests/Test-ScriptSyntaxAll.ps1 @@ -0,0 +1,22 @@ +. (Join-Path $PSScriptRoot '_TestLib.ps1') + +$repo = Get-RdpMonitorRepoRoot +$files = @( + 'Deploy-LoginMonitor.ps1', + 'Login_Monitor.ps1', + 'Sac-Client.ps1', + 'RdpMonitor-TaskQuery.ps1', + 'update-rdp-monitor.ps1' +) + +foreach ($rel in $files) { + $path = Join-Path $repo $rel + Invoke-RdpMonitorTestCase -Name "Syntax: $rel" -Script { + Assert-True -Condition (Test-Path -LiteralPath $path) -Message "Missing $path" + $errs = $null + [void][System.Management.Automation.Language.Parser]::ParseFile($path, [ref]$null, [ref]$errs) + if ($errs -and $errs.Count -gt 0) { + throw ($errs | ForEach-Object { $_.ToString() } | Out-String) + } + } +} diff --git a/tools/tests/Test-SendDeploySacNotice.ps1 b/tools/tests/Test-SendDeploySacNotice.ps1 new file mode 100644 index 0000000..80ad1bc --- /dev/null +++ b/tools/tests/Test-SendDeploySacNotice.ps1 @@ -0,0 +1,75 @@ +. (Join-Path $PSScriptRoot '_TestLib.ps1') + +function Test-RdpMonitorDeploySacHostLabelStrictMode { + Set-StrictMode -Version Latest + $hostLabel = [string]$env:COMPUTERNAME + if (Get-Variable -Name ServerDisplayName -Scope Script -ErrorAction SilentlyContinue) { + $sdn = (Get-Variable -Name ServerDisplayName -Scope Script -ValueOnly) + if ($null -ne $sdn -and -not [string]::IsNullOrWhiteSpace([string]$sdn)) { + $hostLabel = [string]$sdn.Trim() + } + } + return $hostLabel +} + +Invoke-RdpMonitorTestCase -Name 'Deploy SAC host label without ServerDisplayName (StrictMode)' -Script { + $label = Test-RdpMonitorDeploySacHostLabelStrictMode + Assert-True -Condition (-not [string]::IsNullOrWhiteSpace($label)) -Message 'Host label must not be empty' + Assert-True -Condition ($label -eq [string]$env:COMPUTERNAME) -Message 'Expected COMPUTERNAME when ServerDisplayName unset' +} + +Invoke-RdpMonitorTestCase -Name 'Deploy SAC host label with ServerDisplayName (StrictMode)' -Script { + $script:ServerDisplayName = 'Test-Server-Display' + try { + $label = Test-RdpMonitorDeploySacHostLabelStrictMode + Assert-True -Condition ($label -eq 'Test-Server-Display') -Message 'Expected ServerDisplayName value' + } finally { + Remove-Variable -Name ServerDisplayName -Scope Script -ErrorAction SilentlyContinue + } +} + +Invoke-RdpMonitorTestCase -Name 'Login_Monitor -SendDeploySacNotice does not fail on StrictMode host label' -Script { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($identity) + if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host 'SKIP: requires elevated PowerShell (administrator)' + return + } + + $repo = Get-RdpMonitorRepoRoot + $tempRoot = Join-Path $env:TEMP ("rdp-monitor-test-{0}" -f [guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $tempRoot -Force | Out-Null + New-Item -ItemType Directory -Path (Join-Path $tempRoot 'Logs') -Force | Out-Null + + $settings = @' +$UseSAC = 'off' +'@ + $settingsPath = Join-Path $tempRoot 'login_monitor.settings.ps1' + [System.IO.File]::WriteAllText($settingsPath, $settings, (New-Object System.Text.UTF8Encoding $true)) + + Copy-Item -LiteralPath (Join-Path $repo 'Login_Monitor.ps1') -Destination (Join-Path $tempRoot 'Login_Monitor.ps1') -Force + Copy-Item -LiteralPath (Join-Path $repo 'Sac-Client.ps1') -Destination (Join-Path $tempRoot 'Sac-Client.ps1') -Force + + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" + $psi.Arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}" -SendDeploySacNotice' -f (Join-Path $tempRoot 'Login_Monitor.ps1') + $psi.WorkingDirectory = $tempRoot + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + + $proc = [System.Diagnostics.Process]::Start($psi) + $stdout = $proc.StandardOutput.ReadToEnd() + $stderr = $proc.StandardError.ReadToEnd() + $proc.WaitForExit() + + try { + if ($stderr -match 'ServerDisplayName|VariableIsUndefined') { + throw "SendDeploySacNotice stderr contains StrictMode error: $stderr" + } + Assert-True -Condition ($proc.ExitCode -eq 0) -Message "Expected exit 0 with UseSAC=off, got $($proc.ExitCode); stderr=$stderr stdout=$stdout" + } finally { + Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } +} diff --git a/tools/tests/Test-TaskQueryModule.ps1 b/tools/tests/Test-TaskQueryModule.ps1 new file mode 100644 index 0000000..f4416eb --- /dev/null +++ b/tools/tests/Test-TaskQueryModule.ps1 @@ -0,0 +1,15 @@ +. (Join-Path $PSScriptRoot '_TestLib.ps1') + +$repo = Get-RdpMonitorRepoRoot +$taskQuery = Join-Path $repo 'RdpMonitor-TaskQuery.ps1' + +Invoke-RdpMonitorTestCase -Name 'TaskQuery file exists' -Script { + Assert-True -Condition (Test-Path -LiteralPath $taskQuery) -Message "Missing $taskQuery" +} + +Invoke-RdpMonitorTestCase -Name 'TaskQuery dot-source defines core commands' -Script { + . $taskQuery + Assert-CommandExists -Name 'Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved' + Assert-CommandExists -Name 'Test-RdpMonitorScheduledTaskNeedsUnlimitedExecutionTimeLimit' + Assert-CommandExists -Name 'Get-RdpMonitorScheduledTaskExecutionTimeLimitLabel' +} diff --git a/tools/tests/_DeployFunctionsLoader.ps1 b/tools/tests/_DeployFunctionsLoader.ps1 new file mode 100644 index 0000000..767712e --- /dev/null +++ b/tools/tests/_DeployFunctionsLoader.ps1 @@ -0,0 +1,16 @@ +# Dot-source from a test .ps1 at script scope after _TestLib.ps1 (not from a function/scriptblock). +if ($script:RdpMonitorDeployFunctionsLoaded) { return } + +$repo = Get-RdpMonitorRepoRoot +$prev = $env:RDP_DEPLOY_FUNCTIONS_ONLY +$env:RDP_DEPLOY_FUNCTIONS_ONLY = '1' +try { + . (Join-Path $repo 'Deploy-LoginMonitor.ps1') +} finally { + if ($null -eq $prev) { + Remove-Item Env:RDP_DEPLOY_FUNCTIONS_ONLY -ErrorAction SilentlyContinue + } else { + $env:RDP_DEPLOY_FUNCTIONS_ONLY = $prev + } +} +$script:RdpMonitorDeployFunctionsLoaded = $true diff --git a/tools/tests/_TestLib.ps1 b/tools/tests/_TestLib.ps1 new file mode 100644 index 0000000..fa325f8 --- /dev/null +++ b/tools/tests/_TestLib.ps1 @@ -0,0 +1,39 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$script:RdpMonitorDeployFunctionsLoaded = $false + +function Get-RdpMonitorRepoRoot { return (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +} + +function Assert-True { + param( + [Parameter(Mandatory = $true)][bool]$Condition, + [Parameter(Mandatory = $true)][string]$Message + ) + if (-not $Condition) { + throw "FAIL: $Message" + } +} + +function Assert-CommandExists { + param( + [Parameter(Mandatory = $true)][string]$Name + ) + $cmd = Get-Command -Name $Name -ErrorAction SilentlyContinue + Assert-True -Condition ($null -ne $cmd) -Message "Command not found: $Name" +} + +function Invoke-RdpMonitorTestCase { + param( + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][scriptblock]$Script + ) + try { + & $Script + Write-Host "PASS: $Name" + } catch { + Write-Host "FAIL: $Name - $($_.Exception.Message)" + throw + } +} diff --git a/version.txt b/version.txt index 0744909..684bc8e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.31-SAC +2.0.32-SAC