mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
167 lines
4.9 KiB
Go
167 lines
4.9 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package backend
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func findBrowserUserDataProcessesOS(userDataDir string) ([]browserUserDataProcess, error) {
|
|
userDataDir = strings.TrimSpace(userDataDir)
|
|
if userDataDir == "" {
|
|
return nil, nil
|
|
}
|
|
fullUserDataDir, err := filepath.Abs(userDataDir)
|
|
if err != nil {
|
|
fullUserDataDir = userDataDir
|
|
}
|
|
|
|
psScript := `param([string]$UserDataDir)
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
$target = [System.IO.Path]::GetFullPath($UserDataDir).TrimEnd('\')
|
|
$items = @()
|
|
Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -match '--user-data-dir=' } | ForEach-Object {
|
|
$cmd = [string]$_.CommandLine
|
|
$match = [regex]::Match($cmd, '--user-data-dir=(?:"([^"]+)"|([^\s]+))')
|
|
if (-not $match.Success) { return }
|
|
$dir = $match.Groups[1].Value
|
|
if ([string]::IsNullOrWhiteSpace($dir)) { $dir = $match.Groups[2].Value }
|
|
if ([string]::IsNullOrWhiteSpace($dir)) { return }
|
|
try { $dir = [System.IO.Path]::GetFullPath($dir).TrimEnd('\') } catch {}
|
|
if (-not $dir.Equals($target, [System.StringComparison]::OrdinalIgnoreCase)) { return }
|
|
$port = 0
|
|
$portMatch = [regex]::Match($cmd, '--remote-debugging-port=(\d+)')
|
|
if ($portMatch.Success) { [void][int]::TryParse($portMatch.Groups[1].Value, [ref]$port) }
|
|
$items += [pscustomobject]@{ pid = [int]$_.ProcessId; debugPort = [int]$port; commandLine = $cmd }
|
|
}
|
|
$items | ConvertTo-Json -Compress
|
|
`
|
|
|
|
output, err := runPowerShellJSON(psScript, "-UserDataDir", fullUserDataDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
output = bytes.TrimSpace(output)
|
|
if len(output) == 0 || bytes.Equal(output, []byte("null")) {
|
|
return nil, nil
|
|
}
|
|
|
|
var list []browserUserDataProcess
|
|
if bytes.HasPrefix(output, []byte("[")) {
|
|
if err := json.Unmarshal(output, &list); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
var item browserUserDataProcess
|
|
if err := json.Unmarshal(output, &item); err != nil {
|
|
return nil, err
|
|
}
|
|
list = append(list, item)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func terminateBrowserUserDataProcessOS(pid int, timeout time.Duration) error {
|
|
if pid <= 0 {
|
|
return nil
|
|
}
|
|
if timeout <= 0 {
|
|
timeout = 5 * time.Second
|
|
}
|
|
if tryCloseBrowserViaCDPPID(pid, timeout) {
|
|
return nil
|
|
}
|
|
|
|
softKillCmd := exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/T")
|
|
hideWindow(softKillCmd)
|
|
_ = softKillCmd.Run()
|
|
if waitProcessExitWindows(pid, timeout) {
|
|
return nil
|
|
}
|
|
|
|
forceKillCmd := exec.Command("taskkill", "/F", "/PID", strconv.Itoa(pid), "/T")
|
|
hideWindow(forceKillCmd)
|
|
if err := forceKillCmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
if !waitProcessExitWindows(pid, 2*time.Second) {
|
|
return fmt.Errorf("process still running")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func tryCloseBrowserViaCDPPID(pid int, timeout time.Duration) bool {
|
|
processes, err := findBrowserProcessByPID(pid)
|
|
if err != nil || len(processes) == 0 {
|
|
return false
|
|
}
|
|
debugPort := processes[0].DebugPort
|
|
if debugPort <= 0 {
|
|
debugPort = parseRemoteDebuggingPort(processes[0].CommandLine)
|
|
}
|
|
return tryCloseBrowserViaCDP(debugPort, timeout)
|
|
}
|
|
|
|
func findBrowserProcessByPID(pid int) ([]browserUserDataProcess, error) {
|
|
if pid <= 0 {
|
|
return nil, nil
|
|
}
|
|
psScript := `param([int]$PidValue)
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
$p = Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -eq $PidValue } | Select-Object -First 1
|
|
if ($null -eq $p) { exit 0 }
|
|
$cmd = [string]$p.CommandLine
|
|
$port = 0
|
|
$portMatch = [regex]::Match($cmd, '--remote-debugging-port=(\d+)')
|
|
if ($portMatch.Success) { [void][int]::TryParse($portMatch.Groups[1].Value, [ref]$port) }
|
|
[pscustomobject]@{ pid = [int]$p.ProcessId; debugPort = [int]$port; commandLine = $cmd } | ConvertTo-Json -Compress
|
|
`
|
|
output, err := runPowerShellJSON(psScript, "-PidValue", strconv.Itoa(pid))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
output = bytes.TrimSpace(output)
|
|
if len(output) == 0 || bytes.Equal(output, []byte("null")) {
|
|
return nil, nil
|
|
}
|
|
var item browserUserDataProcess
|
|
if err := json.Unmarshal(output, &item); err != nil {
|
|
return nil, err
|
|
}
|
|
return []browserUserDataProcess{item}, nil
|
|
}
|
|
|
|
func runPowerShellJSON(script string, args ...string) ([]byte, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
|
defer cancel()
|
|
|
|
powershellPath := `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
|
|
if _, err := exec.LookPath(powershellPath); err != nil {
|
|
if fallbackPath, lookErr := exec.LookPath("powershell.exe"); lookErr == nil {
|
|
powershellPath = fallbackPath
|
|
}
|
|
}
|
|
|
|
commandArgs := []string{"-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script}
|
|
commandArgs = append(commandArgs, args...)
|
|
cmd := exec.CommandContext(ctx, powershellPath, commandArgs...)
|
|
hideWindow(cmd)
|
|
output, err := cmd.Output()
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return nil, fmt.Errorf("powershell query timed out")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return output, nil
|
|
}
|