Files
EasyTier/script/install.cmd
2025-06-22 12:06:16 +08:00

760 lines
33 KiB
Batchfile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

::BATCH_START
@ECHO off
SETLOCAL EnableDelayedExpansion
TITLE Initializing Script...
CD /d %~dp0
SET ScriptPath=\^"%~f0\^"
SET ScriptRoot=%~dp0
SET ScriptRoot=\^"!ScriptRoot:~0,-1!\^"
SET Args=%*
IF DEFINED Args (SET Args=!Args:"=\"!)
<NUL SET /p="Checking PowerShell ... "
WHERE /q PowerShell
IF !ERRORLEVEL! NEQ 0 (ECHO PowerShell is not installed. & PAUSE & EXIT)
PowerShell -Command "if ($PSVersionTable.PSVersion.Major -lt 3) { exit 1 }"
IF !ERRORLEVEL! NEQ 0 (ECHO Requires PowerShell 3 or later. & PAUSE & EXIT)
ECHO OK
<NUL SET /p="Checking execute permissions ... "
PowerShell -Command "if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { exit 1 }"
IF !ERRORLEVEL! NEQ 0 (CLS & ECHO Restart with administrator privileges ... & PowerShell -Command "Start-Process cmd.exe -Verb RunAs -ArgumentList '/k CD /d !ScriptRoot! && !ScriptPath! !Args!'" & EXIT)
ECHO OK
<NUL SET /p="Extract embedded script ... "
PowerShell -Command "$content = (Get-Content -Path '%~f0' -Encoding UTF8 | Out-String) -replace '(?s)' + [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('OjpCQVRDSF9TVEFSVA==')) + '.*?' + [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('OjpCQVRDSF9FTkQ=')); Set-Content -Path '%~f0.ps1' -Value $content.Trim() -Encoding UTF8"
IF !ERRORLEVEL! NEQ 0 (ECHO Embedded script section not found. & PAUSE & EXIT)
ECHO OK
<NUL SET /p="Execute script ... "
PowerShell -NoProfile -ExecutionPolicy Bypass -File "%~f0.ps1" %*
ECHO OK
<NUL SET /p="Delete script ... "
DEL /f /q "%~f0.ps1"
ECHO OK
EXIT
::BATCH_END
param(
[Parameter(Mandatory = $false)]
[string]$ServiceName = "EasyTierService",
[Parameter(Mandatory = $false)]
[switch]$Uninstall
)
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo("zh-CN")
[System.Threading.Thread]::CurrentThread.CurrentUICulture = [System.Globalization.CultureInfo]::GetCultureInfo("zh-CN")
function Show-Pause {
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[string]$Text = "按任意键继续...",
[string]$Color = "Cyan"
)
Write-Host "$Text" -ForegroundColor $Color
[System.Console]::ReadKey($true) > $null
}
function Show-YesNoPrompt {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[string]$Title = "",
[ValidateRange(0, 1)]
[int]$DefaultIndex = 0,
[string[]]$Labels = @("&Yes", "&No"),
[string[]]$Helps = @("", "")
)
if ($Labels.Count -ne $Helps.Count) {
throw "Labels 和 Helps 的数量必须相同。"
}
$choices = for ($i = 0; $i -lt $Labels.Count; $i++) {
[System.Management.Automation.Host.ChoiceDescription]::new($Labels[$i], $Helps[$i])
}
try {
return $Host.UI.PromptForChoice($Title, $Message, $choices, $DefaultIndex) -eq 0
}
catch {
Write-Error "显示选择提示时出错: $_"
return $false
}
}
function Show-MultipleChoicePrompt {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateNotNullOrEmpty()]
[string[]]$Options,
[string[]]$Helps = @(),
[string]$Title = "",
[int]$DefaultIndex = 0
)
if ($Helps.Count -eq 0) {
$Helps = @("")
for ($i = 1; $i -lt $Options.Count; $i++) {
$Helps += ""
}
}
if ($Options.Count -ne $Helps.Count) {
throw "Options 和 Helps 的数量必须相同。"
}
if ($DefaultIndex -ge $Options.Count) {
$DefaultIndex = $Options.Count - 1
}
$currentSelection = $DefaultIndex
function Show-Menu {
param(
[int]$highlightIndex,
[string]$title,
[string]$message,
[string[]]$options,
[string[]]$helps,
[int]$prevIndex = -1
)
try {
# 首次显示时绘制完整菜单
if ($prevIndex -eq -1) {
Clear-Host
if (-not [string]::IsNullOrEmpty($title)) {
Write-Host "$title`n" -ForegroundColor Blue
}
Write-Host "$message" -ForegroundColor Yellow
# 保存初始光标位置
$script:menuTop = [Console]::CursorTop
# 首次绘制所有选项
for ($i = 0; $i -lt $options.Count; $i++) {
$prefix = if ($i -eq $highlightIndex) { "[>]" } else { "[ ]" }
$color = if ($i -eq $highlightIndex) { "Green" } else { "Gray" }
Write-Host "$prefix $($options[$i])" -ForegroundColor $color -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($helps[$i])) { " - $($helps[$i])" } else { "" }) -ForegroundColor DarkGray
}
}
# 只更新变化的选项
if ($prevIndex -ne -1) {
$safePrevPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $prevIndex)
[Console]::SetCursorPosition(0, $safePrevPos)
Write-Host "[ ] $($options[$prevIndex])" -ForegroundColor Gray -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($helps[$prevIndex])) { " - $($helps[$prevIndex])" } else { "" }) -ForegroundColor DarkGray
}
$safeHighlightPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $highlightIndex)
[Console]::SetCursorPosition(0, $safeHighlightPos)
Write-Host "[>] $($options[$highlightIndex])" -ForegroundColor Green -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($helps[$highlightIndex])) { " - $($helps[$highlightIndex])" } else { "" }) -ForegroundColor DarkGray
# 首次显示时绘制操作提示
if ($prevIndex -eq -1) {
$safePos = [Math]::Min([Console]::WindowHeight - 2, $menuTop + $options.Count)
[Console]::SetCursorPosition(0, $safePos)
Write-Host "操作: 使用 ↑ / ↓ 移动 | Enter - 确认"
}
}
finally {
# 将光标移动到操作提示下方等待位置
$waitPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $options.Count + 1)
[Console]::SetCursorPosition(0, $waitPos)
}
}
$prevSelection = -1
while ($true) {
Show-Menu -highlightIndex $currentSelection -title $Title -message $Message -options $Options -helps $Helps -prevIndex $prevSelection
$prevSelection = $currentSelection
$key = [System.Console]::ReadKey($true)
switch ($key.Key) {
{ $_ -eq [ConsoleKey]::UpArrow } {
$currentSelection = [Math]::Max(0, $currentSelection - 1)
}
{ $_ -eq [ConsoleKey]::DownArrow } {
$currentSelection = [Math]::Min($Options.Count - 1, $currentSelection + 1)
}
{ $_ -eq [ConsoleKey]::Enter } {
Clear-Host
return $currentSelection
}
}
}
}
function Show-MultiSelectPrompt {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateNotNullOrEmpty()]
[string[]]$Options,
[string[]]$Helps = @(),
[string]$Title = "",
[int[]]$DefaultSelections = @()
)
if ($Helps.Count -eq 0) {
$Helps = @("")
for ($i = 1; $i -lt $Options.Count; $i++) {
$Helps += ""
}
}
if ($Options.Count -ne $Helps.Count) {
throw "Options 和 Helps 的数量必须相同。"
}
$selectedIndices = [System.Collections.Generic.List[int]]::new($DefaultSelections)
$currentSelection = 0
function Show-Menu {
param(
[int]$highlightIndex,
[System.Collections.Generic.List[int]]$selectedItems,
[int]$prevIndex = -1,
[int]$prevHighlight = -1
)
try {
# 首次显示时绘制完整菜单
if ($prevIndex -eq -1) {
Clear-Host
if (-not [string]::IsNullOrEmpty($Title)) {
Write-Host "$Title`n" -ForegroundColor Blue
}
Write-Host "$Message" -ForegroundColor Yellow
# 保存初始光标位置
$script:menuTop = [Console]::CursorTop
# 首次绘制所有选项
for ($i = 0; $i -lt $Options.Count; $i++) {
$isSelected = $selectedItems -contains $i
$prefix = if ($isSelected) { "[#]" } else { "[ ]" }
$color = if ($i -eq $highlightIndex) { "Green" } elseif ($isSelected) { "Cyan" } else { "Gray" }
Write-Host "$prefix $($Options[$i])" -ForegroundColor $color -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($Helps[$i])) { " - $($Helps[$i])" } else { "" }) -ForegroundColor DarkGray
}
}
# 只更新变化的选项
if ($prevIndex -ne -1) {
$safePrevPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $prevIndex)
[Console]::SetCursorPosition(0, $safePrevPos)
$isPrevSelected = $selectedItems -contains $prevIndex
$prefix = if ($isPrevSelected) { "[#]" } else { "[ ]" }
Write-Host "$prefix $($Options[$prevIndex])" -ForegroundColor $(if ($isPrevSelected) { "Cyan" } else { "Gray" }) -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($Helps[$prevIndex])) { " - $($Helps[$prevIndex])" } else { "" }) -ForegroundColor DarkGray
}
if ($prevHighlight -ne -1 -and $prevHighlight -ne $highlightIndex) {
$safePrevHighlightPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $prevHighlight)
[Console]::SetCursorPosition(0, $safePrevHighlightPos)
$isPrevHighlightSelected = $selectedItems -contains $prevHighlight
$prefix = if ($isPrevHighlightSelected) { "[#]" } else { "[ ]" }
Write-Host "$prefix $($Options[$prevHighlight])" -ForegroundColor $(if ($isPrevHighlightSelected) { "Cyan" } else { "Gray" }) -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($Helps[$prevHighlight])) { " - $($Helps[$prevHighlight])" } else { "" }) -ForegroundColor DarkGray
}
$safeHighlightPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $highlightIndex)
[Console]::SetCursorPosition(0, $safeHighlightPos)
$isSelected = $selectedItems -contains $highlightIndex
$prefix = if ($isSelected) { "[#]" } else { "[ ]" }
Write-Host "$prefix $($Options[$highlightIndex])" -ForegroundColor "Green" -NoNewline
Write-Host $(if (-not [string]::IsNullOrEmpty($Helps[$highlightIndex])) { " - $($Helps[$highlightIndex])" } else { "" }) -ForegroundColor DarkGray
# 首次显示时绘制操作提示
if ($prevIndex -eq -1) {
$safePos = [Math]::Min([Console]::WindowHeight - 2, $menuTop + $Options.Count)
[Console]::SetCursorPosition(0, $safePos)
Write-Host "操作: 使用 ↑ / ↓ 移动 | Space - 选中/取消 | Enter - 确认"
}
}
finally {
# 将光标移动到操作提示下方等待位置
$waitPos = [Math]::Min([Console]::WindowHeight - 1, $menuTop + $Options.Count + 1)
[Console]::SetCursorPosition(0, $waitPos)
}
}
$prevSelection = -1
$prevHighlight = -1
while ($true) {
Show-Menu -highlightIndex $currentSelection -selectedItems $selectedIndices -prevIndex $prevSelection -prevHighlight $prevHighlight
$prevHighlight = $currentSelection
$key = [System.Console]::ReadKey($true)
switch ($key.Key) {
{ $_ -eq [ConsoleKey]::UpArrow } {
$prevSelection = $currentSelection
$currentSelection = [Math]::Max(0, $currentSelection - 1)
}
{ $_ -eq [ConsoleKey]::DownArrow } {
$prevSelection = $currentSelection
$currentSelection = [Math]::Min($Options.Count - 1, $currentSelection + 1)
}
{ $_ -eq [ConsoleKey]::Spacebar } {
$prevSelection = $currentSelection
if ($selectedIndices.Contains($currentSelection)) {
$selectedIndices.Remove($currentSelection)
}
else {
$selectedIndices.Add($currentSelection)
}
}
{ $_ -eq [ConsoleKey]::Enter } {
Clear-Host
return $selectedIndices
}
}
}
}
function Get-InputWithNoNullOrWhiteSpace {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Prompt
)
while ($true) {
try {
$response = Read-Host "请输入${Prompt}(必填)"
if ([string]::IsNullOrWhiteSpace($response)) {
Write-Host "${Prompt}不能为空!" -ForegroundColor Red
continue
}
if ($response -match '^(?!").*(?<!")(?=.*\s).*$') {
$response = "`"$response`""
}
return $response.Trim()
}
catch {
Write-Error "读取输入时出错: $_"
continue
}
}
}
function Get-InputWithFileValidation {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Prompt
)
while ($true) {
try {
$filePath = (Get-InputWithNoNullOrWhiteSpace -Prompt $Prompt).Trim()
if (-not (Test-Path $filePath)) {
Write-Host "文件不存在: $filePath" -ForegroundColor Red
continue
}
return $filePath
}
catch {
Write-Error "读取输入时出错: $_"
continue
}
}
}
function Get-InputWithDefault {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[string]$Prompt,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DefaultValue
)
try {
$response = Read-Host "${Prompt}(默认: ${DefaultValue})"
if ([string]::IsNullOrWhiteSpace($response)) {
return $DefaultValue
}
if ($response -match '^(?!").*(?<!")(?=.*\s).*$') {
$response = "`"$response`""
}
return $response.Trim()
}
catch {
Write-Error "读取输入时出错: $_"
return $DefaultValue
}
}
function Get-BasicNetworkConfig {
[CmdletBinding()]
param()
$options = @()
$options += "--network-name $(Get-InputWithNoNullOrWhiteSpace -Prompt "网络名称")"
$options += "--network-secret $(Get-InputWithNoNullOrWhiteSpace -Prompt "网络密钥")"
if (Show-YesNoPrompt -Message "是否指定当前设备名称?" -DefaultIndex 1) {
$options += "--hostname $(Get-InputWithNoNullOrWhiteSpace -Prompt "设备名称")"
}
if (Show-YesNoPrompt -Message "是否使用公共共享节点来发现对等节点?") {
$options += "--external-node $(Get-InputWithDefault -Prompt "公共节点地址(格式:协议://IP:端口)" -DefaultValue "tcp://public.easytier.cn:11010")"
}
if (Show-YesNoPrompt -Message "是否添加对等节点?") {
$peers = @()
do {
$peers += Get-InputWithDefault -Prompt "对等节点地址" -DefaultValue "tcp://public.easytier.cn:11010"
} while (Show-YesNoPrompt -Message "是否继续添加对等节点?" -DefaultIndex 1)
if ($peers.Count -gt 0) {
$options += ($peers | ForEach-Object { "--peers $($_.Trim())" }) -join ' '
}
}
$ipChoice = Show-MultipleChoicePrompt -Message "请选择IP分配方式" `
-Options @("手动指定IPv4", "自动DHCP", "不设置IP") `
-Helps @("自定义此节点的IPv4地址如果为空则仅转发数据包", "由Easytier自动确定并设置IP地址", "将仅转发数据包不会创建TUN设备") `
-DefaultIndex 1
switch ($ipChoice) {
0 { $options += "--ipv4 $(Get-InputWithNoNullOrWhiteSpace -Prompt "IPv4地址")" }
1 { $options += "--dhcp" }
2 { break }
}
return $options
}
function Get-AdvancedConfig {
[CmdletBinding()]
param()
$options = @()
# 设备配置
if (Show-YesNoPrompt -Message "TUN" -DefaultIndex 1) {
$options += "--dev-name $(Get-InputWithNoNullOrWhiteSpace -Prompt "TUN接口名称(可选)")"
}
# 网络白名单
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$whitelist = Get-InputWithDefault -Prompt "白名单网络(空格分隔,*=所有,def*=以def开头的网络)" -DefaultValue "*"
$options += "--relay-network-whitelist $whitelist"
}
# 监听器配置
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$listeners = @()
do {
$listener = Get-InputWithNoNullOrWhiteSpace -Prompt "监听器地址(格式:协议://IP:端口)"
$listeners += $listener
} while (Show-YesNoPrompt -Message "是否添加更多监听器?" -DefaultIndex 1)
$options += "--listeners $($listeners -join ' ')"
if (Show-YesNoPrompt -Message "") {
$mapped = Get-InputWithNoNullOrWhiteSpace -Prompt "公网地址(格式:协议://IP:端口)"
$options += "--mapped-listeners $mapped"
}
}
else {
$options += "--no-listener"
}
# 性能选项
$performanceOptions = @(
"启用多线程运行",
"启用延迟优先模式",
"通过系统内核转发",
"启用KCP代理"
)
$performanceHelps = @(
"使用多线程运行时(默认为单线程)",
"延迟优先模式(默认使用最短路径)",
"通过系统内核转发子网代理数据包(禁用内置NAT)",
"使用KCP代理TCP流(提高UDP丢包网络性能)"
)
$selectedPerformance = Show-MultiSelectPrompt -Message "请选择性能选项:" -Options $performanceOptions -Helps $performanceHelps
# 处理选中的性能选项
foreach ($index in $selectedPerformance) {
switch ($index) {
0 { $options += "--multi-thread" }
1 { $options += "--latency-first" }
2 { $options += "--proxy-forward-by-system" }
3 { $options += "--enable-kcp-proxy" }
}
}
return $options
}
function Get-EasyTierConfig {
[CmdletBinding()]
param()
$options = @()
$configChoice = Show-MultipleChoicePrompt -Message "请选择配置方案:" `
-Options @("命令行", "配置文件", "配置服务器") `
-Helps @("使用命令行参数进行配置", "使用本地配置文件", "使用服务器集中管理") `
-DefaultIndex 0
switch ($configChoice) {
0 {
# 基本网络配置
$options += Get-BasicNetworkConfig
# 高级配置
$options += Get-AdvancedConfig
# 专家选项
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$options += Get-ExtraAdvancedOptions
}
}
1 {
$options += "--config-file $(Get-InputWithFileValidation -Prompt "配置文件路径(或将文件拖动到此处)")"
}
2 {
if (Show-YesNoPrompt -Message "使" -DefaultIndex 1) {
$configServer = Get-InputWithNoNullOrWhiteSpace -Prompt "自定义管理服务器(格式:协议://IP:端口/用户)"
}
else {
$configServer = Get-InputWithNoNullOrWhiteSpace -Prompt "官方服务器用户名"
}
$options += "--config-server $configServer"
}
}
return $options
}
function Get-ExtraAdvancedOptions {
[CmdletBinding()]
param()
$options = @()
# 检查并添加缺失的--no-tun参数
if (Show-YesNoPrompt -Message "TUN" -DefaultIndex 1) {
$options += "--no-tun"
}
# 检查并添加缺失的--mtu参数
if (Show-YesNoPrompt -Message "TUNMTU" -DefaultIndex 1) {
$mtu = Get-InputWithDefault -Prompt "MTU值(默认:加密1360/非加密1380)" -DefaultValue ""
if (-not [string]::IsNullOrEmpty($mtu)) {
$options += "--mtu $mtu"
}
}
# 日志配置
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$logLevels = @("trace", "debug", "info", "warn", "error", "critical")
$consoleLog = Show-MultipleChoicePrompt -Message "选择控制台日志级别" -Options $logLevels -DefaultIndex 2
$fileLog = Show-MultipleChoicePrompt -Message "选择文件日志级别" -Options $logLevels -DefaultIndex 2
$options += "--console-log-level $($logLevels[$consoleLog])"
$options += "--file-log-level $($logLevels[$fileLog])"
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$logDir = Get-InputWithDefault -Prompt "日志目录路径" -DefaultValue "$env:ProgramData\EasyTier\logs"
$options += "--file-log-dir `"$logDir`""
}
}
# 实例配置
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$options += "--instance-name $(Get-InputWithNoNullOrWhiteSpace -Prompt "实例名称")"
}
# 网络高级选项
$networkOptions = @(
"禁用IPv6",
"禁用加密",
"禁用P2P通信",
"禁用UDP打洞",
"启用私有模式",
"转发所有对等节点RPC"
)
$networkHelps = @(
"不使用IPv6",
"禁用对等节点通信的加密",
"只通过--peers指定的节点转发数据包",
"禁用UDP打洞功能",
"不允许不同网络的节点通过本节点中转",
"转发所有对等节点的RPC数据包"
)
$selectedNetwork = Show-MultiSelectPrompt -Message "请选择网络高级选项:" -Options $networkOptions -Helps $networkHelps
foreach ($index in $selectedNetwork) {
switch ($index) {
0 { $options += "--disable-ipv6" }
1 { $options += "--disable-encryption" }
2 { $options += "--disable-p2p" }
3 { $options += "--disable-udp-hole-punching" }
4 { $options += "--private-mode" }
5 { $options += "--relay-all-peer-rpc" }
}
}
# 端口转发
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$forwards = @()
do {
Write-Host "`n格式示例: udp://0.0.0.0:12345/10.126.126.1:23456" -ForegroundColor DarkGray
$forward = Get-InputWithNoNullOrWhiteSpace -Prompt "端口转发(格式:协议://本地IP:端口/虚拟IP:端口)"
$forwards += $forward
} while (Show-YesNoPrompt -Message "是否添加更多端口转发?" -DefaultIndex 1)
$options += "--port-forward $($forwards -join ' ')"
}
# SOCKS5代理
if (Show-YesNoPrompt -Message "SOCKS5" -DefaultIndex 1) {
$port = Get-InputWithDefault -Prompt "SOCKS5端口号" -DefaultValue "1080"
$options += "--socks5 $port"
}
# 其他选项
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$otherOptions = @(
"使用smoltcp堆栈",
"启用魔法DNS",
"绑定物理设备",
"启用KCP代理",
"禁用KCP输入",
"设置VPN门户",
"设置默认协议",
"设置压缩算法",
"手动分配路由CIDR",
"启用出口节点"
)
$otherHelps = @(
"为子网代理和KCP代理启用smoltcp堆栈",
"启用魔法DNS(hostname.et.net)",
"将套接字绑定到物理设备避免路由问题",
"使用KCP代理TCP流提高UDP网络性能",
"不允许其他节点使用KCP代理到此节点",
"定义VPN门户URL(wg://IP:端口/网络)",
"设置连接对等节点的默认协议",
"设置压缩算法(none/zstd)",
"手动分配路由CIDR(将禁用子网代理和wireguard路由)",
"允许此节点成为出口节点"
)
$selectedOther = Show-MultiSelectPrompt -Message "请选择其他高级选项:" -Options $otherOptions -Helps $otherHelps
foreach ($index in $selectedOther) {
switch ($index) {
0 { $options += "--use-smoltcp" }
1 { $options += "--accept-dns" }
2 { $options += "--bind-device" }
3 { $options += "--enable-kcp-proxy" }
4 { $options += "--disable-kcp-input" }
5 {
Write-Host "`n格式示例: wg://0.0.0.0:11010/10.14.14.0/24" -ForegroundColor DarkGray
$vpnPortal = Get-InputWithNoNullOrWhiteSpace -Prompt "VPN门户URL(格式:wg://IP:端口/网络)"
$options += "--vpn-portal `"$vpnPortal`""
}
6 {
$protocols = @("tcp", "udp", "ws", "wss", "wg", "ring")
$protoIndex = Show-MultipleChoicePrompt -Message "选择默认协议" -Options $protocols
$options += "--default-protocol $($protocols[$protoIndex])"
}
7 {
$algorithms = @("none", "zstd")
$algoIndex = Show-MultipleChoicePrompt -Message "选择压缩算法" -Options $algorithms
$options += "--compression $($algorithms[$algoIndex])"
}
8 {
$routes = @()
do {
$route = Get-InputWithNoNullOrWhiteSpace -Prompt "手动路由CIDR(如192.168.0.0/16)"
$routes += $route
} while (Show-YesNoPrompt -Message "是否添加更多手动路由?" -DefaultIndex 1)
$options += "--manual-routes $($routes -join ' ')"
}
9 {
$options += "--enable-exit-node"
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$exitNodes = @()
do {
$exitNode = Get-InputWithNoNullOrWhiteSpace -Prompt "出口节点虚拟IPv4地址"
$exitNodes += $exitNode
} while (Show-YesNoPrompt -Message "是否添加更多出口节点?" -DefaultIndex 1)
$options += "--exit-nodes $($exitNodes -join ' ')"
}
}
}
}
}
return $options
}
function Save-ServiceName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string]$ServiceName
)
if (-not (Test-ServiceNameExists -FilePath $FilePath -ServiceName $ServiceName)) {
$uniqueLines = @()
$uniqueLines += Get-Content -Path $FilePath
$uniqueLines += $ServiceName | Sort-Object -Unique
Set-Content -Path $FilePath -Value ($uniqueLines -join [Environment]::NewLine) -Encoding UTF8 -Force
}
}
function Remove-ServiceName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string]$ServiceName
)
if (Test-ServiceNameExists -FilePath $FilePath -ServiceName $ServiceName) {
$uniqueLines = Get-Content -Path $FilePath | Where-Object { $_ -ne $ServiceName } | Sort-Object -Unique
Set-Content -Path $FilePath -Value ($uniqueLines -join [Environment]::NewLine) -Encoding UTF8 -Force
}
}
function Test-ServiceNameExists {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string]$ServiceName
)
if (-Not (Test-Path $FilePath)) {
Set-Content -Path $FilePath -Value "" -Encoding UTF8 -Force
return $false
}
$uniqueLines = Get-Content -Path $FilePath | Sort-Object -Unique
return $uniqueLines -contains $ServiceName
}
$host.ui.rawui.WindowTitle = if ($Uninstall) { "卸载EasyTier服务" } else { "安装EasyTier服务" }
Clear-Host
$ScriptRoot = (Get-Location).Path
$ServicesPath = Join-Path $ScriptRoot "services"
$RequiredFiles = @("easytier-core.exe", "easytier-cli.exe", "nssm.exe", "Packet.dll", "wintun.dll")
foreach ($file in $RequiredFiles) {
if (-not (Test-Path (Join-Path $ScriptRoot $file))) {
Write-Host "缺少必要文件: ${file}" -ForegroundColor Red
Show-Pause -Text "按任意键退出..."
exit 1
}
}
try {
$nssm = Join-Path $ScriptRoot "nssm.exe"
if ($Uninstall) {
$Force = $false
$Action = "designation"
if (-not (Test-ServiceNameExists -FilePath $ServicesPath -ServiceName $ServiceName)) {
Write-Host "服务未安装" -ForegroundColor Red
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
$Force = $true
$Action = "all"
}
else {
Show-Pause -Text "按任意键退出..."
exit 1
}
}
# 参数处理
if ($Action -eq "all") {
if (-not $Force) {
if (-not (Show-YesNoPrompt -Message "" -DefaultIndex 1)) {
Write-Host "已取消卸载操作" -ForegroundColor Yellow
Show-Pause -Text "按任意键退出..."
exit 0
}
}
Write-Host "正在卸载所有服务..." -ForegroundColor Cyan
# 读取所有服务名
$services = Get-Content $ServicesPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
if (-not $services) {
$services = @($ServiceName)
}
}
else {
$services = @($ServiceName)
}
foreach ($service in $services) {
# 停止服务
Write-Host "正在停止服务 $service ..."
& $nssm stop $service
# 删除服务(自动确认)
Write-Host "正在移除服务 $service ..."
& $nssm remove $service confirm
Remove-ServiceName -FilePath $ServicesPath -ServiceName $service
Write-Host "服务 $service 已卸载" -ForegroundColor Green
}
# 如果是完全卸载,删除服务记录文件
if ($Action -eq "all") {
Remove-Item $ServicesPath -Force
Write-Host "已删除服务列表文件" -ForegroundColor Green
}
}
else {
$OPTIONS = Get-EasyTierConfig
$arguments = $OPTIONS -join ' '
Write-Host "生成的配置参数如下:" -ForegroundColor Yellow
Write-Host ($OPTIONS -join " ") -ForegroundColor DarkGray
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
& $nssm install $ServiceName (Join-Path $ScriptRoot "easytier-core.exe")
& $nssm set $ServiceName AppParameters $arguments
& $nssm set $ServiceName Description "EasyTier 核心服务"
& $nssm set $ServiceName AppDirectory $ScriptRoot
& $nssm set $ServiceName Start SERVICE_AUTO_START
& $nssm start $ServiceName
Save-ServiceName -FilePath $ServicesPath -ServiceName $ServiceName
Write-Host "服务安装完成。" -ForegroundColor Green
}
else {
Write-Host "安装已取消。" -ForegroundColor Yellow
}
}
}
catch {
Write-Host "$($host.ui.rawui.WindowTitle)发生错误: $_" -ForegroundColor Red
exit 1
}
Show-Pause -Text "按任意键退出..."
exit