Refine ProxyWarden routing and config flows

This commit is contained in:
2026-07-10 21:15:17 +03:00
parent 9fd0a8c0b9
commit dbba3806cc
31 changed files with 1823 additions and 191 deletions
@@ -0,0 +1,238 @@
param(
[string]$InstallRoot = "",
[switch]$ForceRemoveWindowsPacketFilter
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function New-Result {
param(
[bool]$Success,
[string]$Message,
[hashtable]$Details = @{}
)
[ordered]@{
success = $Success
message = $Message
details = $Details
} | ConvertTo-Json -Depth 8 -Compress
}
function Get-FullPath([string]$Path) {
return [System.IO.Path]::GetFullPath($Path).TrimEnd("\")
}
function Test-PathInside([string]$Path, [string]$Root) {
if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
try {
$fullPath = Get-FullPath $Path
$fullRoot = Get-FullPath $Root
return $fullPath.StartsWith($fullRoot + "\", [StringComparison]::OrdinalIgnoreCase)
} catch {
return $false
}
}
function Assert-SafeInstallRoot([string]$Root) {
if ([string]::IsNullOrWhiteSpace($Root)) {
throw "InstallRoot is empty."
}
$full = Get-FullPath $Root
if ($full -match "^[A-Za-z]:\\?$") {
throw "Refusing to use drive root as InstallRoot: $full"
}
if ($full -match "\\Windows($|\\)" -or $full -match "\\ProgramData$" -or $full -match "\\Users$") {
throw "Refusing unsafe InstallRoot: $full"
}
$knownAppFiles = @(
(Join-Path $full "proxywarden.exe"),
(Join-Path $full "uninstall.exe"),
(Join-Path $full "bundled\cleanup\uninstall-managed-components.ps1")
)
foreach ($candidate in $knownAppFiles) {
if (Test-Path -LiteralPath $candidate) { return $full }
}
throw "InstallRoot does not look like a ProxyWarden install directory: $full"
}
function Resolve-SafeComponentDir([string]$Root, [string]$Leaf) {
$componentRoot = Join-Path $Root "components"
$path = Join-Path $componentRoot $Leaf
$full = Get-FullPath $path
$expectedParent = Get-FullPath $componentRoot
$actualLeaf = Split-Path -Leaf $full
if ($actualLeaf -ne $Leaf) {
throw "Unexpected component directory leaf: $full"
}
if (-not $full.StartsWith($expectedParent + "\", [StringComparison]::OrdinalIgnoreCase)) {
throw "Component directory is outside ProxyWarden components root: $full"
}
return $full
}
function Read-ComponentMarker([string]$Dir) {
$markerPath = Join-Path $Dir "proxywarden-component.json"
if (-not (Test-Path -LiteralPath $markerPath)) { return $null }
try {
return Get-Content -LiteralPath $markerPath -Raw -Encoding UTF8 | ConvertFrom-Json
} catch {
return $null
}
}
function Get-MarkerBool($Marker, [string]$Name) {
if ($null -eq $Marker) { return $false }
$property = $Marker.PSObject.Properties[$Name]
if ($null -eq $property) { return $false }
return [bool]$property.Value
}
function Get-ServiceRecord([string]$Name) {
$escaped = $Name.Replace("'", "''")
return Get-CimInstance Win32_Service -Filter "Name='$escaped'" -ErrorAction SilentlyContinue
}
function Get-ServiceImagePath($Record) {
if ($null -eq $Record -or [string]::IsNullOrWhiteSpace([string]$Record.PathName)) {
return $null
}
$pathName = ([string]$Record.PathName).Trim()
if ($pathName -match '^"([^"]+)"') { return $Matches[1] }
if ($pathName -match '^(.+?\.exe)\b') { return $Matches[1].Trim() }
return $pathName
}
function Stop-ServiceRecord($Record) {
if ($null -eq $Record) { return }
$service = Get-Service -Name $Record.Name -ErrorAction SilentlyContinue
if ($null -ne $service -and $service.Status -ne "Stopped") {
Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue
$service = Get-Service -Name $Record.Name -ErrorAction SilentlyContinue
if ($null -ne $service) {
try { $service.WaitForStatus("Stopped", [TimeSpan]::FromSeconds(12)) } catch {}
}
}
$recordAfterStop = Get-ServiceRecord $Record.Name
if ($null -ne $recordAfterStop -and [int]$recordAfterStop.ProcessId -gt 0) {
taskkill.exe /PID ([int]$recordAfterStop.ProcessId) /F | Out-Null
Start-Sleep -Milliseconds 500
}
}
function Remove-ManagedService {
param(
[string[]]$Names,
[string]$InstallRoot,
[string]$UninstallExe = ""
)
$removed = @()
foreach ($name in $Names) {
$record = Get-ServiceRecord $name
if ($null -eq $record) { continue }
$imagePath = Get-ServiceImagePath $record
if (-not [string]::IsNullOrWhiteSpace($imagePath) -and -not (Test-PathInside $imagePath $InstallRoot)) {
continue
}
Stop-ServiceRecord $record
if (-not [string]::IsNullOrWhiteSpace($UninstallExe) -and (Test-Path -LiteralPath $UninstallExe)) {
Push-Location (Split-Path -Parent $UninstallExe)
try { & $UninstallExe uninstall | Out-Null } finally { Pop-Location }
}
$record = Get-ServiceRecord $name
if ($null -ne $record) {
sc.exe delete $name | Out-Null
}
$removed += $name
}
return $removed
}
function Remove-SafeDirectory([string]$Path, [string]$Root) {
if (-not (Test-Path -LiteralPath $Path)) { return $false }
if (-not (Test-PathInside $Path $Root)) {
throw "Refusing to remove directory outside InstallRoot: $Path"
}
Remove-Item -LiteralPath $Path -Recurse -Force
return $true
}
function Get-InstalledProgram([string]$Pattern) {
$paths = @(
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
return Get-ItemProperty -Path $paths -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match $Pattern } |
Select-Object -First 1 DisplayName, DisplayVersion, PSChildName, UninstallString, QuietUninstallString
}
function Resolve-MsiProductCode($Program, [string]$Label) {
if ($null -eq $Program) { return $null }
if ($Program.PSChildName -match "^\{[0-9A-Fa-f-]{36}\}$") {
return $Program.PSChildName
}
foreach ($candidate in @($Program.QuietUninstallString, $Program.UninstallString)) {
if ($candidate -match "\{[0-9A-Fa-f-]{36}\}") {
return $Matches[0]
}
}
throw "Could not resolve MSI product code for $Label."
}
function Uninstall-MsiProgram($Program, [string]$Label) {
$productCode = Resolve-MsiProductCode $Program $Label
if ([string]::IsNullOrWhiteSpace($productCode)) { return $false }
$logPath = Join-Path ([System.IO.Path]::GetTempPath()) "proxywarden-$Label-uninstall.log"
$process = Start-Process -FilePath "msiexec.exe" -ArgumentList @("/x", $productCode, "/qn", "/norestart", "/L*v", $logPath) -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -ne 0 -and $process.ExitCode -ne 3010 -and $process.ExitCode -ne 1605) {
throw "$Label uninstall exited with code $($process.ExitCode). MSI log: $logPath"
}
return $true
}
try {
$details = @{}
$root = Assert-SafeInstallRoot $InstallRoot
$details.installRoot = $root
$proxifyreDir = Resolve-SafeComponentDir $root "ProxiFyre"
$singboxDir = Resolve-SafeComponentDir $root "sing-box"
$proxifyreMarker = Read-ComponentMarker $proxifyreDir
$removePacketFilter = [bool]$ForceRemoveWindowsPacketFilter -or (Get-MarkerBool $proxifyreMarker "packetFilterInstalledByProxyWarden")
$details.removedProxiFyreServices = Remove-ManagedService -Names @("ProxiFyreService", "ProxiFyre") -InstallRoot $root -UninstallExe (Join-Path $proxifyreDir "ProxiFyre.exe")
$details.removedSingBoxServices = Remove-ManagedService -Names @("ProxyWardenSingBox") -InstallRoot $root -UninstallExe (Join-Path $singboxDir "ProxyWardenSingBox.exe")
$details.removedProxiFyreDir = Remove-SafeDirectory $proxifyreDir $root
$details.removedSingBoxDir = Remove-SafeDirectory $singboxDir $root
if ($removePacketFilter) {
$packetFilter = Get-InstalledProgram "Windows Packet Filter|WinpkFilter|NDISAPI"
$details.removedWindowsPacketFilter = Uninstall-MsiProgram $packetFilter "windows-packet-filter"
} else {
$details.removedWindowsPacketFilter = $false
}
New-Result -Success $true -Message "ProxyWarden managed components cleanup completed." -Details $details
exit 0
} catch {
New-Result -Success $false -Message $_.Exception.Message -Details @{}
exit 1
}
@@ -0,0 +1,6 @@
!macro NSIS_HOOK_PREUNINSTALL
DetailPrint "ProxyWarden: cleaning managed components"
nsExec::ExecToLog 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\bundled\cleanup\uninstall-managed-components.ps1" -InstallRoot "$INSTDIR"'
Pop $0
DetailPrint "ProxyWarden cleanup exit code: $0"
!macroend
+31
View File
@@ -0,0 +1,31 @@
{
"generatedAt": "2026-07-09T16:13:27.3087159Z",
"architectures": [
"x64"
],
"proxifyreRelease": "v2.2.1",
"windowsPacketFilterRelease": "v3.6.2",
"files": [
{
"id": "proxifyre-x64",
"name": "ProxiFyre-v2.2.1-x64-signed.zip",
"sha256": "c38ca1caa68cd730712f5c0911e4240711bf9e7684988ae64ed04ec693cce899",
"size": 1372483,
"sourceUrl": "https://github.com/wiresock/proxifyre/releases/download/v2.2.1/ProxiFyre-v2.2.1-x64-signed.zip"
},
{
"id": "packet-filter-x64",
"name": "Windows.Packet.Filter.3.6.2.1.x64.msi",
"sha256": "9c388c0b7f189f7fa98720bae2caecf7d64f30910838b80b438ecf8956b8502c",
"size": 819200,
"sourceUrl": "https://github.com/wiresock/ndisapi/releases/download/v3.6.2/Windows.Packet.Filter.3.6.2.1.x64.msi"
},
{
"id": "vc-runtime-x64",
"name": "vc_redist.x64.exe",
"sha256": "843068991daaa1f73ad9f6239bce4d0f6a07a51f18c37ea2a867e9beca71295c",
"size": 18731856,
"sourceUrl": "https://aka.ms/vc14/vc_redist.x64.exe"
}
]
}
Binary file not shown.