# Uninstall-ZBEVPN.ps1
# Clean uninstaller for ZBEVPN (Service + GUI + shortcuts + install folder)
# Run from elevated PowerShell:
#   powershell -ExecutionPolicy Bypass -File .\Uninstall-ZBEVPN.ps1
#
# By default this keeps C:\ProgramData\ZBEVPN (state + logs).
# Use -RemoveProgramData to delete ProgramData as well.
# WireGuard is NOT removed by default. Use -RemoveWireGuard to attempt removal.
param(
  [switch]$RemoveProgramData,
  [switch]$RemoveWireGuard
)

Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"


# ----------------------------
# Config (must match installer)
# ----------------------------
$CompanyName   = "ZBEVPN"
$SvcName       = "ZBEVPN"
$InstallRoot   = Join-Path $env:ProgramFiles "ZBEVPN"
$GuiDir        = Join-Path $InstallRoot "GUI"
$DataRoot      = Join-Path $env:ProgramData "ZBEVPN"
$LogDir        = Join-Path $DataRoot "logs"
$StateDir      = Join-Path $DataRoot "state"
$InstallStatePath = Join-Path $StateDir "install-state.json"

$PublicDesktop = [Environment]::GetFolderPath('CommonDesktopDirectory')
$DesktopShortcut = Join-Path $PublicDesktop "ZBEVPN.lnk"

$CommonPrograms = [Environment]::GetFolderPath('CommonPrograms')
$StartMenuFolder = Join-Path $CommonPrograms "ZBEVPN"
$StartMenuShortcut = Join-Path $StartMenuFolder "ZBEVPN.lnk"

# WireGuard uninstall discovery
$WireGuardExePath = Join-Path ${env:ProgramFiles} "WireGuard\wireguard.exe"

$ScriptVersionStamp = "2026-03-17 12:52:00"

# ----------------------------
# Logging
# ----------------------------
function Write-Log([string]$msg) {
  $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
  $line = "[$ts] $msg"
  Write-Host $line
  try {
    if (-not (Test-Path -LiteralPath $LogDir)) {
      New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
    }
    $logFile = Join-Path $LogDir "uninstall.log"
    $line | Out-File -FilePath $logFile -Append -Encoding utf8
  } catch {
    # If ProgramData is unavailable, at least print to console.
  }
}

function Assert-Admin {
  $id = [Security.Principal.WindowsIdentity]::GetCurrent()
  $p  = New-Object Security.Principal.WindowsPrincipal($id)
  if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    throw "Uninstaller must be run as Administrator."
  }
}

function Stop-ProcessIfRunning([string]$name) {
  try {
    $procs = Get-Process -Name $name -ErrorAction SilentlyContinue
    if ($procs) {
      Write-Log "Stopping process(es): $name"
      $procs | Stop-Process -Force -ErrorAction SilentlyContinue
    }
  } catch {}
}

function Remove-PathForce([string]$path) {
  if ([string]::IsNullOrWhiteSpace($path)) { return }
  if (Test-Path -LiteralPath $path) {
    Write-Log "Removing: $path"
    try {
      Remove-Item -LiteralPath $path -Recurse -Force -ErrorAction Stop
    } catch {
      Write-Log ("WARNING: Failed to remove {0}: {1}" -f $path, $_.Exception.Message)
      throw
    }
  } else {
    Write-Log "Not present: $path"
  }
}

function Stop-And-DeleteService([string]$svcName) {
  # Stop
  try {
    $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue
    if ($svc) {
      if ($svc.Status -ne 'Stopped') {
        Write-Log "Stopping service: $svcName"
        try { sc.exe stop $svcName 2>$null | Out-Null } catch {}
        try { $svc.WaitForStatus('Stopped', (New-TimeSpan -Seconds 20)) } catch {}
      }
    }
  } catch {}

  # Delete
  Write-Log "Deleting service (if any): $svcName"
  try {
    sc.exe delete $svcName 2>$null | Out-Null
  } catch {}

  Start-Sleep -Seconds 2
}

function Try-RemoveWireGuard {
  if (-not $RemoveWireGuard) {
    Write-Log "WireGuard removal not requested; leaving WireGuard installed."
    return
  }

  Write-Log "WireGuard removal requested."

  Stop-ProcessIfRunning "wireguard"

  # Find uninstall entry by DisplayName == "WireGuard"
  $keys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
  )

  $entry = $null
  foreach ($k in $keys) {
    try {
      $entry = Get-ItemProperty $k -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq "WireGuard" } | Select-Object -First 1
      if ($entry) { break }
    } catch {}
  }

  if (-not $entry) {
    Write-Log "WireGuard uninstall entry not found in registry. Skipping."
    return
  }

  $cmd = $entry.UninstallString
  if ([string]::IsNullOrWhiteSpace($cmd)) {
    Write-Log "WireGuard uninstall string missing. Skipping."
    return
  }

  Write-Log "WireGuard uninstall string: $cmd"

  # Normalize MSI command to silent uninstall if possible
  if ($cmd -match "MsiExec\.exe") {
    $cmd2 = $cmd -replace "/I", "/X"
    if ($cmd2 -notmatch "/qn") { $cmd2 += " /qn" }
    if ($cmd2 -notmatch "/norestart") { $cmd2 += " /norestart" }
    Write-Log "Running: $cmd2"
    cmd.exe /c $cmd2 | Out-Null
  } else {
    Write-Log "Running uninstall command (as-is)."
    cmd.exe /c $cmd | Out-Null
  }

  Start-Sleep -Seconds 2
  Stop-ProcessIfRunning "wireguard"
  Write-Log "WireGuard uninstall attempted."
}

