Files
Ant-Browser/backend/browser_process_discovery_windows.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
}