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 <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-06-11 15:15:42 +10:00
parent 31ebd5d653
commit 5e9939724c
12 changed files with 316 additions and 6 deletions
+36 -4
View File
@@ -997,6 +997,28 @@ function Test-RdpMonitorDeployTaskQueryReady {
return [bool](Get-Command Get-RdpMonitorScheduledTaskExecutionTimeLimitResolved -ErrorAction SilentlyContinue) 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 { function Import-RdpMonitorDeployTaskQueryModule {
param([string]$ShareRoot = '') param([string]$ShareRoot = '')
@@ -1020,6 +1042,7 @@ function Import-RdpMonitorDeployTaskQueryModule {
continue continue
} }
if (Test-RdpMonitorDeployTaskQueryReady) { if (Test-RdpMonitorDeployTaskQueryReady) {
Publish-RdpMonitorDeployTaskQueryFunctionsToScriptScope
return $true return $true
} }
Write-DeployLog "Предупреждение: $candidate загружен, но функции TaskQuery недоступны — пробуем следующий источник." Write-DeployLog "Предупреждение: $candidate загружен, но функции TaskQuery недоступны — пробуем следующий источник."
@@ -1033,13 +1056,20 @@ function Initialize-RdpMonitorDeployTaskQuery {
if (Test-RdpMonitorDeployTaskQueryReady) { if (Test-RdpMonitorDeployTaskQueryReady) {
return $true return $true
} }
if (Import-RdpMonitorDeployTaskQueryModule -ShareRoot $ShareRoot) {
return $true
}
$loaded = $false
if (Import-RdpMonitorDeployTaskQueryModule -ShareRoot $ShareRoot) {
$loaded = $true
} else {
Write-DeployLog "RdpMonitor-TaskQuery.ps1 недоступен — deploy использует встроенную проверку schtasks /XML." Write-DeployLog "RdpMonitor-TaskQuery.ps1 недоступен — deploy использует встроенную проверку schtasks /XML."
. $script:RdpMonitorDeployTaskQueryInlineScript . $script:RdpMonitorDeployTaskQueryInlineScript
return (Test-RdpMonitorDeployTaskQueryReady) $loaded = (Test-RdpMonitorDeployTaskQueryReady)
}
if ($loaded) {
Publish-RdpMonitorDeployTaskQueryFunctionsToScriptScope
}
return $loaded
} }
$script:RdpMonitorDeployTaskQueryInlineScript = { $script:RdpMonitorDeployTaskQueryInlineScript = {
@@ -1212,6 +1242,8 @@ function Invoke-RdpMonitorDeploySacVersionNotice {
} }
# --- main --- # --- main ---
if ($env:RDP_DEPLOY_FUNCTIONS_ONLY -eq '1') { return }
try { try {
$shareRoot = Resolve-SourceShareRoot $shareRoot = Resolve-SourceShareRoot
$sourceScript = Join-Path $shareRoot $ScriptName $sourceScript = Join-Path $shareRoot $ScriptName
+1 -1
View File
@@ -90,7 +90,7 @@ $script:SkipLogDetailLimit = 15
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах). # строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только # Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1). # исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
$ScriptVersion = "2.0.31-SAC" $ScriptVersion = "2.0.32-SAC"
# Логи (все под InstallRoot) # Логи (все под InstallRoot)
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log" $LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
+20
View File
@@ -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
}
+45
View File
@@ -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
+24
View File
@@ -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`.
+22
View File
@@ -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'
}
+22
View File
@@ -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)
}
}
}
+75
View File
@@ -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
}
}
+15
View File
@@ -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'
}
+16
View File
@@ -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
+39
View File
@@ -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
}
}
+1 -1
View File
@@ -1 +1 @@
2.0.31-SAC 2.0.32-SAC