# PiCode public installer (Windows PowerShell) — one command, auto prerequisites. # irm https://pigpt.ir/install-picode.ps1 | iex # powershell -ExecutionPolicy Bypass -Command "irm https://pigpt.ir/install-picode.ps1 | iex" # # Auto-installs Python 3.12 if missing/too old (winget -> official per-user quiet installer). # No monorepo / admin required in the common case. [CmdletBinding()] param( [string]$VenvPath = "", [string]$ApiBase = $(if ($env:PICODE_API_URL) { $env:PICODE_API_URL } else { "https://pigpt.ir" }), [string]$DownloadBase = $(if ($env:PICODE_DOWNLOAD_BASE) { $env:PICODE_DOWNLOAD_BASE } else { "https://pigpt.ir/downloads/picode" }), [string]$WheelName = $(if ($env:PICODE_WHEEL) { $env:PICODE_WHEEL } else { "" }), [string]$PythonVersion = "3.12.10", [switch]$SkipPythonInstall ) $ErrorActionPreference = "Stop" $CliName = "picode" $DisplayName = "PiCode" $MinMajor = 3 $MinMinor = 10 function Write-Info([string]$Message) { Write-Host $Message } function Write-Die([string]$Message) { throw ("error: {0}" -f $Message) } function Refresh-SessionPath { $machine = [Environment]::GetEnvironmentVariable("Path", "Machine") $user = [Environment]::GetEnvironmentVariable("Path", "User") $parts = @() if ($user) { $parts += $user } if ($machine) { $parts += $machine } $env:Path = ($parts -join ";") $extras = @( (Join-Path $env:LOCALAPPDATA "Programs\Python\Python312"), (Join-Path $env:LOCALAPPDATA "Programs\Python\Python312\Scripts"), (Join-Path $env:LOCALAPPDATA "Programs\Python\Launcher"), "C:\Program Files\Python312", "C:\Program Files\Python312\Scripts", "C:\Program Files\Python311", "C:\Program Files\Python311\Scripts", "C:\Windows" ) foreach ($p in $extras) { if ((Test-Path $p) -and ((";$env:Path;") -notlike ("*;{0};*" -f $p))) { $env:Path = "{0};{1}" -f $p, $env:Path } } } function Test-PythonCandidate { param([string]$Exe, [string[]]$PyArgs) try { $out = & $Exe @($PyArgs + @("-c", "import sys; print('%d.%d'%sys.version_info[:2]); print(sys.executable)")) 2>$null if ($LASTEXITCODE -ne 0 -or -not $out) { return $null } $lines = @($out | ForEach-Object { "$_".Trim() } | Where-Object { $_ }) if ($lines.Count -lt 1) { return $null } $ver = $lines[0] $verParts = $ver.Split(".") if ($verParts.Count -lt 2) { return $null } $major = [int]$verParts[0]; $minor = [int]$verParts[1] if ($major -gt $MinMajor -or ($major -eq $MinMajor -and $minor -ge $MinMinor)) { $exePath = if ($lines.Count -ge 2) { $lines[1] } else { $Exe } return @{ Exe = $Exe; Args = $PyArgs; Version = $ver; Executable = $exePath } } } catch { } return $null } function Find-Python { Refresh-SessionPath $ordered = @( @{ Exe = "py"; Args = @("-3.12") }, @{ Exe = "py"; Args = @("-3") }, @{ Exe = "python"; Args = @() }, @{ Exe = "python3"; Args = @() } ) foreach ($c in $ordered) { if (-not (Get-Command $c.Exe -ErrorAction SilentlyContinue)) { continue } $hit = Test-PythonCandidate -Exe $c.Exe -PyArgs $c.Args if ($hit) { return $hit } } $abs = @( (Join-Path $env:LOCALAPPDATA "Programs\Python\Python312\python.exe"), "C:\Program Files\Python312\python.exe", "C:\Program Files\Python311\python.exe" ) foreach ($p in $abs) { if (Test-Path $p) { $hit = Test-PythonCandidate -Exe $p -PyArgs @() if ($hit) { return $hit } } } return $null } function Find-Winget { if (Get-Command winget -ErrorAction SilentlyContinue) { return (Get-Command winget).Source } $apps = Join-Path $env:LOCALAPPDATA "Microsoft\WindowsApps\winget.exe" if (Test-Path $apps) { return $apps } $wa = Get-ChildItem "$env:ProgramFiles\WindowsApps" -Filter "winget.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($wa) { return $wa.FullName } return $null } function Install-PythonViaWinget { $winget = Find-Winget if (-not $winget) { return $false } Write-Info "Installing Python 3.12 via winget (per-user)..." $wgArgs = @( "install", "Python.Python.3.12", "--accept-package-agreements", "--accept-source-agreements", "--disable-interactivity", "--scope", "user" ) try { & $winget @wgArgs if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq -1978335189) { return $true } Write-Host ("winget user-scope exit {0} - retrying without --scope..." -f $LASTEXITCODE) $wgArgs2 = @( "install", "Python.Python.3.12", "--accept-package-agreements", "--accept-source-agreements", "--disable-interactivity" ) & $winget @wgArgs2 if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq -1978335189) { return $true } } catch { Write-Host ("winget failed: {0}" -f $_.Exception.Message) } return $false } function Get-PythonInstallerUrl { param([string]$Version) $arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "" } $name = if ($arch) { "python-$Version-$arch.exe" } else { "python-$Version.exe" } return "https://www.python.org/ftp/python/$Version/$name" } function Install-PythonViaOfficialInstaller { param([string]$Version) $url = Get-PythonInstallerUrl -Version $Version $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("python-setup-" + [guid]::NewGuid().ToString("N") + ".exe") Write-Info ("Downloading official Python {0} installer..." -f $Version) Write-Host (" {0}" -f $url) try { Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing } catch { Write-Die ("Cannot download Python installer (network blocked?). {0}" -f $url) } $quietArgs = "/quiet InstallAllUsers=0 PrependPath=1 Include_pip=1 Include_launcher=1 Include_test=0 SimpleInstall=1" Write-Info "Running silent per-user Python install..." try { $p = Start-Process -FilePath $tmp -ArgumentList $quietArgs -Wait -PassThru -NoNewWindow if ($p.ExitCode -ne 0) { Write-Host ("Per-user install exit {0}; retrying quiet flags..." -f $p.ExitCode) $p2 = Start-Process -FilePath $tmp -ArgumentList "/quiet InstallAllUsers=0 PrependPath=1 Include_pip=1 Include_launcher=1" -Wait -PassThru -NoNewWindow if ($p2.ExitCode -ne 0) { return $false } } return $true } catch { Write-Host ("Official installer failed: {0}" -f $_.Exception.Message) return $false } finally { Remove-Item -Force $tmp -ErrorAction SilentlyContinue } } function Ensure-Python { $py = Find-Python if ($py) { return $py } $need = "{0}.{1}+" -f $MinMajor, $MinMinor if ($SkipPythonInstall) { Write-Die ("Python {0} not found and -SkipPythonInstall set." -f $need) } Write-Info ("Python {0} not found - installing automatically..." -f $need) $ok = Install-PythonViaWinget Refresh-SessionPath $py = Find-Python if ($py) { return $py } if (-not $ok) { Write-Host "winget unavailable or failed - using python.org installer." } $ok2 = Install-PythonViaOfficialInstaller -Version $PythonVersion Refresh-SessionPath Start-Sleep -Seconds 2 Refresh-SessionPath $py = Find-Python if ($py) { return $py } if (-not $ok2) { Write-Die "Could not auto-install Python. Open https://www.python.org/downloads/ , install 3.12, check Add python.exe to PATH, then re-run: irm https://pigpt.ir/install-picode.ps1 | iex" } Write-Die "Python installed but not yet on PATH in this session. Close this window, open a new PowerShell, then: irm https://pigpt.ir/install-picode.ps1 | iex" } function Resolve-LatestWheel { param([string]$Base, [string]$Preferred) if ($Preferred) { return $Preferred } Write-Info ("Resolving latest wheel from {0}/ ..." -f $Base) try { $resp = Invoke-WebRequest -Uri ($Base.TrimEnd('/') + "/") -UseBasicParsing } catch { Write-Die ("Cannot reach package index (network blocked?): {0}/" -f $Base) } $matches = [regex]::Matches($resp.Content, 'picode-([0-9]+(?:\.[0-9]+)*)-py3-none-any\.whl') if ($matches.Count -eq 0) { return "picode-0.2.1-py3-none-any.whl" } $best = $null $bestKey = $null foreach ($m in $matches) { $name = $m.Value $ver = $m.Groups[1].Value $key = [version]$ver if ($null -eq $bestKey -or $key -gt $bestKey) { $bestKey = $key $best = $name } } return $best } function Ensure-UserPath([string]$Dir) { $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not $userPath) { $userPath = "" } $note = "already on user PATH" if ((";$userPath;") -notlike ("*;{0};*" -f $Dir)) { $newUserPath = if ($userPath) { "{0};{1}" -f $Dir, $userPath } else { $Dir } [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") $note = "added to user PATH" } if ((";$env:Path;") -notlike ("*;{0};*" -f $Dir)) { $env:Path = "{0};{1}" -f $Dir, $env:Path } return $note } # --- main --- Write-Info "$DisplayName installer (Windows)" Write-Host ("API default: {0}" -f $ApiBase) $py = Ensure-Python Write-Info ("Using Python {0} ({1})" -f $py.Version, $py.Executable) if ($VenvPath) { $VenvDir = $VenvPath } else { $VenvDir = Join-Path $env:USERPROFILE ".picode-venv" } $wheelFileName = Resolve-LatestWheel -Base $DownloadBase -Preferred $WheelName $pkgUrl = ($DownloadBase.TrimEnd('/') + "/" + $wheelFileName) $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("picode-install-" + [guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Force -Path $tmp | Out-Null try { $pkgFile = Join-Path $tmp $wheelFileName Write-Info ("Downloading {0}" -f $pkgUrl) try { Invoke-WebRequest -Uri $pkgUrl -OutFile $pkgFile -UseBasicParsing } catch { Write-Die ("Cannot download PiCode wheel (network blocked?): {0}" -f $pkgUrl) } $bytes = [System.IO.File]::ReadAllBytes($pkgFile) $probeLen = [Math]::Min(64, $bytes.Length) $probe = [System.Text.Encoding]::ASCII.GetString($bytes, 0, $probeLen).ToLowerInvariant() if ($probe.Contains("