<# .SYNOPSIS kanholec Windows Setup Wizard (Interactive PowerShell Installer) .DESCRIPTION Interactive installer for kanholec 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\kanholec). .EXAMPLE .\install.ps1 .\install.ps1 -Unattended #> param( [switch]$Unattended, [string]$InstallDir = "$env:ProgramFiles\kanholec" ) $ErrorActionPreference = "Stop" $host.UI.RawUI.WindowTitle = "kanholec Setup Wizard" $env:KANHOLEC_VERSION = "0.62.0" function Write-Banner { Clear-Host Write-Host "========================================" -ForegroundColor Cyan Write-Host " kanholec - kanhole Client v$env:KANHOLEC_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 "kanholec 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="kanholec 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 kanholec..." -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\kanholec" New-Item -Path $configDir -ItemType Directory -Force | Out-Null Write-Host " OK" -ForegroundColor Green # Copy binary Write-Host " Copying kanholec.exe..." -NoNewline if ($BinarySource -and (Test-Path $BinarySource)) { Copy-Item -Path $BinarySource -Destination "$InstallDir\kanholec.exe" -Force } else { # Download from GitHub $url = "https://github.com/fatedier/kanhole/releases/download/v$env:KANHOLEC_VERSION/kanhole_${env:KANHOLEC_VERSION}_windows_amd64.zip" $zipPath = "$env:TEMP\kanholec.zip" Write-Host "" Write-Host " Downloading from GitHub..." -NoNewline Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing Expand-Archive -Path $zipPath -DestinationPath "$env:TEMP\kanholec" -Force $exePath = Get-ChildItem -Path "$env:TEMP\kanholec" -Recurse -Filter "kanholec.exe" | Select-Object -First 1 -ExpandProperty FullName if (-not $exePath) { throw "kanholec.exe not found in archive" } Copy-Item -Path $exePath -Destination "$InstallDir\kanholec.exe" -Force } Write-Host " OK" -ForegroundColor Green # Config file Write-Host " Creating config..." -NoNewline $configFile = "$configDir\kanholec.toml" if (-not (Test-Path $configFile)) { @" # kanholec configuration # Edit this file to configure your kanhole 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\kanholec" New-Item -Path $startMenu -ItemType Directory -Force | Out-Null $wshell = New-Object -ComObject WScript.Shell $shortcut = $wshell.CreateShortcut("$startMenu\kanholec.lnk") $shortcut.TargetPath = "$InstallDir\kanholec.exe" $shortcut.Save() $unlink = $wshell.CreateShortcut("$startMenu\Uninstall kanholec.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\kanholec.lnk") $shortcut.TargetPath = "$InstallDir\kanholec.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 kanholec", "Remove kanholec from this computer?", @(@{Label="&Yes"; Help=""}, @{Label="&No"; Help=""}), 1) if (`$choice -ne 0) { exit } } # Remove files Remove-Item -Path "`$InstallDir\kanholec.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\kanholec" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$([Environment]::GetFolderPath('Desktop'))\kanholec.lnk" -Force -ErrorAction SilentlyContinue Write-Host "kanholec 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 " kanholec --help" Write-Host " kanholec --server-config http://your-server:7500/admin/api/kanholec/proxy-config/YOUR_KEY" Write-Host " kanholec -c $configFile" Write-Host "" Write-Host "Auth with provisioning token:" -ForegroundColor Yellow Write-Host " kanholec auth login --server http://your-server:7500 --client-name myclient" Write-Host "" } # ===== Main ===== $scriptDir = Split-Path -Parent $PSCommandPath $binarySource = Join-Path $scriptDir "..\bin\kanholec-windows-amd64.exe" if (-not (Test-Path $binarySource)) { $binarySource = Join-Path $scriptDir "kanholec-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 kanholec now?", [System.Management.Automation.Host.ChoiceDescription[]]@( @{Label="&Yes"; Help="Run kanholec"}, @{Label="&No"; Help="Close"} ), 0 ) if ($runNow -eq 0) { Start-Process "$dir\kanholec.exe" } }