install.ps1

Windows (PowerShell) install script

install.ps1
# SynthOrg CLI installer for Windows.
# Usage: irm https://synthorg.io/get/install.ps1 | iex
#
# Environment variables:
#   SYNTHORG_VERSION  -- specific version to install (default: latest)
#   INSTALL_DIR       -- installation directory (default: $env:LOCALAPPDATA\synthorg\bin)

$ErrorActionPreference = "Stop"

$Repo = "Aureliolo/synthorg"
$BinaryName = "synthorg.exe"
$InstallDir = if ($env:INSTALL_DIR) { $env:INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "synthorg\bin" }

# --- Resolve version ---

if (-not $env:SYNTHORG_VERSION) {
    Write-Host "Fetching latest release..."
    $Release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest"
    $Version = $Release.tag_name
} else {
    $Version = $env:SYNTHORG_VERSION
}

# Validate version string to prevent injection.
if ($Version -notmatch '^v\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$') {
    Write-Error "Invalid version string: $Version"
    exit 1
}

Write-Host "Installing SynthOrg CLI $Version..."

# --- Detect architecture ---
# Primary: .NET RuntimeInformation (PowerShell 5.1+ with .NET 4.7.1+).
# Fallback: PROCESSOR_ARCHITECTURE env var (always available on Windows).
# The RuntimeInformation API can return $null on older .NET runtimes or
# certain system configurations (see #521).

$OsArch = $null
try {
    $OsArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
} catch {
    # Type not available - fall through to env var detection.
    Write-Verbose "RuntimeInformation unavailable; using PROCESSOR_ARCHITECTURE fallback."
}

if ($null -ne $OsArch) {
    $WinArch = switch ($OsArch) {
        ([System.Runtime.InteropServices.Architecture]::X64)   { "amd64" }
        ([System.Runtime.InteropServices.Architecture]::Arm64) { "arm64" }
        default { Write-Error "Unsupported architecture: $OsArch"; exit 1 }
    }
} else {
    # PROCESSOR_ARCHITEW6432 is set when running 32-bit PowerShell on 64-bit
    # Windows (WOW64). It contains the real OS architecture.
    $ArchEnv = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
    $WinArch = switch ($ArchEnv) {
        "AMD64" { "amd64" }
        "ARM64" { "arm64" }
        default { Write-Error "Unsupported architecture: $ArchEnv"; exit 1 }
    }
}

# --- Download ---

$ArchiveName = "synthorg_windows_$WinArch.zip"
$DownloadUrl = "https://github.com/$Repo/releases/download/$Version/$ArchiveName"
$ChecksumsUrl = "https://github.com/$Repo/releases/download/$Version/checksums.txt"

$TmpDir = Join-Path $env:TEMP "synthorg-install-$(Get-Random)"
New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null

try {
    Write-Host "Downloading $DownloadUrl..."
    Invoke-WebRequest -Uri $DownloadUrl -OutFile (Join-Path $TmpDir $ArchiveName)
    Invoke-WebRequest -Uri $ChecksumsUrl -OutFile (Join-Path $TmpDir "checksums.txt")

    # --- Verify checksum ---

    Write-Host "Verifying checksum..."
    $line = Get-Content (Join-Path $TmpDir "checksums.txt") | Where-Object { ($_ -split '\s+')[1] -eq $ArchiveName }
    $ExpectedHash = ($line -split '\s+')[0].Trim().ToLower()

    if (-not $ExpectedHash) {
        throw "No checksum found for $ArchiveName. Aborting."
    }

    $ActualHash = (Get-FileHash -Path (Join-Path $TmpDir $ArchiveName) -Algorithm SHA256).Hash.ToLower()

    if ($ExpectedHash -ne $ActualHash) {
        throw "Checksum mismatch: expected $ExpectedHash, got $ActualHash"
    }

    # --- Extract and install ---

    Write-Host "Extracting..."
    Expand-Archive -Path (Join-Path $TmpDir $ArchiveName) -DestinationPath $TmpDir -Force

    Write-Host "Installing to $InstallDir..."
    New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
    Move-Item -Path (Join-Path $TmpDir $BinaryName) -Destination (Join-Path $InstallDir $BinaryName) -Force

    # Add to PATH if not already there (exact entry match, not substring).
    $NormalizedInstallDir = $InstallDir.TrimEnd('\')
    $UserPath = [Environment]::GetEnvironmentVariable("PATH", "User")
    $UserPathEntries = ($UserPath -split ';') | ForEach-Object { $_.TrimEnd('\') } | Where-Object { $_ }
    if ($UserPathEntries -notcontains $NormalizedInstallDir) {
        $NewUserPath = if ($UserPath) { "$UserPath;$InstallDir" } else { $InstallDir }
        [Environment]::SetEnvironmentVariable("PATH", $NewUserPath, "User")
        Write-Host "Added $InstallDir to user PATH."
    }
    $ProcessPathEntries = ($env:PATH -split ';') | ForEach-Object { $_.TrimEnd('\') } | Where-Object { $_ }
    if ($ProcessPathEntries -notcontains $NormalizedInstallDir) {
        $env:PATH = "$env:PATH;$InstallDir"
    }

    & (Join-Path $InstallDir $BinaryName) version
    Write-Host ""
    Write-Host "SynthOrg CLI installed successfully. Run 'synthorg init' to get started."
} finally {
    Remove-Item -Path $TmpDir -Recurse -Force -ErrorAction SilentlyContinue
}