From a3c65191d17134d80a5fd1a043d588fc06b6b0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20D=2E?= Date: Fri, 19 Jun 2026 20:36:06 -0300 Subject: [PATCH 1/4] fix(install): add PowerShell 5.1 fallback for SHA256 checksum verification Get-FileHash is not available in Windows PowerShell 5.1, causing the install script to fail with 'Get-FileHash is not recognized'. Add a fallback using .NET cryptography (System.Security.Cryptography.SHA256) that works on both PowerShell 5.1 and 7+. The script now: 1. Checks if Get-FileHash is available (PS 7+) 2. Falls back to .NET SHA256.Create() for PS 5.1 Fixes checksum verification on Windows PowerShell 5.1. Related: #835, #910 --- scripts/install.ps1 | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index c08d8c3d9..568d92e97 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -255,7 +255,23 @@ function Install-ViaBinary { $expectedLine = $checksums | Where-Object { $_ -match $archiveName } if ($expectedLine) { $expectedChecksum = ($expectedLine -split "\s+")[0] - $actualChecksum = (Get-FileHash -Path $archivePath -Algorithm SHA256).Hash.ToLower() + + # Compute SHA256 hash - use Get-FileHash if available (PS 7+), + # otherwise fall back to .NET cryptography for PS 5.1 compatibility + if (Get-Command Get-FileHash -ErrorAction SilentlyContinue) { + $actualChecksum = (Get-FileHash -Path $archivePath -Algorithm SHA256).Hash.ToLower() + } else { + # PowerShell 5.1 fallback using .NET + $sha256 = [System.Security.Cryptography.SHA256]::Create() + $fileStream = [System.IO.File]::OpenRead($archivePath) + try { + $hashBytes = $sha256.ComputeHash($fileStream) + $actualChecksum = [System.BitConverter]::ToString($hashBytes).Replace("-", "").ToLower() + } finally { + $fileStream.Close() + $sha256.Dispose() + } + } if ($actualChecksum -ne $expectedChecksum) { Stop-WithError "Checksum mismatch!`n Expected: $expectedChecksum`n Got: $actualChecksum" From a66caf7185259d418fce5217c590ecdbbe0add27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20D=2E?= Date: Fri, 19 Jun 2026 23:52:49 -0300 Subject: [PATCH 2/4] fix(install): correct PowerShell version comment for Get-FileHash Get-FileHash was introduced in PowerShell 4.0, not 7+. Update comment to reflect accurate version information. Keep fallback for edge cases where cmdlet may be unavailable (corrupted install, restricted context). Addresses CodeRabbit review feedback on PR #937 --- scripts/install.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 568d92e97..c29888634 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -256,12 +256,13 @@ function Install-ViaBinary { if ($expectedLine) { $expectedChecksum = ($expectedLine -split "\s+")[0] - # Compute SHA256 hash - use Get-FileHash if available (PS 7+), - # otherwise fall back to .NET cryptography for PS 5.1 compatibility + # Compute SHA256 hash - use Get-FileHash if available (PS 4.0+), + # otherwise fall back to .NET cryptography for edge cases where + # the cmdlet is unavailable (corrupted install, restricted context, etc.) if (Get-Command Get-FileHash -ErrorAction SilentlyContinue) { $actualChecksum = (Get-FileHash -Path $archivePath -Algorithm SHA256).Hash.ToLower() } else { - # PowerShell 5.1 fallback using .NET + # Fallback using .NET for environments where Get-FileHash is unavailable $sha256 = [System.Security.Cryptography.SHA256]::Create() $fileStream = [System.IO.File]::OpenRead($archivePath) try { From cb03fcba37836929b6c57c57d6c727492b6d6124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20D=2E?= Date: Sat, 20 Jun 2026 13:51:14 -0300 Subject: [PATCH 3/4] test(install): add regression test for .NET SHA256 fallback Verifies that the .NET cryptography fallback produces identical SHA256 hashes to Get-FileHash, ensuring checksum verification works correctly when the cmdlet is unavailable. Addresses review feedback from Alan-TheGentleman on PR #937 --- scripts/test-hash-fallback.ps1 | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 scripts/test-hash-fallback.ps1 diff --git a/scripts/test-hash-fallback.ps1 b/scripts/test-hash-fallback.ps1 new file mode 100644 index 000000000..32eaa4368 --- /dev/null +++ b/scripts/test-hash-fallback.ps1 @@ -0,0 +1,41 @@ +# Test script to verify .NET SHA256 fallback produces identical results to Get-FileHash +# This ensures the fallback path works correctly when Get-FileHash is unavailable + +$testFile = "$env:TEMP\gentle-ai-hash-test.txt" +$testContent = "Test content for SHA256 verification - $(Get-Random)" + +try { + # Create test file + $testContent | Out-File -FilePath $testFile -Encoding UTF8 + + # Calculate hash using Get-FileHash (standard path) + $standardHash = (Get-FileHash -Path $testFile -Algorithm SHA256).Hash.ToLower() + + # Calculate hash using .NET fallback + $sha256 = [System.Security.Cryptography.SHA256]::Create() + $fileStream = [System.IO.File]::OpenRead($testFile) + try { + $hashBytes = $sha256.ComputeHash($fileStream) + $fallbackHash = [System.BitConverter]::ToString($hashBytes).Replace("-", "").ToLower() + } finally { + $fileStream.Close() + $sha256.Dispose() + } + + # Verify both methods produce identical results + if ($standardHash -eq $fallbackHash) { + Write-Host "PASS: .NET fallback produces identical hash to Get-FileHash" -ForegroundColor Green + Write-Host "Hash: $standardHash" + exit 0 + } else { + Write-Host "FAIL: Hash mismatch between Get-FileHash and .NET fallback" -ForegroundColor Red + Write-Host "Get-FileHash: $standardHash" + Write-Host ".NET fallback: $fallbackHash" + exit 1 + } +} finally { + # Cleanup + if (Test-Path $testFile) { + Remove-Item -Path $testFile -Force + } +} From db93938b93e118ba39a8d954fb1f9f7ea68ab4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20D=2E?= Date: Sat, 20 Jun 2026 21:39:33 -0300 Subject: [PATCH 4/4] fix(install): make hash fallback test work without Get-FileHash The test now validates the .NET fallback independently without requiring Get-FileHash. If Get-FileHash is available, it compares both methods. If not, it still validates the fallback produces a valid SHA256 hash. Addresses CodeRabbit review feedback on PR #937 --- scripts/test-hash-fallback.ps1 | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/scripts/test-hash-fallback.ps1 b/scripts/test-hash-fallback.ps1 index 32eaa4368..e82e48f96 100644 --- a/scripts/test-hash-fallback.ps1 +++ b/scripts/test-hash-fallback.ps1 @@ -1,17 +1,15 @@ -# Test script to verify .NET SHA256 fallback produces identical results to Get-FileHash -# This ensures the fallback path works correctly when Get-FileHash is unavailable +# Test script to verify .NET SHA256 fallback works correctly +# This ensures the fallback path produces valid SHA256 hashes even when Get-FileHash is unavailable $testFile = "$env:TEMP\gentle-ai-hash-test.txt" -$testContent = "Test content for SHA256 verification - $(Get-Random)" +# Use fixed content for deterministic testing +$testContent = "Gentle AI SHA256 fallback test" try { - # Create test file - $testContent | Out-File -FilePath $testFile -Encoding UTF8 + # Create test file with UTF-8 encoding (no BOM) + [System.IO.File]::WriteAllText($testFile, $testContent, [System.Text.UTF8Encoding]::new($false)) - # Calculate hash using Get-FileHash (standard path) - $standardHash = (Get-FileHash -Path $testFile -Algorithm SHA256).Hash.ToLower() - - # Calculate hash using .NET fallback + # Calculate hash using .NET fallback (the path we're testing) $sha256 = [System.Security.Cryptography.SHA256]::Create() $fileStream = [System.IO.File]::OpenRead($testFile) try { @@ -22,15 +20,31 @@ try { $sha256.Dispose() } - # Verify both methods produce identical results - if ($standardHash -eq $fallbackHash) { - Write-Host "PASS: .NET fallback produces identical hash to Get-FileHash" -ForegroundColor Green - Write-Host "Hash: $standardHash" + # Verify the hash is valid (64 hex characters for SHA256) + if ($fallbackHash -match '^[a-f0-9]{64}$') { + Write-Host "PASS: .NET fallback produces valid SHA256 hash" -ForegroundColor Green + Write-Host "Hash: $fallbackHash" + + # If Get-FileHash is available, verify both methods match + if (Get-Command Get-FileHash -ErrorAction SilentlyContinue) { + $standardHash = (Get-FileHash -Path $testFile -Algorithm SHA256).Hash.ToLower() + if ($standardHash -eq $fallbackHash) { + Write-Host "PASS: .NET fallback matches Get-FileHash" -ForegroundColor Green + } else { + Write-Host "FAIL: Hash mismatch between Get-FileHash and .NET fallback" -ForegroundColor Red + Write-Host "Get-FileHash: $standardHash" + Write-Host ".NET fallback: $fallbackHash" + exit 1 + } + } else { + Write-Host "INFO: Get-FileHash not available, skipping comparison test" -ForegroundColor Yellow + } + exit 0 } else { - Write-Host "FAIL: Hash mismatch between Get-FileHash and .NET fallback" -ForegroundColor Red - Write-Host "Get-FileHash: $standardHash" - Write-Host ".NET fallback: $fallbackHash" + Write-Host "FAIL: .NET fallback produced invalid hash format" -ForegroundColor Red + Write-Host "Got: $fallbackHash" + Write-Host "Expected: 64 hex characters" exit 1 } } finally {