# Installs the Caprock desktop app on Windows: # irm https://caprock.dev/win.ps1 | iex # # It downloads the same installer the site's button links to, runs it # silently for your user only (no administrator prompt) and opens Caprock. # A file fetched by PowerShell carries no "downloaded from the internet" mark, # so SmartScreen does not stop it. Run it again to update. # Source: https://github.com/dspv/caprock # # CAPROCK_NO_OPEN=1 skips the open; NO_COLOR=1 prints plain lines. # Works in Windows PowerShell 5.1 and PowerShell 7. The file is plain ASCII on # purpose (symbols are built from char codes), so it reads the same whatever # encoding PowerShell assumes. It never calls exit: under iex that would close # the window you ran it in. & { $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $repo = 'https://github.com/dspv/caprock' $url = "$repo/releases/latest/download/Caprock-Windows-setup.exe" $setup = Join-Path $env:TEMP 'Caprock-Windows-setup.exe' $start = Get-Date # Colour and a live bar on a console, plain lines when output is redirected. $tty = -not [Console]::IsOutputRedirected -and -not $env:NO_COLOR # Windows Terminal and PowerShell 7 fonts carry the check mark; the classic # console's fonts may not, so it gets a square root sign that every one has. $fancy = [bool]($env:WT_SESSION -or $env:TERM_PROGRAM -or $PSVersionTable.PSVersion.Major -ge 7) $ok = if (-not $tty) { '+' } elseif ($fancy) { [string][char]0x2713 } else { [string][char]0x221A } $full = [string][char]0x2588; $empty = [string][char]0x2591 $down = if ($tty) { [string][char]0x2193 } else { 'v' } $mark = if ($tty) { [string][char]0x25B2 + ' ' } else { '' } $dash = if ($tty) { [string][char]0x2014 } else { '-' } $dot = if ($tty) { ' ' + [char]0x00B7 + ' ' } else { ' - ' } function Out-Part([string]$text, [string]$color) { if ($tty -and $color) { Write-Host $text -NoNewline -ForegroundColor $color } else { Write-Host $text -NoNewline } } function Out-Step([string]$label, [string]$detail) { Out-Part ' '; Out-Part $ok 'Green'; Out-Part (' ' + $label.PadRight(27) + ' ') Out-Part $detail 'DarkGray'; Write-Host '' } function Out-Fail([string]$message) { if ($tty) { Write-Host ("`r" + (' ' * 78) + "`r") -NoNewline } Write-Host ''; Write-Host " x $message" -ForegroundColor Red; Write-Host '' } function Format-MB([long]$bytes) { ('{0:0.0}' -f ($bytes / 1MB)).Replace(',', '.') } function Out-Bar([long]$have, $total, [string]$label) { Out-Part ("`r ") Out-Part $down 'DarkYellow' Out-Part (' ' + $label.PadRight(27) + ' ') if ($total -and $total -gt 0) { $pct = [Math]::Min(100, [int][Math]::Floor($have * 100 / $total)) $fill = [int][Math]::Floor($pct * 24 / 100) Out-Part ($full * $fill) 'DarkYellow'; Out-Part ($empty * (24 - $fill)) 'DarkGray' Out-Part (' {0,3}% ' -f $pct) Out-Part ((Format-MB $have) + '/' + (Format-MB $total) + ' MB ') 'DarkGray' } else { Out-Part ((Format-MB $have) + ' MB ') 'DarkGray' } } Write-Host '' Out-Part (' ' + $mark + 'caprock') 'DarkYellow'; Out-Part (' ' + $dash + ' installing the desktop app') 'DarkGray' Write-Host ''; Write-Host '' try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 Add-Type -AssemblyName System.Net.Http $os = [Environment]::OSVersion.Version $arch = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'x86' } $win = if ($os.Major -eq 10 -and $os.Build -ge 22000) { 'Windows 11' } elseif ($os.Major -eq 10) { 'Windows 10' } else { "Windows $($os.Major).$($os.Minor)" } Out-Step 'Checked your PC' ("$win (build $($os.Build))$dot$arch$dot" + "PowerShell $($PSVersionTable.PSVersion.Major)") # The version, from where /releases/latest redirects to (best effort). $version = '' try { $h = New-Object System.Net.Http.HttpClientHandler $h.AllowAutoRedirect = $false $c = New-Object System.Net.Http.HttpClient($h) $r = $c.GetAsync("$repo/releases/latest").GetAwaiter().GetResult() if ($r.Headers.Location) { $version = ($r.Headers.Location.ToString() -split '/v')[-1] } $c.Dispose() } catch { $version = '' } $suffix = if ($version) { " $version" } else { '' } $label = "Downloading Caprock$suffix" # Stream the installer to disk, drawing the bar at most ten times a second. $client = New-Object System.Net.Http.HttpClient $client.Timeout = [TimeSpan]::FromMinutes(15) $client.DefaultRequestHeaders.UserAgent.ParseAdd('caprock-installer') $resp = $client.GetAsync($url, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).GetAwaiter().GetResult() if (-not $resp.IsSuccessStatusCode) { throw "The download failed (HTTP $([int]$resp.StatusCode)). Run the line again." } $total = $resp.Content.Headers.ContentLength $in = $resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult() $out = [IO.File]::Create($setup) $have = [long]0 try { $buf = New-Object byte[] 81920 $tick = [Diagnostics.Stopwatch]::StartNew() if ($tty) { Out-Bar 0 $total $label } while (($n = $in.Read($buf, 0, $buf.Length)) -gt 0) { $out.Write($buf, 0, $n) $have += $n if ($tty -and $tick.ElapsedMilliseconds -ge 100) { Out-Bar $have $total $label; $tick.Restart() } } if ($tty) { Out-Bar $have $total $label } } finally { $out.Dispose(); $in.Dispose(); $client.Dispose() } if ($tty) { Write-Host ("`r" + (' ' * 78) + "`r") -NoNewline } if ($total -and $have -ne $total) { throw 'The download was cut short. Check your connection and run the line again.' } Out-Step "Downloaded Caprock$suffix" ((Format-MB $have) + ' MB') $p = Start-Process -FilePath $setup -ArgumentList '/S' -Wait -PassThru Remove-Item $setup -Force -ErrorAction SilentlyContinue if ($p.ExitCode -ne 0) { throw "The installer stopped with code $($p.ExitCode). Close Caprock if it is open and run the line again." } $exe = Join-Path $env:LOCALAPPDATA 'Caprock\caprock-app.exe' if (Test-Path $exe) { Out-Step 'Installed' $exe if (-not $env:CAPROCK_NO_OPEN) { Start-Process $exe; Out-Step 'Opened Caprock' '' } } else { Out-Step 'Installed' 'open Caprock from the Start menu' } $secs = [int]((Get-Date) - $start).TotalSeconds Write-Host '' Out-Part " Done in ${secs}s." ''; Out-Part ' Run this line again to update.' 'DarkGray' Write-Host ''; Write-Host '' } catch { Remove-Item $setup -Force -ErrorAction SilentlyContinue $msg = $_.Exception.Message if ($_.Exception.InnerException) { $msg = $_.Exception.InnerException.Message } Out-Fail $msg } }