27c1f49be1
Реестр отдаёт byte[]; TermService принимал cert, но проверка Persisted ложно падала. Добавлены ConvertTo-RdpRepairThumbprintHex и самотесты на форматы DAK-PC. Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
7.4 KiB
PowerShell
205 lines
7.4 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Самотесты модуля RdpRepair (синтаксис, функции, dry-run логики).
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$IncludeAdminTests
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$script:SelfTestFailed = 0
|
|
$script:SelfTestPassed = 0
|
|
|
|
function Assert-Test {
|
|
param(
|
|
[string]$Name,
|
|
[scriptblock]$Test
|
|
)
|
|
try {
|
|
& $Test
|
|
$script:SelfTestPassed++
|
|
Write-Host "[PASS] $Name" -ForegroundColor Green
|
|
} catch {
|
|
$script:SelfTestFailed++
|
|
Write-Host "[FAIL] $Name - $($_.Exception.Message)" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
$libPath = Join-Path $PSScriptRoot 'lib\RdpRepair.Common.ps1'
|
|
$mainPath = Join-Path $PSScriptRoot 'Fix-RdpTls.ps1'
|
|
. $libPath
|
|
|
|
Write-Host "`n=== RdpRepair SelfTest ===" -ForegroundColor Cyan
|
|
|
|
Assert-Test 'RdpRepair.Common.ps1 exists' {
|
|
if (-not (Test-Path -LiteralPath $libPath)) { throw 'file missing' }
|
|
}
|
|
|
|
Assert-Test 'Fix-RdpTls.ps1 exists' {
|
|
if (-not (Test-Path -LiteralPath $mainPath)) { throw 'file missing' }
|
|
}
|
|
|
|
Assert-Test 'PowerShell parse RdpRepair.Common.ps1' {
|
|
$parseErrors = $null
|
|
$null = [System.Management.Automation.Language.Parser]::ParseFile($libPath, [ref]$null, [ref]$parseErrors)
|
|
if ($parseErrors.Count -gt 0) { throw (($parseErrors | ForEach-Object { $_.ToString() }) -join '; ') }
|
|
}
|
|
|
|
Assert-Test 'PowerShell parse Fix-RdpTls.ps1' {
|
|
$parseErrors = $null
|
|
$null = [System.Management.Automation.Language.Parser]::ParseFile($mainPath, [ref]$null, [ref]$parseErrors)
|
|
if ($parseErrors.Count -gt 0) { throw (($parseErrors | ForEach-Object { $_.ToString() }) -join '; ') }
|
|
}
|
|
|
|
Assert-Test 'Dot-source Common without throw' {
|
|
if (-not (Get-Command Initialize-RdpRepairDataRoot -ErrorAction SilentlyContinue)) {
|
|
throw 'Common module not loaded'
|
|
}
|
|
}
|
|
|
|
Assert-Test 'Initialize-RdpRepairDataRoot returns path' {
|
|
$p = Initialize-RdpRepairDataRoot
|
|
if (-not (Test-Path -LiteralPath $p)) { throw 'dir not created' }
|
|
}
|
|
|
|
Assert-Test 'Get-RdpRepairComputerDnsNames non-empty' {
|
|
$n = @(Get-RdpRepairComputerDnsNames)
|
|
if ($n.Count -lt 1) { throw 'no dns names' }
|
|
}
|
|
|
|
Assert-Test 'Get-RdpRepairRemoteDesktopCertificates exposes Count' {
|
|
$certs = @(Get-RdpRepairRemoteDesktopCertificates)
|
|
$null = $certs.Count
|
|
}
|
|
|
|
Assert-Test 'single cert in PSCustomObject keeps Thumbprint' {
|
|
$mock = [PSCustomObject]@{
|
|
Thumbprint = 'DEADBEEF'
|
|
Subject = 'CN=test'
|
|
NotAfter = (Get-Date).AddDays(30)
|
|
HasKey = $true
|
|
}
|
|
$box = [PSCustomObject]@{ Certificates = @($mock) }
|
|
$first = Get-RdpRepairFirstCertificate -Certificates $box.Certificates
|
|
if ($first.Thumbprint -ne 'DEADBEEF') { throw 'Thumbprint lost via property unwrap' }
|
|
}
|
|
|
|
Assert-Test 'Get-RdpRepairCertificateArray flattens nested Object[]' {
|
|
$mock = [PSCustomObject]@{
|
|
Thumbprint = 'NESTED01'
|
|
Subject = 'CN=nested'
|
|
NotAfter = (Get-Date).AddDays(1)
|
|
HasKey = $true
|
|
}
|
|
$box = [PSCustomObject]@{ Certificates = ,[object[]]@(,[object[]]@($mock)) }
|
|
$first = Get-RdpRepairFirstCertificate -Certificates $box.Certificates
|
|
if ($first.Thumbprint -ne 'NESTED01') { throw "expected NESTED01 got $($first.Thumbprint)" }
|
|
}
|
|
|
|
Assert-Test 'foreach must use @() wrapper on single cert (Export pattern)' {
|
|
$mock = [PSCustomObject]@{
|
|
Thumbprint = 'LOOP01'
|
|
Subject = 'CN=loop'
|
|
NotAfter = (Get-Date).AddDays(1)
|
|
HasKey = $true
|
|
}
|
|
$lines = @()
|
|
foreach ($c in @(Get-RdpRepairCertificateArray @($mock))) {
|
|
$lines += $c.Subject
|
|
}
|
|
if ($lines.Count -ne 1 -or $lines[0] -ne 'CN=loop') {
|
|
throw "expected CN=loop got $($lines -join ',')"
|
|
}
|
|
}
|
|
|
|
Assert-Test 'Get-RdpRepairDiagnosticSnapshot object shape' {
|
|
if (-not (Test-RdpRepairIsAdministrator)) {
|
|
Write-Host ' (skip snapshot fields - not admin)' -ForegroundColor DarkYellow
|
|
return
|
|
}
|
|
$s = Get-RdpRepairDiagnosticSnapshot
|
|
foreach ($prop in @('ComputerName', 'SecurityLayer', 'CertCount', 'TermService')) {
|
|
if (-not $s.PSObject.Properties[$prop]) { throw "missing property $prop" }
|
|
}
|
|
if ($s.CertCount -gt 0) {
|
|
$first = Get-RdpRepairFirstCertificate -Certificates $s.Certificates
|
|
if (-not $first.Thumbprint) { throw 'snapshot cert missing Thumbprint' }
|
|
}
|
|
}
|
|
|
|
Assert-Test 'Registry path constants well-formed' {
|
|
if ($script:RdpRepairRdpTcpKey -notmatch 'RDP-Tcp$') { throw 'bad rdp tcp key' }
|
|
}
|
|
|
|
Assert-Test 'ConvertTo-RdpRepairThumbprintHex from decimal byte string' {
|
|
$hex = ConvertTo-RdpRepairThumbprintHex -HashValue '20 77 141 24 197 69 45 187 141 61 233 254 108 97 230 158 36 151 225 169'
|
|
if ($hex -ne '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9') { throw "got $hex" }
|
|
}
|
|
|
|
Assert-Test 'ConvertTo-RdpRepairThumbprintHex from byte array' {
|
|
$bytes = [byte[]](0x14, 0x4D, 0x8D, 0x18, 0xC5, 0x45, 0x2D, 0xBB, 0x8D, 0x3D, 0xE9, 0xFE, 0x6C, 0x61, 0xE6, 0x9E, 0x24, 0x97, 0xE1, 0xA9)
|
|
$hex = ConvertTo-RdpRepairThumbprintHex -HashValue $bytes
|
|
if ($hex -ne '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9') { throw "got $hex" }
|
|
}
|
|
|
|
Assert-Test 'ConvertTo-RdpRepairThumbprintHex hex equals byte forms' {
|
|
$fromHex = ConvertTo-RdpRepairThumbprintHex -HashValue '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9'
|
|
$fromBytes = ConvertTo-RdpRepairThumbprintHex -HashValue ([byte[]](20, 77, 141, 24, 197, 69, 45, 187, 141, 61, 233, 254, 108, 97, 230, 158, 36, 151, 225, 169))
|
|
if ($fromHex -ne $fromBytes) { throw 'format mismatch' }
|
|
}
|
|
|
|
Assert-Test 'Export-RdpRepairDiagnosticReport writes file' {
|
|
if (-not (Test-RdpRepairIsAdministrator)) {
|
|
Write-Host ' (skip report write - not admin)' -ForegroundColor DarkYellow
|
|
return
|
|
}
|
|
$log = Join-Path $env:TEMP 'rdp-selftest.log'
|
|
$rp = Export-RdpRepairDiagnosticReport -LogPath $log
|
|
if (-not (Test-Path -LiteralPath $rp)) { throw 'report not written' }
|
|
if ((Get-Item $rp).Length -lt 50) { throw 'report too small' }
|
|
}
|
|
|
|
Assert-Test 'Confirm-RdpRepairContinue noninteractive' {
|
|
$env:RDP_REPAIR_NONINTERACTIVE = '1'
|
|
if (-not (Confirm-RdpRepairContinue 'test')) { throw 'expected true' }
|
|
Remove-Item Env:\RDP_REPAIR_NONINTERACTIVE -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Assert-Test 'SecurityLayer values documented (0,1,2)' {
|
|
foreach ($v in 0, 1, 2) {
|
|
if ($v -lt 0 -or $v -gt 2) { throw 'invalid' }
|
|
}
|
|
}
|
|
|
|
Assert-Test 'docs REPAIR-INSTALL.md exists' {
|
|
$d = Join-Path (Split-Path $PSScriptRoot -Parent) 'docs\REPAIR-INSTALL.md'
|
|
if (-not (Test-Path -LiteralPath $d)) { throw 'missing' }
|
|
}
|
|
|
|
Assert-Test 'docs RETURN-TO-TLS.md exists' {
|
|
$d = Join-Path (Split-Path $PSScriptRoot -Parent) 'docs\RETURN-TO-TLS.md'
|
|
if (-not (Test-Path -LiteralPath $d)) { throw 'missing' }
|
|
}
|
|
|
|
if ($IncludeAdminTests -and (Test-RdpRepairIsAdministrator)) {
|
|
Write-Host "`n--- Admin-only tests ---" -ForegroundColor Cyan
|
|
Assert-Test 'TermService service exists' {
|
|
if (-not (Get-Service TermService -ErrorAction SilentlyContinue)) { throw 'TermService missing' }
|
|
}
|
|
Assert-Test 'Get-RdpRepairRemoteDesktopCertificates no throw' {
|
|
$null = Get-RdpRepairRemoteDesktopCertificates
|
|
}
|
|
} elseif ($IncludeAdminTests) {
|
|
Write-Host "`n(Warning: -IncludeAdminTests skipped - not running as Administrator)" -ForegroundColor Yellow
|
|
}
|
|
|
|
Write-Host "`n=== Results: PASS=$script:SelfTestPassed FAIL=$script:SelfTestFailed ===" -ForegroundColor Cyan
|
|
if ($script:SelfTestFailed -gt 0) {
|
|
exit 1
|
|
}
|
|
exit 0
|