From 5761710fa13f9c6a10a466de08bd77394b3884c4 Mon Sep 17 00:00:00 2001 From: PTah Date: Fri, 12 Jun 2026 20:31:03 +1000 Subject: [PATCH] =?UTF-8?q?fix:=20Thumbprint=20=D0=BF=D1=80=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D0=BE=D0=BC=20cert=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D1=81=D0=B1=D1=80=D0=BE=D1=81=D0=B0=20(PS=205.1=20unwrap)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены Get-RdpRepairCertificateArray/FirstCertificate, самотесты на вложенные массивы; Fix-RdpTls больше не использует Select-Object -First 1 на cert. Co-authored-by: Cursor --- README.md | 2 ++ scripts/Fix-RdpTls.ps1 | 7 +++--- scripts/Invoke-RdpRepairSelfTest.ps1 | 28 ++++++++++++++++++++++++ scripts/lib/RdpRepair.Common.ps1 | 32 +++++++++++++++++++++++----- 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5944d50..807007a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ cd C:\path\to\fix-DAK-PC\scripts powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Invoke-RdpRepairSelfTest.ps1 -IncludeAdminTests ``` +Самотесты проверяют синтаксис, загрузку модулей, чтение cert store и ловушки PowerShell 5.1 с одним cert; **не** прогоняют полный `Fix-RdpTls.ps1` (очистку store, reboot, привязку). + 5. **Полный интерактивный ремонт**: ```powershell diff --git a/scripts/Fix-RdpTls.ps1 b/scripts/Fix-RdpTls.ps1 index 2cae616..58438e6 100644 --- a/scripts/Fix-RdpTls.ps1 +++ b/scripts/Fix-RdpTls.ps1 @@ -71,7 +71,7 @@ try { Write-RdpRepairLog 'No cert in Remote Desktop store - run full repair or repair install first' 'ERROR' $logPath exit 2 } - $valid = $certs | Where-Object { $_.NotAfter -gt (Get-Date) } | Select-Object -First 1 + $valid = @(Get-RdpRepairCertificateArray $certs | Where-Object { $_.NotAfter -gt (Get-Date) })[0] if (-not $valid) { Write-RdpRepairLog 'Все cert просрочены' 'ERROR' $logPath exit 2 @@ -98,7 +98,7 @@ try { $snap = Get-RdpRepairDiagnosticSnapshot $snap | Format-List ComputerName, OsCaption, SecurityLayer, SslHash, CertCount, Events1057_24h, GpoLicenseSrv - $expired = @($snap.Certificates | Where-Object { $_.NotAfter -lt (Get-Date) }) + $expired = @(Get-RdpRepairCertificateArray $snap.Certificates | Where-Object { $_.NotAfter -lt (Get-Date) }) if ($expired.Count -gt 0) { Write-RdpRepairLog "Найдено просроченных cert: $($expired.Count)" 'WARN' $logPath } @@ -111,7 +111,8 @@ try { $native = Invoke-RdpRepairNativeReset -LogPath $logPath if ($native.CertCount -gt 0) { Write-RdpRepairLog "После сброса без reboot: нативный cert найден ($($native.CertCount))" 'OK' $logPath - $nativeCert = $native.Certificates | Select-Object -First 1 + $nativeCert = Get-RdpRepairFirstCertificate -Certificates $native.Certificates + if (-not $nativeCert) { throw 'Native cert list empty after reset' } $bindNative = Test-RdpRepairCertificateBindingPersisted -ExpectedThumbprint $nativeCert.Thumbprint -LogPath $logPath if ($bindNative.Persisted) { Write-RdpRepairLog 'Native cert accepted - you can use RestoreTls' 'OK' $logPath diff --git a/scripts/Invoke-RdpRepairSelfTest.ps1 b/scripts/Invoke-RdpRepairSelfTest.ps1 index fc6beca..60961fd 100644 --- a/scripts/Invoke-RdpRepairSelfTest.ps1 +++ b/scripts/Invoke-RdpRepairSelfTest.ps1 @@ -76,6 +76,30 @@ Assert-Test 'Get-RdpRepairRemoteDesktopCertificates exposes Count' { $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 'Get-RdpRepairDiagnosticSnapshot object shape' { if (-not (Test-RdpRepairIsAdministrator)) { Write-Host ' (skip snapshot fields - not admin)' -ForegroundColor DarkYellow @@ -85,6 +109,10 @@ Assert-Test 'Get-RdpRepairDiagnosticSnapshot object shape' { 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' { diff --git a/scripts/lib/RdpRepair.Common.ps1 b/scripts/lib/RdpRepair.Common.ps1 index 28eeb09..505e109 100644 --- a/scripts/lib/RdpRepair.Common.ps1 +++ b/scripts/lib/RdpRepair.Common.ps1 @@ -86,8 +86,30 @@ function Get-RdpRepairRemoteDesktopCertificates { } finally { $store.Close() } - # Запятая перед return: иначе PS 5.1 разворачивает массив из одного cert и ломает .Count - return ,[object[]]$list.ToArray() + return [object[]]$list.ToArray() +} + +function Get-RdpRepairCertificateArray { + param([object]$Certificates) + if ($null -eq $Certificates) { return @() } + $flat = New-Object System.Collections.Generic.List[object] + foreach ($item in @($Certificates)) { + if ($item -is [object[]]) { + foreach ($inner in @($item)) { + if ($null -ne $inner) { [void]$flat.Add($inner) } + } + } elseif ($null -ne $item) { + [void]$flat.Add($item) + } + } + return [object[]]$flat.ToArray() +} + +function Get-RdpRepairFirstCertificate { + param([object]$Certificates) + $arr = @(Get-RdpRepairCertificateArray -Certificates $Certificates) + if ($arr.Count -lt 1) { return $null } + return $arr[0] } function Clear-RdpRepairCertificateStores { @@ -258,7 +280,7 @@ function Invoke-RdpRepairNativeReset { return [PSCustomObject]@{ RebootScheduled = $false CertCount = $certs.Count - Certificates = $certs + Certificates = ,[object[]]$certs } } @@ -283,7 +305,7 @@ function Get-RdpRepairDiagnosticSnapshot { TermService = (Get-Service TermService).Status RdpAllowed = -not (Get-ItemProperty $script:RdpRepairTsKey).fDenyTSConnections CertCount = $certs.Count - Certificates = $certs + Certificates = ,[object[]]$certs GpoLicenseSrv = $pol.LicenseServers GpoLicMode = $pol.LicensingMode Events1057_24h = $ev1057 @@ -302,7 +324,7 @@ function Export-RdpRepairDiagnosticReport { $snap.PSObject.Properties | ForEach-Object { if ($_.Name -eq 'Certificates') { $lines += 'Certificates:' - foreach ($c in $_.Value) { + foreach ($c in (Get-RdpRepairCertificateArray $_.Value)) { $lines += (' - {0}; Thumb={1}; NotAfter={2}; Key={3}' -f $c.Subject, $c.Thumbprint, $c.NotAfter, $c.HasKey) } } else {