#Requires -Version 5.1 #Requires -RunAsAdministrator <# .SYNOPSIS Локальная починка RDP TLS / сертификата TermService. .PARAMETER DiagnoseOnly Только диагностика и отчёт. .PARAMETER EmergencyRdpOnly SecurityLayer=0 (без TLS) для срочного доступа. .PARAMETER RestoreTls Вернуть SecurityLayer=2 после успешной привязки cert. .PARAMETER SkipReboot Не предлагать reboot при нативном сбросе. .PARAMETER NonInteractive Без вопросов (для автоматизации). $env:RDP_REPAIR_NONINTERACTIVE=1 #> [CmdletBinding()] param( [switch]$DiagnoseOnly, [switch]$EmergencyRdpOnly, [switch]$RestoreTls, [switch]$SkipReboot, [switch]$NonInteractive ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($NonInteractive) { $env:RDP_REPAIR_NONINTERACTIVE = '1' } $libDir = Join-Path $PSScriptRoot 'lib' . (Join-Path $libDir 'RdpRepair.Common.ps1') $logPath = Join-Path (Initialize-RdpRepairDataRoot) ("repair-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date)) function Show-RdpRepairClientCacheHint { Write-RdpRepairLog 'На клиенте RDP выполните:' 'INFO' $logPath Write-RdpRepairLog ' Remove-Item "HKCU:\Software\Microsoft\Terminal Server Client\Servers\<имя-сервера>" -Recurse -Force' 'INFO' $logPath } try { Assert-RdpRepairAdministrator Write-RdpRepairLog "=== Fix-RdpTls start $(Get-Date -Format o) ===" 'STEP' $logPath Write-RdpRepairLog "Компьютер: $env:COMPUTERNAME" 'INFO' $logPath $report = Export-RdpRepairDiagnosticReport -LogPath $logPath Write-RdpRepairLog "Диагностика сохранена: $report" 'OK' $logPath if ($DiagnoseOnly) { Get-RdpRepairDiagnosticSnapshot | Format-List exit 0 } if ($EmergencyRdpOnly) { Write-RdpRepairLog 'ЭКСТРЕННЫЙ РЕЖИМ: SecurityLayer=0 (RDP без TLS)' 'WARN' $logPath Set-RdpRepairSecurityLayer -Layer 0 -LogPath $logPath Show-RdpRepairClientCacheHint Write-RdpRepairLog 'Проверьте RDP. Для TLS после repair install: .\Fix-RdpTls.ps1 -RestoreTls' 'OK' $logPath exit 0 } if ($RestoreTls) { Write-RdpRepairLog 'Возврат SecurityLayer=2 (TLS)' 'STEP' $logPath $certs = Get-RdpRepairRemoteDesktopCertificates if ($certs.Count -eq 0) { 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 if (-not $valid) { Write-RdpRepairLog 'Все cert просрочены' 'ERROR' $logPath exit 2 } $thumb = $valid.Thumbprint if (-not (Get-RdpRepairSslCertificateHash)) { Write-RdpRepairLog "Привязка $($valid.Thumbprint)..." 'INFO' $logPath Set-RdpRepairCertificateBinding -Thumbprint $thumb -LogPath $logPath | Out-Null } $bind = Test-RdpRepairCertificateBindingPersisted -ExpectedThumbprint $thumb -LogPath $logPath if (-not $bind.Persisted) { Write-RdpRepairLog 'TermService does not keep hash - cannot restore TLS. See docs\REPAIR-INSTALL.md' 'ERROR' $logPath exit 3 } Set-ItemProperty -Path $script:RdpRepairRdpTcpKey -Name SecurityLayer -Value 2 Restart-RdpRepairTermService Write-RdpRepairLog 'SecurityLayer=2, TermService перезапущен' 'OK' $logPath Show-RdpRepairClientCacheHint exit 0 } # --- Полный интерактивный ремонт --- Write-RdpRepairLog 'ФАЗА 1: текущее состояние' 'STEP' $logPath $snap = Get-RdpRepairDiagnosticSnapshot $snap | Format-List ComputerName, OsCaption, SecurityLayer, SslHash, CertCount, Events1057_24h, GpoLicenseSrv $expired = @($snap.Certificates | Where-Object { $_.NotAfter -lt (Get-Date) }) if ($expired.Count -gt 0) { Write-RdpRepairLog "Найдено просроченных cert: $($expired.Count)" 'WARN' $logPath } if (-not (Confirm-RdpRepairContinue 'Продолжить: очистка cert и нативный сброс?')) { exit 0 } Write-RdpRepairLog 'ФАЗА 2: очистка stores + нативный сброс' 'STEP' $logPath $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 $bindNative = Test-RdpRepairCertificateBindingPersisted -ExpectedThumbprint $nativeCert.Thumbprint -LogPath $logPath if ($bindNative.Persisted) { Write-RdpRepairLog 'Native cert accepted - you can use RestoreTls' 'OK' $logPath if (Confirm-RdpRepairContinue 'Установить SecurityLayer=2 сейчас?') { Set-ItemProperty -Path $script:RdpRepairRdpTcpKey -Name SecurityLayer -Value 2 Restart-RdpRepairTermService } Show-RdpRepairClientCacheHint exit 0 } } if (-not $SkipReboot -and (Confirm-RdpRepairContinue 'Нативный cert не прижился. Запланировать REBOOT через 60 с?')) { Invoke-RdpRepairNativeReset -LogPath $logPath -Reboot | Out-Null Write-RdpRepairLog 'После загрузки снова запустите: .\Fix-RdpTls.ps1' 'WARN' $logPath exit 0 } if (-not (Confirm-RdpRepairContinue 'ФАЗА 3: создать локальный cert и привязать?')) { exit 0 } Write-RdpRepairLog 'ФАЗА 3: создание cert' 'STEP' $logPath Clear-RdpRepairCertificateStores -LogPath $logPath $dns = @(Get-RdpRepairComputerDnsNames) Write-RdpRepairLog "DNS: $($dns -join ', ')" 'INFO' $logPath $cert = New-RdpRepairSelfSignedCertificate -DnsNames $dns -LogPath $logPath Write-RdpRepairLog 'ФАЗА 4: ACL + Remote Desktop store' 'STEP' $logPath Set-RdpRepairPrivateKeyAcl -Certificate $cert -LogPath $logPath | Out-Null Add-RdpRepairCertificateToRemoteDesktopStore -Certificate $cert Write-RdpRepairLog 'ФАЗА 5: привязка и проверка AFTER' 'STEP' $logPath Set-RdpRepairCertificateBinding -Thumbprint $cert.Thumbprint -LogPath $logPath | Out-Null $bind = Test-RdpRepairCertificateBindingPersisted -ExpectedThumbprint $cert.Thumbprint -LogPath $logPath if ($bind.Persisted) { if (Confirm-RdpRepairContinue 'Привязка OK. Установить SecurityLayer=2?') { Set-ItemProperty -Path $script:RdpRepairRdpTcpKey -Name SecurityLayer -Value 2 Restart-RdpRepairTermService } Show-RdpRepairClientCacheHint Write-RdpRepairLog '=== Ремонт TLS завершён успешно ===' 'OK' $logPath exit 0 } Write-RdpRepairLog '=== TermService отклоняет cert ===' 'ERROR' $logPath Write-RdpRepairLog 'Варианты:' 'WARN' $logPath Write-RdpRepairLog ' 1) docs\REPAIR-INSTALL.md - repair install Windows' 'WARN' $logPath Write-RdpRepairLog ' 2) .\Fix-RdpTls.ps1 -EmergencyRdpOnly - RDP without TLS' 'WARN' $logPath if (Confirm-RdpRepairContinue 'Включить Emergency SecurityLayer=0 сейчас?') { Set-RdpRepairSecurityLayer -Layer 0 -LogPath $logPath Show-RdpRepairClientCacheHint } exit 4 } catch { Write-RdpRepairLog "FATAL: $($_.Exception.Message)" 'ERROR' $logPath Write-RdpRepairLog $_.ScriptStackTrace 'ERROR' $logPath exit 1 }