<# .SYNOPSIS 安装 portbug 客户端(Windows,不需要管理员)。 .DESCRIPTION 装到 %LOCALAPPDATA%\Programs\portbug,把它加进当前用户的 PATH。 全程用户级:不写 Program Files,不写 HKLM,不需要 UAC 提权。 可以从三处取二进制,按顺序尝试: -Source <目录或zip> 本地产物(build.ps1 的输出) -Source 远端 zip 脚本自己所在目录 随发布包一起分发时的默认情况 .EXAMPLE .\install.ps1 -Source ..\dist\0.1.0\windows-amd64 .\install.ps1 -Source ..\dist\0.1.0\windows-amd64 -Node # 顺带把节点装成开机任务 .\install.ps1 -Uninstall #> [CmdletBinding()] param( [string]$Source = "", [string]$Prefix = "", [switch]$Node, [string]$NodeId = "", [string]$NodePorts = "19001-19010", [string]$Center = "", [string]$BaseUrl = "https://portbug.pckee.com", [switch]$NoPath, [switch]$Uninstall ) $ErrorActionPreference = 'Stop' if (-not $Prefix) { $Prefix = Join-Path $env:LOCALAPPDATA 'Programs\portbug' } function Write-Step($msg) { Write-Host " $msg" -ForegroundColor DarkGray } function Write-Ok($msg) { Write-Host " $msg" -ForegroundColor Green } # ---- 卸载 ------------------------------------------------------------------ if ($Uninstall) { Write-Host "卸载 portbug" -ForegroundColor Cyan $task = Get-ScheduledTask -TaskName 'portbugd' -ErrorAction SilentlyContinue if ($task) { Unregister-ScheduledTask -TaskName 'portbugd' -Confirm:$false Write-Ok "已移除计划任务 portbugd" } Get-Process portbug, portbugd, portbugctl -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($userPath) { $kept = @($userPath -split ';' | Where-Object { $_ -and $_.TrimEnd('\') -ne $Prefix.TrimEnd('\') }) [Environment]::SetEnvironmentVariable('Path', ($kept -join ';'), 'User') Write-Ok "已从用户 PATH 移除 $Prefix" } if (Test-Path $Prefix) { Remove-Item $Prefix -Recurse -Force Write-Ok "已删除 $Prefix" } Write-Host "卸载完成。" -ForegroundColor Green return } # ---- 取二进制 -------------------------------------------------------------- Write-Host "安装 portbug" -ForegroundColor Cyan $staging = $null if (-not $Source) { # Piped straight into the shell (irm ... | iex) there is no script file, so # $PSScriptRoot is empty and there is nothing local to copy from - fall back # to the release zip on the site. A release archive shipped with this script # next to the binaries still installs offline. if ($PSScriptRoot -and (Test-Path (Join-Path $PSScriptRoot 'portbug.exe'))) { $Source = $PSScriptRoot } else { $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' } $Source = "$BaseUrl/dl/portbug-windows-$arch.zip" } } if ($Source -match '^https?://') { $staging = Join-Path ([IO.Path]::GetTempPath()) ("portbug-dl-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) New-Item -ItemType Directory -Force -Path $staging | Out-Null $zip = Join-Path $staging 'portbug.zip' Write-Step "下载 $Source" Invoke-WebRequest -Uri $Source -OutFile $zip -UseBasicParsing Expand-Archive -Path $zip -DestinationPath $staging -Force $srcDir = $staging } elseif ((Test-Path $Source) -and ($Source -like '*.zip')) { $staging = Join-Path ([IO.Path]::GetTempPath()) ("portbug-zip-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) Expand-Archive -Path $Source -DestinationPath $staging -Force $srcDir = $staging } elseif (Test-Path $Source) { $srcDir = (Resolve-Path $Source).Path } else { throw "找不到来源:$Source" } # zip 里可能多包一层目录,往下找一层就够。 if (-not (Test-Path (Join-Path $srcDir 'portbug.exe'))) { $found = Get-ChildItem $srcDir -Recurse -Filter 'portbug.exe' -File | Select-Object -First 1 if (-not $found) { throw "在 $srcDir 里找不到 portbug.exe。先跑 .\build.ps1,再用 -Source 指向 dist 下的 windows-amd64 目录。" } $srcDir = $found.DirectoryName } # ---- 复制 ------------------------------------------------------------------ New-Item -ItemType Directory -Force -Path $Prefix | Out-Null # 正在运行的 exe 无法覆盖,先停掉。 Get-Process portbug, portbugd, portbugctl -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($Prefix, 'OrdinalIgnoreCase') } | ForEach-Object { Write-Step "停止运行中的 $($_.ProcessName)"; $_ | Stop-Process -Force } foreach ($exe in @('portbug.exe', 'portbugd.exe', 'portbugctl.exe')) { $from = Join-Path $srcDir $exe if (Test-Path $from) { Copy-Item $from (Join-Path $Prefix $exe) -Force Write-Step "$exe" } } if ($staging -and (Test-Path $staging)) { Remove-Item $staging -Recurse -Force -ErrorAction SilentlyContinue } $portbug = Join-Path $Prefix 'portbug.exe' if (-not (Test-Path $portbug)) { throw "复制后仍找不到 $portbug" } Write-Ok "已装到 $Prefix" # ---- PATH ------------------------------------------------------------------ if (-not $NoPath) { $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') $parts = @() if ($userPath) { $parts = @($userPath -split ';' | Where-Object { $_ }) } $already = $parts | Where-Object { $_.TrimEnd('\') -ieq $Prefix.TrimEnd('\') } if ($already) { Write-Step "用户 PATH 里已有 $Prefix" } else { [Environment]::SetEnvironmentVariable('Path', (($parts + $Prefix) -join ';'), 'User') Write-Ok "已加入用户 PATH(新开的终端才生效)" } # 让当前这个会话立刻能用,不用重开窗口。 if (($env:Path -split ';') -notcontains $Prefix) { $env:Path = "$env:Path;$Prefix" } } # ---- 节点(可选)----------------------------------------------------------- if ($Node) { $portbugd = Join-Path $Prefix 'portbugd.exe' if (-not (Test-Path $portbugd)) { throw "这个包里没有 portbugd.exe" } if (-not $NodeId) { $NodeId = "win-" + $env:COMPUTERNAME.ToLower() } if (-not $Center) { $Center = 'http://127.0.0.1:18080' } # 用计划任务而不是 Windows 服务:真服务要响应 SCM 的控制消息, # 普通 exe 注册上去会以「服务没有及时响应」失败。 $args = @( '-id', $NodeId, '-mode', 'range', '-ports', $NodePorts, '-reserved', '22', '-center', $Center ) -join ' ' $action = New-ScheduledTaskAction -Execute $portbugd -Argument $args $trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME $set = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) Register-ScheduledTask -TaskName 'portbugd' -Action $action -Trigger $trigger ` -Settings $set -Force | Out-Null Start-ScheduledTask -TaskName 'portbugd' Write-Ok "节点 $NodeId 已注册为计划任务 portbugd(登录自启),端口 $NodePorts" Write-Step "range 模式:只监听列出的端口。全端口捕获需要 Linux + root,见 README。" } # ---- 收尾 ------------------------------------------------------------------ Write-Host "" & $portbug version Write-Host "" Write-Host "试一下:" -ForegroundColor Gray Write-Host " portbug nodes --center http://127.0.0.1:18080" -ForegroundColor Gray Write-Host " portbug quick --to <节点> --center http://127.0.0.1:18080" -ForegroundColor Gray Write-Host "卸载: .\install.ps1 -Uninstall" -ForegroundColor DarkGray