param( [switch]$tip, [string]$v ) # Copyright (c) ReviewAI Inc. # Disable StrictMode in this script Set-StrictMode -Off # Detect if being run via Invoke-Expression $IS_EXECUTED_FROM_IEX = ($null -eq $MyInvocation.MyCommand.Path) function Write-InstallInfo { param( [Parameter(Mandatory = $True, Position = 0)] [String] $String, [Parameter(Mandatory = $False, Position = 1)] [System.ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor ) $backup = $host.UI.RawUI.ForegroundColor if ($ForegroundColor -ne $host.UI.RawUI.ForegroundColor) { $host.UI.RawUI.ForegroundColor = $ForegroundColor } Write-Output "$String" $host.UI.RawUI.ForegroundColor = $backup } function Test-IsAdministrator { return ([Security.Principal.WindowsPrincipal]` [Security.Principal.WindowsIdentity]::GetCurrent()` ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Get-Architecture { $arch = [System.Environment]::GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") switch ($arch) { "AMD64" { return "amd64" } "ARM64" { return "arm64" } default { Write-InstallInfo "Unsupported architecture: $arch" -ForegroundColor DarkRed exit 1 } } } function Get-Downloader { $downloadSession = New-Object System.Net.WebClient # Enable TLS 1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 return $downloadSession } function Download-File { param( [string]$url, [string]$file ) try { $downloader = Get-Downloader $downloader.DownloadFile($url, $file) return $true } catch [System.Net.WebException] { $response = $_.Exception.Response if ($response -and $response.StatusCode -eq [System.Net.HttpStatusCode]::NotFound) { Write-InstallInfo "Error: File not found at $url" -ForegroundColor DarkRed return $false } throw } } function Get-FileHash256 { param( [string]$file ) try { $stream = [System.IO.File]::OpenRead($file) $sha256 = [System.Security.Cryptography.SHA256]::Create() $hash = [System.BitConverter]::ToString($sha256.ComputeHash($stream)).Replace("-", "").ToLower() return $hash } finally { if ($null -ne $stream) { $stream.Dispose() } if ($null -ne $sha256) { $sha256.Dispose() } } } function Publish-Env { if (-not ('Win32.NativeMethods' -as [Type])) { Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @' [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); '@ } $HWND_BROADCAST = [IntPtr] 0xffff $WM_SETTINGCHANGE = 0x1a $result = [UIntPtr]::Zero [Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref] $result ) | Out-Null } function Get-Env { param( [String] $name, [Switch] $global ) $regPath = if ($global) { 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' } else { 'HKCU:\Environment' } try { $value = Get-ItemProperty -Path $regPath -Name $name -ErrorAction SilentlyContinue return $value.$name } catch { return $null } } function Write-Env { param( [String] $name, [String] $val, [Switch] $global ) $regPath = if ($global) { 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' } else { 'HKCU:\Environment' } if ($null -eq $val) { Remove-ItemProperty -Path $regPath -Name $name -ErrorAction SilentlyContinue } else { Set-ItemProperty -Path $regPath -Name $name -Value $val } Publish-Env } function Install-ReviewBinary { Write-InstallInfo 'Initializing installation...' # Check if running as administrator if (-not (Test-IsAdministrator)) { Write-InstallInfo "This script requires administrator privileges. Please run as administrator." -ForegroundColor DarkRed if ($IS_EXECUTED_FROM_IEX) { break } else { exit 1 } } # Validate version flags if ($tip -and $v) { Write-InstallInfo "Error: Cannot use both -tip and -v flags together" -ForegroundColor DarkRed exit 1 } if ($v -and -not [string]::IsNullOrEmpty($v)) { if ($v -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+$') { Write-InstallInfo "Error: Version must be in format vX.Y.Z (e.g., v0.4.2)" -ForegroundColor DarkRed exit 1 } } $arch = Get-Architecture Write-InstallInfo "Detected architecture: $arch" # Create temp directory $tempDir = [System.IO.Path]::GetTempPath() + [System.Guid]::NewGuid().ToString() New-Item -ItemType Directory -Path $tempDir | Out-Null try { # Determine version path based on flags $versionPath = if ($tip) { "tip" } elseif ($v) { $v } else { "latest" } Write-InstallInfo "Using $versionPath version" # Download binary $binaryUrl = "https://releases.review.ai/review/$versionPath/windows-$arch/review.exe" $binaryPath = Join-Path $tempDir "review.exe" Write-InstallInfo "Downloading review binary from $binaryUrl" if (-not (Download-File $binaryUrl $binaryPath)) { Write-InstallInfo "Installation failed: Could not download the binary for your architecture ($arch)" -ForegroundColor DarkRed exit 1 } # Download SHA256 $shaUrl = "https://releases.review.ai/review/$versionPath/windows-$arch/review.exe.sha256" $shaPath = Join-Path $tempDir "review.exe.sha256" Write-InstallInfo "Downloading SHA256 checksum from $shaUrl" if (-not (Download-File $shaUrl $shaPath)) { Write-InstallInfo "Installation failed: Could not download the checksum file" -ForegroundColor DarkRed exit 1 } # Verify SHA256 $expectedHashLine = [System.IO.File]::ReadAllText($shaPath, [System.Text.Encoding]::UTF8).Replace("`r`n", "`n").Trim() $expectedHash = $expectedHashLine.Split(' ')[0].Trim() $actualHash = Get-FileHash256 $binaryPath if ($actualHash -ne $expectedHash) { Write-InstallInfo "SHA256 checksum verification failed" -ForegroundColor DarkRed Write-InstallInfo "Expected: $expectedHash" Write-InstallInfo "Got: $actualHash" exit 1 } Write-InstallInfo "SHA256 checksum verified successfully" -ForegroundColor DarkGreen # Install binary $installDir = "$env:ProgramFiles\ReviewAI" $installPath = "$installDir\review.exe" # Create install directory if it doesn't exist if (-not (Test-Path $installDir)) { New-Item -ItemType Directory -Path $installDir | Out-Null } # Copy binary to install location Write-InstallInfo "Installing review to $installPath" Copy-Item -Path $binaryPath -Destination $installPath -Force # Add to PATH if not already present $currentPath = Get-Env 'PATH' -global if ($currentPath -notlike "*$installDir*") { Write-InstallInfo "Adding $installDir to system PATH" Write-Env 'PATH' "$currentPath;$installDir" -global # Update current session's PATH as well $env:PATH = "$installDir;$env:PATH" } # Run initial login Write-InstallInfo "Running initial login..." & "$installPath" login --if-not-logged-in Write-InstallInfo "`nReview installed!" -ForegroundColor Magenta Write-InstallInfo "The installer has attempted to update your PATH." Write-InstallInfo "If 'review' is not available in your terminal, please close it and open a new one." Write-InstallInfo " > review" -ForegroundColor Green } finally { # Cleanup if (Test-Path $tempDir) { Remove-Item -Path $tempDir -Recurse -Force } } } # Main execution $ErrorActionPreference = 'Stop' Install-ReviewBinary