From f90a12f7f1da50f34e5d812a75d8793dd7a129f0 Mon Sep 17 00:00:00 2001 From: GCH Date: Tue, 2 Jun 2026 12:53:27 +0200 Subject: [PATCH] =?UTF-8?q?feat(SignOnly=5Fdetect):=20tilf=C3=B8j=20detalj?= =?UTF-8?q?eret=20logning=20og=20forbedret=20fejlh=C3=A5ndtering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tilføj Write-Log på alle checkpoints (certifikat, registry, filer) - Omskriv cert-tjek til at logge alle manglende stores før exit - Skift fra exit 1 til skip på brugere uden RDP-filer - Indpak i try/catch for robust fejlhåndtering feat(sandbox): tilføj sandbox_signonly.ps1 til test af SignOnly + Detect - Kombiner SignOnly (cert + signering) og Detect (verifikation) i ét script - Samme skip-logik for brugere uden RDP-filer --- app/SignOnly_detect.ps1 | 111 ++++++++++++++----- app/sandbox_signonly.ps1 | 234 +++++++++++++++++++++++++++++++++++++++ output/Install.intunewin | Bin 0 -> 15139 bytes 3 files changed, 317 insertions(+), 28 deletions(-) create mode 100644 app/sandbox_signonly.ps1 create mode 100644 output/Install.intunewin diff --git a/app/SignOnly_detect.ps1 b/app/SignOnly_detect.ps1 index 23f009f..74a99f5 100644 --- a/app/SignOnly_detect.ps1 +++ b/app/SignOnly_detect.ps1 @@ -1,5 +1,6 @@ # ============================================================ # CHANGELOG +# v1.3 - Added detailed logging for detect failures # v1.2 - Get-DesktopPaths: Depth 2 search for OneDrive Desktop # v1.1 - AllUsers switch + Get-DesktopPaths helper # v1.0 - Initial @@ -9,6 +10,21 @@ param( [switch]$AllUsers ) +# Setup logging +$baseFolder = Join-Path $env:SystemDrive "Intune" +$logFile = Join-Path $baseFolder "RDPSign.log" + +if (-not (Test-Path $baseFolder)) { + New-Item -Path $baseFolder -ItemType Directory -Force | Out-Null +} + +function Write-Log { + param($Message) + $logMessage = "$(Get-Date -Format 'dd-MM-yyyy HH:mm'): $Message" + Add-Content -Path $logFile -Value $logMessage + Write-Host $logMessage +} + function Get-DesktopPaths { if ($AllUsers) { Get-ChildItem "$env:SystemDrive\Users" -Directory -Exclude "Public","Default" | ForEach-Object { @@ -19,40 +35,79 @@ function Get-DesktopPaths { [Environment]::GetFolderPath("CommonDesktopDirectory") } } -$certSubject = "CN=RDPSign" -# Check certificate exists in all three stores -$certInMy = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -eq $certSubject } -$certInRoot = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -eq $certSubject } -$certInPub = Get-ChildItem Cert:\LocalMachine\TrustedPublisher | Where-Object { $_.Subject -eq $certSubject } +try { + Write-Log "=== SignOnly Detect START ===" + Write-Log "Running in mode: $(if ($AllUsers) { 'All Users' } else { 'Public Desktop' })" -if (-not $certInMy -or -not $certInRoot -or -not $certInPub) { - exit 1 -} + $certSubject = "CN=RDPSign" -# Check registry thumbprint -$gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -$thumbprintValue = (Get-ItemProperty -Path $gpoPath -Name "TrustedCertThumbprints" -ErrorAction SilentlyContinue).TrustedCertThumbprints -if ([string]::IsNullOrWhiteSpace($thumbprintValue)) { - exit 1 -} + # Check certificate exists in all three stores + Write-Log "Checking certificate: $certSubject..." + $certInMy = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -eq $certSubject } + $certInRoot = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -eq $certSubject } + $certInPub = Get-ChildItem Cert:\LocalMachine\TrustedPublisher | Where-Object { $_.Subject -eq $certSubject } -# Check that all RDP files on desktop(s) are signed -foreach ($desktopPath in (Get-DesktopPaths)) { - $rdpFiles = Get-ChildItem -Path $desktopPath -Filter "*.rdp" -ErrorAction SilentlyContinue - - if ($rdpFiles.Count -eq 0) { + $certOk = $true + if (-not $certInMy) { + Write-Log "FAIL: Certificate not found in LocalMachine\My" + $certOk = $false + } + if (-not $certInRoot) { + Write-Log "FAIL: Certificate not found in LocalMachine\Root" + $certOk = $false + } + if (-not $certInPub) { + Write-Log "FAIL: Certificate not found in LocalMachine\TrustedPublisher" + $certOk = $false + } + if (-not $certOk) { exit 1 } + Write-Log "OK: Certificate found in all stores" - foreach ($file in $rdpFiles) { - $content = Get-Content $file.FullName -ErrorAction SilentlyContinue - $hasSig = $content | Where-Object { $_ -match "^signature:s:" } - if (-not $hasSig) { - exit 1 - } + # Check registry thumbprint + Write-Log "Checking registry thumbprint..." + $gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" + $thumbprintValue = (Get-ItemProperty -Path $gpoPath -Name "TrustedCertThumbprints" -ErrorAction SilentlyContinue).TrustedCertThumbprints + if ([string]::IsNullOrWhiteSpace($thumbprintValue)) { + Write-Log "FAIL: TrustedCertThumbprints not found in registry" + exit 1 } -} + Write-Log "OK: Thumbprint found in registry" -Write-Host "Detected" -exit 0 + # Check that all RDP files on desktop(s) are signed + Write-Log "Checking RDP file signatures..." + $desktopsChecked = 0 + $filesChecked = 0 + + foreach ($desktopPath in (Get-DesktopPaths)) { + $rdpFiles = Get-ChildItem -Path $desktopPath -Filter "*.rdp" -ErrorAction SilentlyContinue + + if ($rdpFiles.Count -eq 0) { + Write-Log "Skip: No .rdp files on $desktopPath" + continue + } + + Write-Log "Found $($rdpFiles.Count) file(s) to check on: $desktopPath" + + foreach ($file in $rdpFiles) { + $filesChecked++ + $content = Get-Content $file.FullName -ErrorAction SilentlyContinue + $hasSig = $content | Where-Object { $_ -match "^signature:s:" } + if (-not $hasSig) { + Write-Log "FAIL: $($file.Name) on $desktopPath is not signed" + exit 1 + } + } + $desktopsChecked++ + } + + Write-Log "OK: All $filesChecked file(s) across $desktopsChecked desktop(s) are signed" + Write-Log "=== SignOnly Detect END - Installed ===" + exit 0 +} +catch { + Write-Log "Error: $_" + exit 1 +} diff --git a/app/sandbox_signonly.ps1 b/app/sandbox_signonly.ps1 new file mode 100644 index 0000000..e5e3fff --- /dev/null +++ b/app/sandbox_signonly.ps1 @@ -0,0 +1,234 @@ +# ============================================================ +# SIGNONLY + DETECT - Sandbox test script +# ============================================================ +# CHANGELOG +# v1.0 - Initial +# ============================================================ + +param( + [switch]$AllUsers +) +Start-Sleep -Seconds 10 +# Setup logging +$baseFolder = Join-Path $env:SystemDrive "Intune" +$logFile = Join-Path $baseFolder "RDPSign.log" + +# Check if Sysnative exists (means script is running in 32-bit mode on x64 OS) +if (Test-Path "$($env:windir)\Sysnative") { + $rdpSignPath = "$($env:windir)\Sysnative\rdpsign.exe" +} else { + $rdpSignPath = "$($env:windir)\System32\rdpsign.exe" +} + +if (-not (Test-Path $baseFolder)) { + New-Item -Path $baseFolder -ItemType Directory -Force | Out-Null +} + +function Write-Log { + param($Message) + $logMessage = "$(Get-Date -Format 'dd-MM-yyyy HH:mm'): $Message" + Add-Content -Path $logFile -Value $logMessage + Write-Host $logMessage +} + +function Get-DesktopPaths { + if ($AllUsers) { + Get-ChildItem "$env:SystemDrive\Users" -Directory -Exclude "Public","Default" | ForEach-Object { + Get-ChildItem $_.FullName -Directory -Filter "Desktop" -Depth 2 -ErrorAction SilentlyContinue | + Select-Object -ExpandProperty FullName + } + } else { + [Environment]::GetFolderPath("CommonDesktopDirectory") + } +} + +# ============================================================ +# SIGNONLY +# ============================================================ +Write-Log "=== START SIGNONLY ===" +Write-Log "Running in mode: $(if ($AllUsers) { 'All Users' } else { 'Public Desktop' })" + +try { + $certSubjectName = "RDPSign" + $certSubject = "CN=$certSubjectName" + + Write-Log "Searching for existing certificate: $certSubjectName..." + $existingCert = Get-ChildItem Cert:\LocalMachine\My | + Where-Object { $_.Subject -eq $certSubject } | + Select-Object -First 1 + + if ($existingCert) { + Write-Log "Found existing certificate with Thumbprint: $($existingCert.Thumbprint)" + $thumbprint = $existingCert.Thumbprint + } else { + Write-Log "No existing certificate found. Creating new one..." + + $cert = New-SelfSignedCertificate ` + -Subject $certSubject ` + -CertStoreLocation "Cert:\LocalMachine\My" ` + -Type CodeSigningCert ` + -KeyExportPolicy NonExportable ` + -NotAfter (Get-Date).AddYears(25) + + $thumbprint = $cert.Thumbprint + + # Add to Trusted Root + $rootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store("Root", "LocalMachine") + $rootStore.Open("ReadWrite") + $rootStore.Add($cert) + $rootStore.Close() + + # Add to Trusted Publishers + $pubStore = New-Object System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher", "LocalMachine") + $pubStore.Open("ReadWrite") + $pubStore.Add($cert) + $pubStore.Close() + + # Add thumbprint to RDP trusted certs in registry + $gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" + $keyName = "TrustedCertThumbprints" + + if (-not (Test-Path $gpoPath)) { + New-Item -Path $gpoPath -Force | Out-Null + } + + $currentValue = (Get-ItemProperty -Path $gpoPath -Name $keyName -ErrorAction SilentlyContinue).$keyName + + if ($currentValue -notmatch [regex]::Escape($thumbprint)) { + $newValue = if ([string]::IsNullOrWhiteSpace($currentValue)) { $thumbprint } else { "$currentValue,$thumbprint" } + + if ($null -eq $currentValue) { + New-ItemProperty -Path $gpoPath -Name $keyName -Value $newValue -PropertyType String -Force | Out-Null + } else { + Set-ItemProperty -Path $gpoPath -Name $keyName -Value $newValue + } + Write-Log "Updated TrustedCertThumbprints registry value." + } else { + Write-Log "Thumbprint already exists in registry. Skipping update." + } + + Write-Log "Certificate created and trusted successfully." + } + + $successCount = 0 + $failCount = 0 + + foreach ($desktopPath in (Get-DesktopPaths)) { + $rdpFiles = Get-ChildItem -Path $desktopPath -Filter "*.rdp" -Recurse -ErrorAction SilentlyContinue + + if ($rdpFiles.Count -eq 0) { + Write-Log "Skip: No .rdp files on $desktopPath" + continue + } + + Write-Log "Found $($rdpFiles.Count) file(s) to sign on: $desktopPath" + + foreach ($rdpFile in $rdpFiles) { + Write-Log "Signing: $($rdpFile.Name)" + & $rdpSignPath /sha256 $thumbprint $rdpFile.FullName + + if ($LASTEXITCODE -eq 0) { + Write-Log " [OK] $($rdpFile.Name)" + $successCount++ + } else { + Write-Log " [FAILED] $($rdpFile.Name) - rdpsign exit code: $LASTEXITCODE" + $failCount++ + } + } + } + + # Set registry key to suppress RDP warning dialogs + reg add "HKLM\Software\Policies\Microsoft\Windows NT\Terminal Services\Client" /v RedirectionWarningDialogVersion /t REG_DWORD /d 1 /f + + if ($failCount -eq 0) { + Write-Log "All $successCount file(s) signed successfully." + } else { + Write-Log "$successCount file(s) signed successfully, $failCount file(s) failed." + } + +} catch { + Write-Log "SIGNONLY error: $_" +} + +# ============================================================ +# DETECT +# ============================================================ +Write-Log "=== START DETECT ===" + +try { + $certSubject = "CN=RDPSign" + + Write-Log "Checking certificate: $certSubject..." + $certInMy = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -eq $certSubject } + $certInRoot = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -eq $certSubject } + $certInPub = Get-ChildItem Cert:\LocalMachine\TrustedPublisher | Where-Object { $_.Subject -eq $certSubject } + + $certOk = $true + if (-not $certInMy) { + Write-Log "FAIL: Certificate not found in LocalMachine\My" + $certOk = $false + } + if (-not $certInRoot) { + Write-Log "FAIL: Certificate not found in LocalMachine\Root" + $certOk = $false + } + if (-not $certInPub) { + Write-Log "FAIL: Certificate not found in LocalMachine\TrustedPublisher" + $certOk = $false + } + if (-not $certOk) { + Write-Log "DETECT [FAIL]: Certificate missing" + } else { + Write-Log "OK: Certificate found in all stores" + } + + # Check registry thumbprint + Write-Log "Checking registry thumbprint..." + $gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" + $thumbprintValue = (Get-ItemProperty -Path $gpoPath -Name "TrustedCertThumbprints" -ErrorAction SilentlyContinue).TrustedCertThumbprints + if ([string]::IsNullOrWhiteSpace($thumbprintValue)) { + Write-Log "FAIL: TrustedCertThumbprints not found in registry" + $certOk = $false + } else { + Write-Log "OK: Thumbprint found in registry" + } + + # Check that all RDP files on desktop(s) are signed + Write-Log "Checking RDP file signatures..." + $desktopsChecked = 0 + $filesChecked = 0 + $allSigned = $true + + foreach ($desktopPath in (Get-DesktopPaths)) { + $rdpFiles = Get-ChildItem -Path $desktopPath -Filter "*.rdp" -ErrorAction SilentlyContinue + + if ($rdpFiles.Count -eq 0) { + Write-Log "Skip: No .rdp files on $desktopPath" + continue + } + + Write-Log "Found $($rdpFiles.Count) file(s) to check on: $desktopPath" + + foreach ($file in $rdpFiles) { + $filesChecked++ + $content = Get-Content $file.FullName -ErrorAction SilentlyContinue + $hasSig = $content | Where-Object { $_ -match "^signature:s:" } + if (-not $hasSig) { + Write-Log "FAIL: $($file.Name) on $desktopPath is not signed" + $allSigned = $false + } + } + $desktopsChecked++ + } + + if ($allSigned -and $certOk) { + Write-Log "DETECT [OK]: $filesChecked file(s) across $desktopsChecked desktop(s) - all signed and configured" + exit 0 + } else { + Write-Log "DETECT [FAIL]: Check log for details" + exit 1 + } +} catch { + Write-Log "Detect error: $_" + exit 1 +} diff --git a/output/Install.intunewin b/output/Install.intunewin new file mode 100644 index 0000000000000000000000000000000000000000..e7334cd058453c5a941e862099ab2be33a3004ba GIT binary patch literal 15139 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 literal 0 HcmV?d00001