[CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidatePattern('^v[0-9][0-9A-Za-z._-]*$')] [string]$Version, [string]$InstallDir = $( if ($env:GETH_INSTALL_DIR) { $env:GETH_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA 'Programs\geth\bin' } ), [string]$ReleaseBaseUrl = $( if ($env:GETH_RELEASE_BASE_URL) { $env:GETH_RELEASE_BASE_URL } else { 'https://forge.tionis.dev/eric/geth/releases/download' } ), [string]$ArchivePath, [string]$ChecksumPath, [switch]$NoModifyPath, [switch]$PrintAsset ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest if (-not [Environment]::Is64BitOperatingSystem) { throw 'No geth release artifact is available for 32-bit Windows.' } $asset = "geth-$Version-windows-x86_64.zip" if ($PrintAsset) { Write-Output $asset return } $releaseBase = $ReleaseBaseUrl.TrimEnd('/') $url = "$releaseBase/$Version/$asset" $tempDir = Join-Path ([IO.Path]::GetTempPath()) ("geth-install-" + [Guid]::NewGuid()) $extractDir = Join-Path $tempDir 'extract' try { New-Item -ItemType Directory -Force -Path $tempDir, $extractDir | Out-Null if ($ArchivePath) { $archive = (Resolve-Path -LiteralPath $ArchivePath).Path $checksumCandidate = if ($ChecksumPath) { $ChecksumPath } else { "$ArchivePath.sha256" } $checksum = (Resolve-Path -LiteralPath $checksumCandidate).Path Write-Host "using local archive $archive" } else { if ($ChecksumPath) { throw '-ChecksumPath requires -ArchivePath.' } if (([Uri]$url).Scheme -ne 'https') { throw 'Release downloads require an HTTPS base URL.' } $archive = Join-Path $tempDir $asset $checksum = "$archive.sha256" Write-Host "downloading $url" Invoke-WebRequest -Uri $url -OutFile $archive Invoke-WebRequest -Uri "$url.sha256" -OutFile $checksum } $expected = ((Get-Content -LiteralPath $checksum -Raw).Trim() -split '\s+')[0].ToLowerInvariant() if ($expected -notmatch '^[0-9a-f]{64}$') { throw "Invalid SHA-256 file for $asset." } $actual = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() if ($actual -ne $expected) { throw "Checksum mismatch for $asset." } Write-Host "verified SHA-256: $actual" Expand-Archive -LiteralPath $archive -DestinationPath $extractDir -Force $sourceBinary = Join-Path $extractDir 'geth.exe' if (-not (Test-Path -LiteralPath $sourceBinary -PathType Leaf)) { throw 'Archive does not contain geth.exe.' } New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null $destination = Join-Path $InstallDir 'geth.exe' $staged = Join-Path $InstallDir ('.geth.install.' + [Guid]::NewGuid() + '.exe') try { Copy-Item -LiteralPath $sourceBinary -Destination $staged Move-Item -LiteralPath $staged -Destination $destination -Force } catch { Remove-Item -LiteralPath $staged -Force -ErrorAction SilentlyContinue throw "Could not replace $destination. Stop the daemon with 'geth daemon stop' and retry. $($_.Exception.Message)" } if (-not $NoModifyPath) { $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') $pathParts = @($userPath -split ';' | Where-Object { $_ }) if ($pathParts -notcontains $InstallDir) { $newUserPath = (@($pathParts) + $InstallDir) -join ';' try { [Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User') Write-Host "added $InstallDir to the current user's PATH; open a new terminal to use it" } catch { Write-Warning "Could not update the user PATH. Add this directory manually: $InstallDir" } } else { Write-Host "$InstallDir is already on the current user's PATH" } } Write-Host "installed geth to $destination" Write-Host "the daemon was not started; run 'geth daemon install' when ready" } finally { if (Test-Path -LiteralPath $tempDir) { Remove-Item -LiteralPath $tempDir -Recurse -Force } }