//go:build windows package files import ( "context" "fmt" "path/filepath" "strings" ) type windowsExecutablePicker struct{ runner commandRunner } func NewExecutablePicker() ExecutablePicker { return windowsExecutablePicker{runner: execRunner{}} } func newExecutablePickerWithRunner(runner commandRunner) windowsExecutablePicker { return windowsExecutablePicker{runner: runner} } func (p windowsExecutablePicker) ChooseExecutable(ctx context.Context) (string, error) { if err := ctx.Err(); err != nil { return "", err } output, err := p.runner.Run(ctx, "powershell.exe", "-NoProfile", "-NonInteractive", "-STA", "-WindowStyle", "Hidden", "-Command", executableDialogScript) if err != nil { if ctxErr := ctx.Err(); ctxErr != nil { return "", ctxErr } return "", fmt.Errorf("open executable picker: %w", err) } selected := strings.TrimSpace(string(output)) if selected == "" { return "", ErrSelectionCanceled } if !filepath.IsAbs(selected) { return "", fmt.Errorf("executable picker returned a non-absolute path") } if !strings.EqualFold(filepath.Ext(selected), ".exe") { return "", fmt.Errorf("executable picker returned a non-executable path") } return filepath.Clean(selected), nil } const executableDialogScript = `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) Add-Type -AssemblyName System.Windows.Forms $dialog = New-Object System.Windows.Forms.OpenFileDialog $dialog.Title = '选择 Chrome 或 Edge 可执行文件' $dialog.Filter = '浏览器可执行文件 (*.exe)|*.exe|所有文件 (*.*)|*.*' $dialog.CheckFileExists = $true $dialog.Multiselect = $false if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Out.Write($dialog.FileName) }`