Files
EasyTier/script/install.cmd
dawn-lc d686c8721f feat(install): enhance installation script functionality (#1641)
* feat(install): enhance installation script functionality
* fix temp file extname
2025-12-04 23:06:06 +08:00

749 lines
24 KiB
Batchfile
Raw Permalink 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
<NUL SET /p="Checking PowerShell ... "
WHERE /q PowerShell
IF !ERRORLEVEL! NEQ 0 ( ECHO PowerShell is not installed. & PAUSE & EXIT )
ECHO OK
<NUL SET /p="Checking PowerShell version ... "
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 (
ECHO Fail
ECHO Restart with administrator privileges ...
SET args=%*
IF DEFINED args (
SET args=!args:'=''!
SET args=!args:"=\"!
)
PowerShell -NoProfile -Command "Start-Process 'cmd.exe' -Verb RunAs -WorkingDirectory '%~dp0' -ArgumentList '/d /s /k \"\"%~f0\" !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 '%~n0.ps1' -Value $content -Encoding UTF8"
IF !ERRORLEVEL! NEQ 0 ( ECHO Embedded script section not found. & PAUSE & EXIT )
ECHO OK
ECHO Execute script
PowerShell -NoProfile -ExecutionPolicy Bypass -File "%~n0.ps1" %*
<NUL SET /p="Delete script ... "
DEL /f /q "%~n0.ps1"
ECHO OK
EXIT
::BATCH_END
param(
[Parameter(Mandatory = $false)]
[Alias("h", "?")]
[switch]$Help,
[Parameter(Mandatory = $false)]
[Alias("u")]
[switch]$Update,
[Parameter(Mandatory = $false)]
[Alias("x")]
[switch]$Uninstall,
[Parameter(Mandatory = $false)]
[Alias("ughp")]
[switch]$UseGitHubProxy = $false,
[Parameter(Mandatory = $false)]
[Alias("ghp")]
[string]$GitHubProxy = "https://ghfast.top/",
[Parameter(Mandatory = $false)]
[Alias("up")]
[switch]$UseProxy = $false,
[Parameter(Mandatory = $false)]
[Alias("p")]
[string]$Proxy = "http://127.0.0.1:7890",
[Parameter(Mandatory = $false)]
[Alias("c")]
[string]$ConfigType,
[Parameter(Mandatory = $false)]
[Alias("n")]
[string]$ServiceName = "EasyTierService",
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$ServiceArgs
)
try {
$culture = [System.Globalization.CultureInfo]::CurrentCulture
[System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}
catch {
throw "环境初始化未成功,若运行正常请无视此消息`n$_"
}
function Invoke-WebRequestCompatible {
param(
[Parameter(Mandatory = $true)]
[string]$Uri,
[Parameter(Mandatory = $false)]
[string]$OutFile
)
if ($PSVersionTable.PSVersion.Major -lt 5) {
try {
$client = New-Object System.Net.WebClient
if ($UseProxy) {
$client.Proxy = New-Object System.Net.WebProxy($Proxy)
}
if ($OutFile) {
$client.DownloadFile($Uri, $OutFile)
return $null
}
else {
$response = $client.DownloadString($Uri)
return $response
}
}
catch {
throw "[兼容模式]Invoke-WebRequest`n$_"
}
}
else {
$params = @{
Uri = $Uri
UseBasicParsing = $true
}
if ($UseProxy) {
$params.Proxy = $Proxy
}
if ($OutFile) {
$params.OutFile = $OutFile
Invoke-WebRequest @params
}
else {
Invoke-WebRequest @params
}
}
}
function Invoke-RestMethodCompatible {
param(
[Parameter(Mandatory = $true)]
[string]$Uri
)
if ($PSVersionTable.PSVersion.Major -lt 5) {
$rawJson = Invoke-WebRequestCompatible -Uri $Uri
try {
return $rawJson | ConvertFrom-Json
}
catch {
throw "[兼容模式]Invoke-RestMethod`n$_"
}
}
else {
$params = @{
Uri = $Uri
UseBasicParsing = $true
}
if ($UseProxy) {
$params.Proxy = $Proxy
}
return Invoke-RestMethod @params
}
}
function Expand-ZipFile {
param(
[Parameter(Mandatory = $true)]
[string]$ZipPath,
[Parameter(Mandatory = $true)]
[string]$DestinationPath
)
if ($PSVersionTable.PSVersion.Major -lt 5) {
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
[System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $DestinationPath)
}
catch {
throw "[兼容模式]Expand-Archive`n$_"
}
}
else {
Expand-Archive -Path $ZipPath -DestinationPath $DestinationPath -Force
}
}
function Show-Pause {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$Text = "请按任意键继续...",
[Parameter(Mandatory = $false)]
[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 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
}
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 Initialize-RegistryEntryExists {
if (-not (Test-Path $RegistryPath)) {
New-Item -Path $RegistryPath -Force | Out-Null
}
try {
$null = Get-ItemProperty -Path $RegistryPath -Name $RegistryName -ErrorAction Stop
}
catch {
New-ItemProperty -Path $RegistryPath -Name $RegistryName -Value @() -PropertyType MultiString -Force | Out-Null
}
}
function Get-ServiceNames {
Initialize-RegistryEntryExists
try {
$value = (Get-ItemProperty -Path $RegistryPath -Name $RegistryName).$RegistryName
}
catch {
return @()
}
if ($null -eq $value -or $value.Count -eq 0) {
return @()
}
return $value
}
function Save-ServiceName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Name
)
$existing = Get-ServiceNames
if ($existing -notcontains $Name) {
$existing += $Name
Set-ItemProperty -Path $RegistryPath -Name $RegistryName -Value $existing -Force
}
}
function Remove-ServiceName {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Name
)
$existing = Get-ServiceNames
if ($existing -contains $Name) {
$updated = $existing | Where-Object { $_ -ne $Name }
Set-ItemProperty -Path $RegistryPath -Name $RegistryName -Value $updated -Force
}
}
function Test-ServiceNameExists {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Name
)
$existing = Get-ServiceNames
return $existing -contains $Name
}
function Remove-ServiceCompatible {
param (
[Parameter(Mandatory = $true)]
[string]$Name
)
if (Get-Command -Name Remove-Service -ErrorAction SilentlyContinue) {
Remove-Service -Name $Name -Force
}
else {
sc.exe delete "$Name" > $null 2>&1
}
}
function Get-SystemArchitecture {
try {
$platform = [System.Environment]::OSVersion.Platform
if ($platform -ne [System.PlatformID]::Win32NT) {
throw "Unsupported OS: Non-Windows platform detected ($platform)"
}
}
catch {
throw "Unsupported OS: Unable to determine platform."
}
if ($env:PROCESSOR_ARCHITEW6432) {
$arch = $env:PROCESSOR_ARCHITEW6432
}
else {
$arch = $env:PROCESSOR_ARCHITECTURE
}
switch ($arch) {
"AMD64" { return "windows-x86_64" }
"ARM64" { return "windows-arm64" }
"x86" { return "windows-i686" }
default { throw "Unsupported architecture: windows-$arch" }
}
}
function Get-LocalVersion {
param([string]$CorePath)
if (-not (Test-Path $CorePath)) {
Write-Host "未找到本地程序!" -ForegroundColor Yellow
return [System.Version]"0.0.0"
}
try {
$versionOutput = & $CorePath --version 2>$null
if ($versionOutput -match "easytier-core\s+([0-9]+\.[0-9]+\.[0-9]+)") {
return [System.Version]$matches[1]
}
else {
throw "无法解析返回值: $versionOutput"
}
}
catch {
throw "获取本地版本失败`n$_"
}
}
function Get-RemoteVersion {
param([PSCustomObject]$response)
try {
return [System.Version]$response.tag_name.TrimStart("v")
}
catch {
throw "无法从 GitHub 获取最新版本信息`n$response"
}
}
function Get-EasyTier {
$Arch = Get-SystemArchitecture
$tempDirectory = Join-Path $ScriptRoot "easytier_update"
try {
if (-not (Test-Path $tempDirectory)) {
New-Item -ItemType Directory -Path $tempDirectory | Out-Null
}
}
catch {
throw "无法创建文件夹 $tempDirectory`n$_"
}
try {
Write-Host "检查最新版本..." -ForegroundColor Green
$response = Invoke-RestMethodCompatible -Uri "https://api.github.com/repos/EasyTier/EasyTier/releases/latest"
$latestVersion = Get-RemoteVersion($response)
}
catch {
throw "获取最新版本失败。请检查网络连接或API请求达到上限`n$_"
}
$localVersion = Get-LocalVersion($EasyTierPath)
if ($localVersion -ge $latestVersion) {
Write-Host "EasyTier 已是最新版本 $localVersion" -ForegroundColor Green
return
}
$asset = $response.assets | Where-Object { $_.name -like "easytier-$Arch*.zip" } | Select-Object -First 1
if ($asset) {
Write-Output "发现新版本 $latestVersion"
$downloadUrl = $asset.browser_download_url
if ($UseGitHubProxy) {
$downloadUrl = "$GitHubProxy$downloadUrl"
}
}
else {
throw "未适配当前平台!"
}
$updateFile = Join-Path $tempDirectory $asset.name
try {
Write-Output "开始下载: $downloadUrl"
Invoke-WebRequestCompatible -Uri $downloadUrl -OutFile $updateFile
}
catch {
throw "下载失败!`n$_"
}
try {
Expand-ZipFile -ZipPath $updateFile -DestinationPath $tempDirectory
$extractedRoot = Get-ChildItem -Path $tempDirectory -Directory | Select-Object -First 1
$updateFileDirectory = $extractedRoot.FullName
}
catch {
throw "解压失败!`n$_"
}
Write-Host "准备就绪,开始更新..." -ForegroundColor Green
$activeServices = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq "Running" }
if ($activeServices) {
try {
Write-Host "发现正在运行的服务: $ServiceName" -ForegroundColor Yellow
Write-Host "停止 EasyTier 服务..." -ForegroundColor Yellow
$activeServices | Stop-Service -Force
}
catch {
throw "停止服务失败!`n$_"
}
}
try {
Write-Host "更新文件..."
Get-ChildItem -Path $updateFileDirectory | Copy-Item -Destination $ScriptRoot -Recurse -Force
}
catch {
throw "更新文件失败!`n$_"
}
$localVersion = Get-LocalVersion($EasyTierPath)
if ($localVersion -ge $latestVersion) {
if ($activeServices) {
try {
Write-Host "启动服务..." -ForegroundColor Green
$activeServices | Start-Service
}
catch {
throw "服务启动失败!`n$_"
}
}
Write-Host "EasyTier 已成功更新到版本 $latestVersion" -ForegroundColor Green
}
else {
throw "更新文件失败! 请检查脚本是否过时或者文件/文件夹是否被其他程序占用"
}
Remove-Item -Recurse -Force $tempDirectory
}
$HelpText = @"
EasyTier 服务管理脚本
【使用方式】
直接双击运行或在命令行中执行:
install.cmd [参数]
【可用参数】
-H / -? / -Help
显示此帮助信息并退出。
-U / -Update
更新 EasyTier 到最新版本
-X / -Uninstall
卸载 EasyTier 服务
-UGHP / -UseGitHubProxy
使用 GitHub 镜像代理下载 (默认: $false)
-GHP / -GitHubProxy <代理地址>
指定 GitHub 镜像代理地址 (默认: https://ghfast.top/)
-UP / -UseProxy
使用自定义代理 (默认: $false)
-P / -Proxy <代理地址>
指定自定义代理地址 (默认: http://127.0.0.1:7890)
-C / -ConfigType <类型>
指定配置模式,可选值:
* File 本地配置文件
* Remote 远程服务器集中管理
* CLI 使用命令行直接传参
-N / -ServiceName <名称>
指定安装的服务名称 (默认: EasyTierService)
<其他参数...>
当选择 CLI 模式时,用于传递自定义参数
【运行要求】
* 已安装 PowerShell 3.0 或更高版本
* 管理员权限 (自动检测并可请求提升)
【配置模式说明】
1. File 模式:
从指定的本地配置文件加载运行参数
脚本会提示输入配置文件路径 (可拖入文件)
2. Remote 模式:
从服务器集中管理配置,可输入自定义管理服务器地址
(例如: udp://x.x.x.x:22020/admin)
3. CLI 模式:
直接通过命令行参数传递任意配置
【代理设置说明】
* UseGitHubProxy 和 UseProxy 参数不能同时使用
* 使用 GitHub 镜像代理可加速下载
【示例】
1. 使用本地配置文件安装:
install.cmd -ConfigType File
2. 使用远程服务器并设定服务名称:
install.cmd -ConfigType Remote -ServiceName EasyTierService
3. 使用命令行传参:
install.cmd -ConfigType CLI --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010
4. 使用 GitHub 镜像代理更新:
install.cmd -Update -UseGitHubProxy
5. 使用系统代理安装:
install.cmd -UseProxy -Proxy http://127.0.0.1:8080
6. 卸载服务:
install.cmd -Uninstall
7. 显示帮助信息:
install.cmd -Help
"@
$WatchDogTemplate = @"
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>$((Get-Date).ToString("s"))</Date>
<URI>\EasyTierWatchDog</URI>
</RegistrationInfo>
<Triggers>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="System"&gt;&lt;Select Path="System"&gt;*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=107]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
</EventTrigger>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Microsoft-Windows-WLAN-AutoConfig/Operational"&gt;&lt;Select Path="Microsoft-Windows-WLAN-AutoConfig/Operational"&gt;*[System[Provider[@Name='Microsoft-Windows-WLAN-AutoConfig'] and EventID=8001]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
</EventTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>SYSTEM</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<Enabled>true</Enabled>
</Settings>
<Actions Context="Author">
<Exec>
<Command>cmd.exe</Command>
<Arguments>/c "net stop %#ServiceName#% &amp; net start %#ServiceName#%"</Arguments>
</Exec>
</Actions>
</Task>
"@
$host.ui.rawui.WindowTitle = "安装/卸载/更新 EasyTier 服务"
Clear-Host
$ScriptRoot = (Get-Location).Path
$RegistryPath = "HKLM:\SOFTWARE\EasyTierServiceManage"
$RegistryName = "Services"
$EasyTierPath = Join-Path $ScriptRoot "easytier-core.exe"
$OPTIONS = @()
$ErrorActionPreference = "Stop"
try {
if ($Help) {
Write-Host $HelpText
Show-Pause -Text "按任意键退出..."
exit 0
}
if ($UseProxy -and $UseGitHubProxy) {
throw "UseProxy 和 UseGitHubProxy 参数不能同时使用,请选择其中一种代理方式。"
}
if ($Uninstall) {
$services = Get-ServiceNames
if ($services.Count -lt 1 -and (-not (Test-ServiceNameExists -Name $ServiceName))) {
Write-Host "服务未安装" -ForegroundColor Red
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
Write-Host "正在停止服务 $ServiceName ..."
Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue
Write-Host "正在移除服务 $ServiceName ..."
Remove-ServiceCompatible -Name "$ServiceName" -ErrorAction SilentlyContinue
Remove-ServiceName -Name $ServiceName -ErrorAction SilentlyContinue
Write-Host "服务 $ServiceName 已卸载" -ForegroundColor Green
}
}
else {
foreach ($service in $services) {
if (Get-Service -Name $service -ErrorAction SilentlyContinue) {
Write-Host "正在停止服务 $service ..."
Stop-Service -Name $service -Force
Write-Host "正在移除服务 $service ..."
Remove-ServiceCompatible -Name "$service"
Remove-ServiceName -Name $service
}
Write-Host "服务 $service 已卸载" -ForegroundColor Green
}
}
Show-Pause -Text "按任意键退出..."
Unregister-ScheduledTask -TaskName "EasyTierWatchDog" -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
exit 0
}
if ($Update) {
Get-EasyTier
Show-Pause -Text "按任意键退出..."
exit 0
}
if (-not (Test-Path $EasyTierPath)) {
Get-EasyTier
}
if (-not $ConfigType) {
$choices = @(
[System.Management.Automation.Host.ChoiceDescription]::new("&File", "本地配置文件"),
[System.Management.Automation.Host.ChoiceDescription]::new("&Remote", "服务器集中管理"),
[System.Management.Automation.Host.ChoiceDescription]::new("&CLI", "命令行传参")
)
$selected = $Host.UI.PromptForChoice("您准备如何配置EasyTier?", "请选择: ", $choices, 0)
$ConfigType = @("File", "Remote", "CLI")[$selected]
}
switch ($ConfigType) {
"File" {
$OPTIONS += "--config-file $(Get-InputWithFileValidation -Prompt "配置文件路径(或将文件拖动到此处)")"
}
"Remote" {
if (Show-YesNoPrompt -Message "使" -DefaultIndex 1) {
$configServer = Get-InputWithNoNullOrWhiteSpace -Prompt "自定义管理服务器(格式: 协议://IP:端口/用户)"
}
else {
$configServer = Get-InputWithNoNullOrWhiteSpace -Prompt "官方服务器用户名"
}
$OPTIONS += "--config-server $configServer"
}
"CLI" {
if (-not $ServiceArgs -or $ServiceArgs.Count -eq 0) {
$OPTIONS += Get-InputWithNoNullOrWhiteSpace -Prompt "自定义启动参数"
}
else {
$OPTIONS += $ServiceArgs
}
}
default {
throw "未知配置类型: $ConfigType"
}
}
$BinaryPath = "`"$EasyTierPath`" $($OPTIONS -join ' ')"
Write-Host "生成的配置参数如下: " -ForegroundColor Yellow
Write-Host ($OPTIONS -join " ") -ForegroundColor DarkGray
if (Show-YesNoPrompt -Message "" -DefaultIndex 1) {
if (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue) {
Stop-Service -Name $ServiceName -Force | Out-Null
Remove-ServiceCompatible -Name $ServiceName
}
New-Service -Name $ServiceName -DisplayName "EasyTier" `
-Description "EasyTier 核心服务" `
-StartupType Automatic `
-BinaryPathName $BinaryPath | Out-Null
Start-Service -Name $ServiceName | Out-Null
Register-ScheduledTask -TaskName "EasyTierWatchDog" -User "SYSTEM" -Xml $WatchDogTemplate.Replace("%#ServiceName#%", $ServiceName) -Force | Out-Null
Save-ServiceName -Name $ServiceName
Write-Host "安装完成。" -ForegroundColor Green
}
else {
Write-Host "安装已取消。" -ForegroundColor Yellow
}
Show-Pause -Text "按任意键退出..."
}
catch {
Write-Host "发生错误: $_" -ForegroundColor Red
Show-Pause -Text "按任意键退出..."
Unregister-ScheduledTask -TaskName "EasyTierWatchDog" -Confirm:$false -ErrorAction SilentlyContinue
exit 1
}