Skip to content
89 changes: 56 additions & 33 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,17 @@ function Write-Die {
# ── Platform / architecture detection ─────────────────────────────────────────

function Get-PlatformTriple {
$arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
# RuntimeInformation.ProcessArchitecture can be unavailable in Windows PowerShell 5.1.
# WOW64 exposes the native architecture separately from the 32-bit process.
$arch = $env:PROCESSOR_ARCHITEW6432
if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE }
if (-not $arch) {
Write-Die "could not detect Windows architecture: PROCESSOR_ARCHITEW6432 and PROCESSOR_ARCHITECTURE are empty"
}

switch ($arch) {
"X64" { $archId = "x64" }
"Arm64" { $archId = "arm64" }
"AMD64" { $archId = "x64" }
"ARM64" { $archId = "arm64" }
default { Write-Die "unsupported architecture: $arch (x64 and ARM64 only)" }
}

Expand All @@ -67,25 +73,21 @@ function Add-ToUserPath {

$currentUserPath = @([Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ })

if ($currentUserPath -contains $Dir) {
Write-Log "$Dir is already in your user PATH"
$newUserPath = (@($Dir) + @($currentUserPath | Where-Object { $_ -ine $Dir })) -join ";"
if (($currentUserPath -join ";") -ceq $newUserPath) {
Write-Log "$Dir is already first in your user PATH"
return
}

$newUserPath = ($currentUserPath + $Dir) -join ";"
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
Write-Log "added ${Dir} to user PATH"
Write-Log "placed ${Dir} first in user PATH"
}

function Add-ToSessionPath {
param([string]$Dir)

$pathEntries = $env:PATH -split ";" | Where-Object { $_ }
if ($pathEntries -contains $Dir) {
return
}

$env:PATH = "$Dir;$env:PATH"
$pathEntries = @($env:PATH -split ";" | Where-Object { $_ -and $_ -ine $Dir })
$env:PATH = (@($Dir) + $pathEntries) -join ";"
}

# ── Git Bash (bash environment) PATH helper ──────────────────────────────────
Expand Down Expand Up @@ -126,14 +128,19 @@ function Install-Binary {
$staged = "$Target.install-$([Guid]::NewGuid().ToString('N'))"
try {
[System.IO.File]::Copy($Source, $staged)
# The running daemon may come from another installation directory.
# Use the downloaded CLI: older versions have broken Windows liveness checks.
# Stop verifies daemon identity and uses the current BSK_HOME.
# Fail closed for both new installs and replacements; never discard daemon metadata here.
& $Source daemon stop
if ($LASTEXITCODE -ne 0) {
$daemonHome = if ($env:BSK_HOME) { $env:BSK_HOME } else { Join-Path $HOME ".bsk" }
$daemonInfoPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Join-Path $daemonHome "daemon.json"))
throw "could not stop bsk daemon; installation was not changed. Stop any running bsk daemon, remove '$daemonInfoPath', then retry the installer."
}
if ([System.IO.File]::Exists($Target)) {
# Use the downloaded CLI: older versions have broken Windows
# liveness checks. Stop verifies daemon identity before terminating it.
& $Source daemon stop
if ($LASTEXITCODE -ne 0) { throw "could not stop bsk daemon; existing installation was not replaced" }
# PowerShell 5.1 converts $null to an empty path for string parameters.
[System.IO.File]::Replace($staged, $Target, [NullString]::Value)
Write-Log "daemon will restart automatically on the next browser command"
}
else {
[System.IO.File]::Move($staged, $Target)
Expand Down Expand Up @@ -166,14 +173,20 @@ function Main {
Write-Log "latest version is ${version}"
}

$archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"

$expectedSha = $null
$platformKey = $platform.PlatformKey
$asset = $null
if ($manifest -and $manifest.assets) {
$expectedSha = $manifest.assets.$platformKey.sha256
$asset = $manifest.assets.$platformKey
}
# ARM64 is not in the current release matrix. Require a published entry
# before attempting it, while preserving legacy x64 installs without a manifest.
if ($platform.ArchId -eq "arm64" -and -not $asset) {
Write-Die "version.json does not list a Windows ARM64 package for bsk $version"
}

$archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"
$expectedSha = if ($asset) { $asset.sha256 } else { $null }
if (-not $expectedSha) {
if (-not $manifest) {
Write-Log "warning: could not fetch version.json; skipping checksum verification"
Expand All @@ -184,17 +197,21 @@ function Main {
}

$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
[System.IO.Directory]::CreateDirectory($tempDir) | Out-Null

try {
$archivePath = Join-Path $tempDir $archiveName

Write-Log "downloading ${downloadUrl}"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing -ErrorAction Stop
# PowerShell 5.1's -OutFile treats brackets as wildcards. Write raw HTTP bytes literally.
$response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing -ErrorAction Stop
try {
[System.IO.File]::WriteAllBytes($archivePath, $response.RawContentStream.ToArray())
} finally { $response.RawContentStream.Dispose() }

if ($expectedSha) {
Write-Log "verifying checksum"
$actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash
$actualSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash
if ($actualSha -ieq $expectedSha) {
Write-Log "checksum OK"
}
Expand All @@ -204,15 +221,15 @@ function Main {
}

Write-Log "extracting ${archiveName}"
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force
# PowerShell 5.1's Expand-Archive treats the destination as a wildcard path.
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($archivePath, $tempDir)

if (-not (Test-Path (Join-Path $tempDir "bsk.exe"))) {
if (-not (Test-Path -LiteralPath (Join-Path $tempDir "bsk.exe"))) {
Write-Die "bsk.exe not found in archive"
}

if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
[System.IO.Directory]::CreateDirectory($InstallDir) | Out-Null

Install-Binary -Source (Join-Path $tempDir "bsk.exe") -Target (Join-Path $InstallDir "bsk.exe")

Expand All @@ -232,12 +249,18 @@ function Main {
& $bskPath --version
if ($LASTEXITCODE -ne 0) { throw "installed bsk failed verification" }

$command = Get-Command bsk -ErrorAction SilentlyContinue
if (-not $command -or $command.CommandType -ne "Application" -or $command.Source -ine $bskPath) {
Write-Log "warning: 'bsk' does not resolve to $bskPath in this session; check Get-Command bsk -All for a conflicting command"
}

Write-Log "done"
Write-Host ""
Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect."
Write-Host "This PATH check covers the current session only; a new terminal may prefer a Machine PATH entry or alias."
Write-Host "In a new PowerShell terminal, run Get-Command bsk -All and confirm the first result is $bskPath."
}
finally {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}

Expand Down
Loading