function Remove-ZBEVPNUninstallRegistryEntries {
  param(
    [string]$DisplayNamePrefix = "ZBEVPN"
  )

  $roots = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
  )

  $removed = 0

  foreach ($root in $roots) {
    if (-not (Test-Path -LiteralPath $root)) { continue }

    $keys = Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue
    foreach ($k in $keys) {
      try {
        $p = Get-ItemProperty -LiteralPath $k.PSPath -ErrorAction SilentlyContinue
        $dn = [string]$p.DisplayName
        if ([string]::IsNullOrWhiteSpace($dn)) { continue }

        if ($dn -like "$DisplayNamePrefix*") {
          Write-Log "Removing uninstall entry key: $($k.Name) (DisplayName='$dn')"
          Remove-Item -LiteralPath $k.PSPath -Recurse -Force -ErrorAction SilentlyContinue
          $removed++
        }
      } catch {}
    }
  }

  # Also try the exact keys (current behavior), harmless if already removed:
  Remove-Item "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ZBEVPN" -Recurse -Force -ErrorAction SilentlyContinue
  Remove-Item "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\ZBEVPN" -Recurse -Force -ErrorAction SilentlyContinue
  Remove-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ZBEVPN" -Recurse -Force -ErrorAction SilentlyContinue
  Remove-Item "HKCU:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\ZBEVPN" -Recurse -Force -ErrorAction SilentlyContinue

  Write-Log "Uninstall registry cleanup removed $removed matching entries (plus exact-key attempts)."
}
function Remove-ZBEVPNUninstallRegistryEntries {
  param([string]$DisplayNamePrefix = "ZBEVPN")

  $roots = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
    "HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
  )

  $removed = 0

  foreach ($root in $roots) {
    if (-not (Test-Path -LiteralPath $root)) { continue }
    foreach ($k in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) {
      try {
        $p = Get-ItemProperty -LiteralPath $k.PSPath -ErrorAction SilentlyContinue
        $dn = [string]$p.DisplayName
        if ([string]::IsNullOrWhiteSpace($dn)) { continue }
        if ($dn -like "$DisplayNamePrefix*") {
          Write-Log "Removing uninstall entry key: $($k.Name) (DisplayName='$dn')"
          Remove-Item -LiteralPath $k.PSPath -Recurse -Force -ErrorAction SilentlyContinue
          $removed++
        }
      } catch {}
    }
  }

  Write-Log "Uninstall registry cleanup removed $removed matching entries."
}
# ----------------------------
# Main
# ----------------------------
try {
  Assert-Admin

  Write-Log "=== ZBEVPN Uninstaller starting ==="
  Write-Log "ScriptVersionStamp: $ScriptVersionStamp"
  Write-Log "InstallRoot: $InstallRoot"
  Write-Log "ProgramData: $DataRoot"
  Write-Log "RemoveProgramData: $RemoveProgramData"
  Write-Log "RemoveWireGuard: $RemoveWireGuard"

  # Kill GUI + service process names that might lock files
  Write-Log "Stopping running ZBEVPN / WireGuard processes"


  Start-Sleep -Seconds 2
  Stop-ProcessIfRunning "zbevpn-gui"
  Stop-ProcessIfRunning "zbevpn_gui"
  Stop-ProcessIfRunning "windows-client"
  Stop-ProcessIfRunning "ZBEVPN"

  # Service first
  Stop-And-DeleteService $SvcName

  # Clobber if it's still running (do not let taskkill failures abort uninstall)
  cmd.exe /c "taskkill /f /im zbevpn-gui.exe >nul 2>nul"
  cmd.exe /c "taskkill /f /im windows-client.exe >nul 2>nul"
  cmd.exe /c "taskkill /f /im wireguard.exe >nul 2>nul"
  cmd.exe /c "taskkill /f /im wireguard-ui.exe >nul 2>nul"
  cmd.exe /c "taskkill /f /im wireguard-service.exe >nul 2>nul"


  # Shortcuts
  try { Remove-PathForce $DesktopShortcut } catch { Write-Log "WARNING: Desktop shortcut removal error: $($_.Exception.Message)" }
  try { Remove-PathForce $StartMenuShortcut } catch { Write-Log "WARNING: Start menu shortcut removal error: $($_.Exception.Message)" }
  try { Remove-PathForce $StartMenuFolder } catch { Write-Log "WARNING: Start menu folder removal error: $($_.Exception.Message)" }

  # Install folder
  if (Test-Path -LiteralPath $InstallRoot) {
    Remove-PathForce $InstallRoot
  } else {
    Write-Log "Install root not present."
  }

  if ($RemoveProgramData) {
    Write-Log "Cleaning ProgramData folders"

    $folders = @(
      "C:\ProgramData\ZBEVPN",
      "C:\ProgramData\zbeugene-vpn",
      "C:\ProgramData\zbeugene7-vpn"
    )

    foreach ($f in $folders) {
      if (Test-Path -LiteralPath $f) {
        Write-Log "Removing $f"
        Remove-Item -LiteralPath $f -Recurse -Force -ErrorAction SilentlyContinue
      }
    }
  } else {
    Write-Log "RemoveProgramData not requested; preserving ProgramData folders."
  }

  Write-Log "Removing uninstall registry entries"
  Remove-ZBEVPNUninstallRegistryEntries -DisplayNamePrefix "ZBEVPN"
  
  Try-RemoveWireGuard
  Write-Log "Verifying uninstall registry entry removal"

  Write-Log "=== ZBEVPN Uninstaller finished OK ==="
  exit 0
}
catch {
  Write-Log "ERROR: $($_.Exception.Message)"
  Write-Log "Uninstall failed."
  exit 1
}
