<# .SYNOPSIS frpc Windows Setup Wizard (Interactive PowerShell Installer) .DESCRIPTION Interactive installer for frpc with EULA acceptance, directory selection, and optional features (PATH, shortcuts, desktop icon). .PARAMETER Unattended Run in silent mode with defaults. .PARAMETER InstallDir Installation directory (default: $env:ProgramFiles\frpc). .EXAMPLE .\install.ps1 .\install.ps1 -Unattended #> param( [switch]$Unattended, [string]$InstallDir = "$env:ProgramFiles\frpc" ) $ErrorActionPreference = "Stop" $host.UI.RawUI.WindowTitle = "frpc Setup Wizard" $env:FRPC_VERSION = "0.62.0" function Write-Banner { Clear-Host Write-Host "========================================" -ForegroundColor Cyan Write-Host " frpc - frp Client v$env:FRPC_VERSION" -ForegroundColor Cyan Write-Host " Setup Wizard" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "" } function Show-License { param([string]$Path) Write-Banner Write-Host "License Agreement" -ForegroundColor Yellow Write-Host "==================" -ForegroundColor Yellow Write-Host "" if (Test-Path $Path) { Get-Content $Path | ForEach-Object { Write-Host $_ } } else { Write-Host "frpc is licensed under the Apache License, Version 2.0." Write-Host "See http://www.apache.org/licenses/LICENSE-2.0" } Write-Host "" if (-not $Unattended) { $choice = $host.UI.PromptForChoice( "License Agreement", "Do you accept the terms?", [System.Management.Automation.Host.ChoiceDescription[]]@( @{Label="&Agree"; Help="I accept the agreement"}, @{Label="&Decline"; Help="I do not accept the agreement"} ), 1 ) if ($choice -ne 0) { Write-Host "Setup cancelled. You must accept the license to install." -ForegroundColor Red exit 1 } } } function Select-Directory { param([string]$DefaultPath) Write-Banner Write-Host "Installation Directory" -ForegroundColor Yellow Write-Host "======================" -ForegroundColor Yellow Write-Host "" Write-Host "Default: $DefaultPath" -ForegroundColor Gray Write-Host "" if (-not $Unattended) { $input = Read-Host "Press Enter to accept, or type a custom path" if ($input -ne "") { return $input } } return $DefaultPath } function Select-Components { Write-Banner Write-Host "Select Components" -ForegroundColor Yellow Write-Host "=================" -ForegroundColor Yellow Write-Host "" $components = @{ "Main" = @{Desc="frpc binary and config"; Default=$true} "Path" = @{Desc="Add to system PATH"; Default=$true} "StartMenu" = @{Desc="Start Menu shortcuts"; Default=$true} "Desktop" = @{Desc="Desktop shortcut"; Default=$false} } $selected = @{} $i = 1 $choices = @() foreach ($key in $components.Keys) { $c = $components[$key] $default = if ($c.Default) { "Y" } else { "N" } $displayDefault = if ($c.Default) { "Yes" } else { "No" } Write-Host " $i. $($c.Desc) [$displayDefault]" -ForegroundColor Gray $choices += @{Key=$key; Default=$default} $i++ } Write-Host "" if (-not $Unattended) { Write-Host "Press Enter for defaults, or type numbers to toggle (e.g. 1,3):" -ForegroundColor Yellow $input = Read-Host if ($input -ne "") { $toggleIndices = $input -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } for ($j = 0; $j -lt $choices.Count; $j++) { $idx = $j + 1 $isToggled = [string]$idx -in $toggleIndices $c = $components[$choices[$j].Key] $default = $c.Default $selected[$choices[$j].Key] = if ($isToggled) { -not $default } else { $default } } } else { foreach ($ch in $choices) { $selected[$ch.Key] = $components[$ch.Key].Default } } } else { foreach ($ch in $choices) { $selected[$ch.Key] = $components[$ch.Key].Default } } Write-Host "" Write-Host "Selected components:" -ForegroundColor Green foreach ($key in $selected.Keys) { $mark = if ($selected[$key]) { "[X]" } else { "[ ]" } Write-Host " $mark $($components[$key].Desc)" -ForegroundColor $(if ($selected[$key]) { "Green" } else { "DarkGray" }) } Start-Sleep 1 return $selected } function Install-Frpc { param( [string]$InstallDir, [hashtable]$Components, [string]$BinarySource ) Write-Banner Write-Host "Installing frpc..." -ForegroundColor Yellow Write-Host "==================" -ForegroundColor Yellow Write-Host "" # Create directories Write-Host " Creating directories..." -NoNewline New-Item -Path $InstallDir -ItemType Directory -Force | Out-Null $configDir = "$env:ProgramData\frpc" New-Item -Path $configDir -ItemType Directory -Force | Out-Null Write-Host " OK" -ForegroundColor Green # Copy binary Write-Host " Copying frpc.exe..." -NoNewline if ($BinarySource -and (Test-Path $BinarySource)) { Copy-Item -Path $BinarySource -Destination "$InstallDir\frpc.exe" -Force } else { # Download from GitHub $url = "https://github.com/fatedier/frp/releases/download/v$env:FRPC_VERSION/frp_${env:FRPC_VERSION}_windows_amd64.zip" $zipPath = "$env:TEMP\frpc.zip" Write-Host "" Write-Host " Downloading from GitHub..." -NoNewline Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing Expand-Archive -Path $zipPath -DestinationPath "$env:TEMP\frpc" -Force $exePath = Get-ChildItem -Path "$env:TEMP\frpc" -Recurse -Filter "frpc.exe" | Select-Object -First 1 -ExpandProperty FullName if (-not $exePath) { throw "frpc.exe not found in archive" } Copy-Item -Path $exePath -Destination "$InstallDir\frpc.exe" -Force } Write-Host " OK" -ForegroundColor Green # Config file Write-Host " Creating config..." -NoNewline $configFile = "$configDir\frpc.toml" if (-not (Test-Path $configFile)) { @" # frpc configuration # Edit this file to configure your frp client. serverAddr = "127.0.0.1" serverPort = 7000 auth.token = "" "@ | Out-File -FilePath $configFile -Encoding utf8 } Write-Host " OK" -ForegroundColor Green # PATH if ($Components["Path"]) { Write-Host " Adding to PATH..." -NoNewline $currentPath = [Environment]::GetEnvironmentVariable("PATH", "Machine") if ($currentPath -notlike "*$InstallDir*") { $newPath = "$currentPath;$InstallDir" [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") $env:PATH = "$env:PATH;$InstallDir" } Write-Host " OK" -ForegroundColor Green } # Start Menu if ($Components["StartMenu"]) { Write-Host " Creating Start Menu shortcuts..." -NoNewline $startMenu = "$([Environment]::GetFolderPath('CommonStartMenu'))\Programs\frpc" New-Item -Path $startMenu -ItemType Directory -Force | Out-Null $wshell = New-Object -ComObject WScript.Shell $shortcut = $wshell.CreateShortcut("$startMenu\frpc.lnk") $shortcut.TargetPath = "$InstallDir\frpc.exe" $shortcut.Save() $unlink = $wshell.CreateShortcut("$startMenu\Uninstall frpc.lnk") $unlink.TargetPath = "$InstallDir\uninstall.ps1" $unlink.Save() Write-Host " OK" -ForegroundColor Green } # Desktop if ($Components["Desktop"]) { Write-Host " Creating desktop shortcut..." -NoNewline $desktop = [Environment]::GetFolderPath('Desktop') $wshell = New-Object -ComObject WScript.Shell $shortcut = $wshell.CreateShortcut("$desktop\frpc.lnk") $shortcut.TargetPath = "$InstallDir\frpc.exe" $shortcut.Save() Write-Host " OK" -ForegroundColor Green } # Write uninstall script Write-Host " Creating uninstaller..." -NoNewline @" param([switch]`$Silent) `$InstallDir = "$InstallDir" if (-not `$Silent) { `$choice = `$host.UI.PromptForChoice("Uninstall frpc", "Remove frpc from this computer?", @(@{Label="&Yes"; Help=""}, @{Label="&No"; Help=""}), 1) if (`$choice -ne 0) { exit } } # Remove files Remove-Item -Path "`$InstallDir\frpc.exe" -Force -ErrorAction SilentlyContinue Remove-Item -Path "`$InstallDir\uninstall.ps1" -Force -ErrorAction SilentlyContinue Remove-Item -Path "`$InstallDir" -Force -ErrorAction SilentlyContinue # Remove shortcuts Remove-Item -Path "$([Environment]::GetFolderPath('CommonStartMenu'))\Programs\frpc" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$([Environment]::GetFolderPath('Desktop'))\frpc.lnk" -Force -ErrorAction SilentlyContinue Write-Host "frpc has been uninstalled." "@ | Out-File -FilePath "$InstallDir\uninstall.ps1" -Encoding utf8 Write-Host " OK" -ForegroundColor Green Write-Host "" Write-Host "Installation complete!" -ForegroundColor Green Write-Host "" Write-Host "Quick start:" -ForegroundColor Yellow Write-Host " frpc --help" Write-Host " frpc --server-config http://your-server:7500/admin/api/frpc/proxy-config/YOUR_KEY" Write-Host " frpc -c $configFile" Write-Host "" Write-Host "Auth with provisioning token:" -ForegroundColor Yellow Write-Host " frpc auth login --server http://your-server:7500 --client-name myclient" Write-Host "" } # ===== Main ===== $scriptDir = Split-Path -Parent $PSCommandPath $binarySource = Join-Path $scriptDir "..\bin\frpc-windows-amd64.exe" if (-not (Test-Path $binarySource)) { $binarySource = Join-Path $scriptDir "frpc-windows-amd64.exe" } if (-not (Test-Path $binarySource)) { $binarySource = $null } # Check admin rights $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Host "This installer requires administrator privileges." -ForegroundColor Red Write-Host "Please run PowerShell as Administrator and try again." -ForegroundColor Yellow if (-not $Unattended) { Start-Sleep 2 # Self-elevate $script = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" Start-Process powershell -Verb RunAs -ArgumentList $script } exit 1 } if ($Unattended) { Install-Frpc -InstallDir $InstallDir -Components @{ Main=$true; Path=$true; StartMenu=$true; Desktop=$false } -BinarySource $binarySource } else { Show-License -Path (Join-Path $scriptDir "license.txt") $dir = Select-Directory -DefaultPath $InstallDir $components = Select-Components Install-Frpc -InstallDir $dir -Components $components -BinarySource $binarySource Write-Host "" $runNow = $host.UI.PromptForChoice( "Setup Complete", "Run frpc now?", [System.Management.Automation.Host.ChoiceDescription[]]@( @{Label="&Yes"; Help="Run frpc"}, @{Label="&No"; Help="Close"} ), 0 ) if ($runNow -eq 0) { Start-Process "$dir\frpc.exe" } }