<# ENCODING RULE (2026-09-15, found on the ThinkPad): this file must stay pure ASCII. Windows PowerShell 5.1 reads a BOM-less script as ANSI, so any non-ASCII byte (an em dash, a curly quote, a box-drawing line) corrupts the token stream and the whole script fails to parse before it runs a single line. Enforced by build/static_check.sh. #> <# .SYNOPSIS Elicana installer - Windows (build order rev 2, Ameet 2026-09-14: one-command install for all three OSes, no code signing). .DESCRIPTION Served at https://elicana.ai/install.ps1 and run as: irm https://elicana.ai/install.ps1 | iex or, to pass parameters, download it first: iwr https://elicana.ai/install.ps1 -OutFile install.ps1; .\install.ps1 Downloads the windows-portable build through the counting redirect (https://api.elicana.ai/api/v1/dl/windows-portable), verifies it against the published SHA256SUMS, installs it to %LOCALAPPDATA%\Elicana\elicana.exe, strips the mark-of-the-web so it will run without an extra click-through, adds the install directory to the user's PATH, and launches the first-run setup wizard. No admin rights are used or required - everything here is per-user (HKCU / %LOCALAPPDATA%). .NOTES Compatible with Windows PowerShell 5.1+ (the version that ships with Windows 10/11 out of the box) - deliberately avoids PowerShell-7-only syntax (ternary ?:, null-coalescing ??, etc.) so it runs with no prerequisite install. SmartScreen - what is KNOWN vs. what NEEDS the real-hardware check: KNOWN (corrected 2026-09-14): Invoke-WebRequest -OutFile does NOT attach a Zone.Identifier alternate data stream - the mark-of-the-web is applied by browsers and mail/IM clients via the Attachment Execution Services (IAttachmentExecute) shell API, which .NET's HTTP client does not call. A file this script downloads and writes with -OutFile is therefore unmarked to begin with. Unblock-File below is consequently a harmless no-op here in the common case - kept for safety in case some intermediary (an AV product, a proxy, a future code path) does end up marking the file, since running it unconditionally costs nothing and correctly does nothing when there is no stream to remove. NEEDS HARDWARE VERIFICATION: whether SmartScreen's app-reputation check fires when this script launches the exe directly (via &) - since the exe was never MOTW-tagged, the classic "Windows protected your PC" double-click prompt (which keys off MOTW) may not trigger at all here, but SmartScreen's app-reputation service and Microsoft Defender's real-time/cloud-delivered protection can still flag an unsigned, freshly-downloaded, unrecognized exe on first execution independent of MOTW state (Locked Decisions: no code signing, so the binary is permanently "unrecognized publisher"). Do not assert either way in user-facing copy until this has been run on a real, default-configured Windows 10/11 machine. .PARAMETER DlBase Base URL for the counting download redirect. Override for testing. .PARAMETER SumsUrl SHA256SUMS location to verify against. Override for testing. #> [CmdletBinding()] param( [string]$DlBase = "https://api.elicana.ai/api/v1/dl", [string]$SumsUrl = "https://downloads.elicana.ai/latest/SHA256SUMS" ) $ErrorActionPreference = "Stop" # Older Windows 10 defaults (and Windows PowerShell 5.1's .NET Framework # runtime in general) can leave TLS 1.2 disabled in SecurityProtocol, which # makes Invoke-WebRequest fail against modern TLS-1.2-only endpoints. Force # it on unconditionally - cheap, and this script has no other network calls # before it that could be affected. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 # Skip the WriteProgress rendering overhead for Invoke-WebRequest - this can # otherwise turn a fast download into a slow one, especially over a remote # session or a pipe (irm ... | iex). $ProgressPreference = 'SilentlyContinue' $Platform = "windows-portable" $FileName = "elicana-windows-amd64-portable.exe" $InstallDir = Join-Path $env:LOCALAPPDATA "Elicana" $Target = Join-Path $InstallDir "elicana.exe" if (-not (Test-Path -LiteralPath $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null } $WorkDir = Join-Path $env:TEMP ("elicana-install-" + [System.Guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null try { $DownloadedExe = Join-Path $WorkDir $FileName $SumsFile = Join-Path $WorkDir "SHA256SUMS" Write-Host "Downloading Elicana ($Platform)..." Invoke-WebRequest -Uri "$DlBase/$Platform" -OutFile $DownloadedExe -UseBasicParsing Write-Host "Verifying checksum against $SumsUrl ..." Invoke-WebRequest -Uri $SumsUrl -OutFile $SumsFile -UseBasicParsing $ExpectedHash = $null foreach ($line in Get-Content -LiteralPath $SumsFile) { # SHA256SUMS lines: " " (sha256sum/shasum format, # one or two spaces between fields). $parts = $line -split '\s+', 3 if ($parts.Length -ge 2 -and $parts[1] -eq $FileName) { $ExpectedHash = $parts[0].ToLowerInvariant() break } } if (-not $ExpectedHash) { Write-Error "No checksum entry for '$FileName' in $SumsUrl" exit 1 } $ActualHash = (Get-FileHash -LiteralPath $DownloadedExe -Algorithm SHA256).Hash.ToLowerInvariant() if ($ActualHash -ne $ExpectedHash) { Write-Error "Checksum mismatch for $FileName`nexpected: $ExpectedHash`nactual: $ActualHash" exit 1 } Write-Host "Checksum OK ($FileName)" try { Copy-Item -LiteralPath $DownloadedExe -Destination $Target -Force } catch { throw "Could not replace $Target - Elicana appears to be running. Quit Elicana (tray icon) or stop the service, then re-run this installer.`n$_" } } finally { Remove-Item -LiteralPath $WorkDir -Recurse -Force -ErrorAction SilentlyContinue } # Strip the mark-of-the-web (Zone.Identifier) picked up during download - see # the SmartScreen note above for what this does and does not guarantee. Unblock-File -LiteralPath $Target Write-Host "Elicana installed to: $Target" # -- Add the install dir to the user's PATH (no admin required - HKCU only) -- $UserPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($null -eq $UserPath) { $UserPath = "" } $PathEntries = $UserPath -split ";" | Where-Object { $_ -ne "" } $AlreadyOnPath = $PathEntries | Where-Object { $_.TrimEnd("\") -ieq $InstallDir.TrimEnd("\") } if (-not $AlreadyOnPath) { $NewUserPath = if ($UserPath -eq "") { $InstallDir } else { "$UserPath;$InstallDir" } [Environment]::SetEnvironmentVariable("Path", $NewUserPath, "User") Write-Host "" Write-Host "Added $InstallDir to your user PATH." Write-Host "Open a new terminal for this to take effect there." } # Make it available in *this* session immediately too, so the wizard launch # below and any immediate re-run of 'elicana' both work without a restart. if ($env:Path -notlike "*$InstallDir*") { $env:Path = "$env:Path;$InstallDir" } # -- First-run setup ---------------------------------------------------------- Write-Host "" Write-Host "Starting first-time setup wizard..." Write-Host "(The wizard opens in your browser. If it doesn't open automatically," Write-Host " copy the URL printed below and paste it into your browser.)" Write-Host "" & $Target setup