# 管理员权限执行指令 > 适用于 Windows 平台,检查是否以管理员身份运行及提权执行命令 --- ## 1. 检测当前是否以管理员运行 ```go package main import ( "fmt" "os/user" "runtime" "syscall" "unsafe" ) var ( shell32 = syscall.NewLazyDLL("shell32.dll") isUserAnAdmin = shell32.NewProc("IsUserAnAdmin") ) func isAdmin() bool { if runtime.GOOS != "windows" { // Linux/macOS: 检测 UID 是否为 0 u, err := user.Current() if err != nil { return false } return u.Uid == "0" } ret, _, _ := isUserAnAdmin.Call() return ret != 0 } func main() { if isAdmin() { fmt.Println("当前以管理员身份运行") } else { fmt.Println("当前以普通用户身份运行") } } ``` --- ## 2. 以管理员身份重新启动自身 ```go func runAsAdmin() error { if runtime.GOOS != "windows" { return fmt.Errorf("此功能仅支持 Windows") } exe, _ := os.Executable() verb := "runas" cwd, _ := os.Getwd() verbPtr, _ := syscall.UTF16PtrFromString(verb) exePtr, _ := syscall.UTF16PtrFromString(exe) cwdPtr, _ := syscall.UTF16PtrFromString(cwd) var showCmd int32 = 1 // SW_NORMAL return syscall.ShellExecute(0, verbPtr, exePtr, nil, cwdPtr, showCmd) } // 使用方式:检测到非管理员时提权重启 func ensureAdmin() { if !isAdmin() { fmt.Println("需要管理员权限,正在提权...") runAsAdmin() os.Exit(0) } } ``` > **注意:** `runAsAdmin` 会弹出 UAC 弹窗,用户需点击"是"确认。 --- ## 3. 以管理员身份执行任意程序 ```go func runAsAdminExe(targetExe string, args string) error { verbPtr, _ := syscall.UTF16PtrFromString("runas") exePtr, _ := syscall.UTF16PtrFromString(targetExe) // args 传入时是 lpParameters,填充启动参数 paramsPtr, _ := syscall.UTF16PtrFromString(args) dirPtr, _ := syscall.UTF16PtrFromString("") var showCmd int32 = 1 return syscall.ShellExecute(0, verbPtr, exePtr, paramsPtr, dirPtr, showCmd) } ``` --- ## 4. 使用 PowerShell 管理员执行命令 ```go func runAsAdminPSCommand(command string) *exec.Cmd { // 组装 PowerShell 命令,-Verb RunAs 触发 UAC 提权 psCmd := fmt.Sprintf( `Start-Process PowerShell -Verb RunAs -ArgumentList '-NoProfile -Command "%s"'`, strings.ReplaceAll(command, `"`, `\"`), ) return exec.Command("powershell", "-Command", psCmd) } ``` **缺点:** `Start-Process` 会开启新窗口,无法直接获取输出。如需获取输出,见下方方式。 --- ## 5. 管理员执行命令 + 获取输出 ### 方式 A:不走 PowerShell,纯 ShellExecuteEx + cmd.exe ```go func runAsAdminCmd(command string) (string, error) { // 输出重定向到临时文件 tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("cmdout_%d.txt", os.Getpid())) // 拼命令: cmd /c "your_command > tmpFile 2>&1" fullCmd := fmt.Sprintf(`%s > "%s" 2>&1`, command, tmpFile) cmdLine, _ := syscall.UTF16PtrFromString(`/c ` + fullCmd) var op uint32 shell32 := syscall.NewLazyDLL("shell32.dll") procShellExecuteEx := shell32.NewProc("ShellExecuteExW") // SEE_MASK_NOCLOSEPROCESS = 0x40 — 获取进程句柄以便等待 type SHELLEXECUTEINFO struct { cbSize uint32 fMask uint32 hwnd syscall.Handle lpVerb *uint16 lpFile *uint16 lpParameters *uint16 lpDirectory *uint16 nShow int32 hInstApp uintptr lpIDList uintptr lpClass *uint16 hkeyClass syscall.Handle dwHotKey uint32 hIcon syscall.Handle hProcess syscall.Handle } verb, _ := syscall.UTF16PtrFromString("runas") cmdexe, _ := syscall.UTF16PtrFromString("cmd.exe") info := SHELLEXECUTEINFO{ cbSize: uint32(unsafe.Sizeof(SHELLEXECUTEINFO{})), fMask: 0x40, // SEE_MASK_NOCLOSEPROCESS lpVerb: verb, lpFile: cmdexe, lpParameters: cmdLine, nShow: 0, // SW_HIDE 隐藏窗口 } ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(&info))) if ret == 0 { return "", fmt.Errorf("ShellExecuteEx 失败") } defer syscall.CloseHandle(info.hProcess) // 等待进程结束 syscall.WaitForSingleObject(info.hProcess, syscall.INFINITE) // 读取输出文件 data, err := os.ReadFile(tmpFile) os.Remove(tmpFile) if err != nil { return "", err } return string(data), nil } ``` **关键点:** - `SEE_MASK_NOCLOSEPROCESS (0x40)` — 获取进程句柄 - `WaitForSingleObject` — 等待提权后的 cmd.exe 执行完毕 - `SW_HIDE (0)` — 隐藏 cmd 黑窗口 - `> tmpFile 2>&1` — stdout 和 stderr 都重定向到临时文件 - 无需 PowerShell,纯 Win32 API ### 方式 B:PowerShell 方式(兼容性好,但依赖 PS) ```go func runAsAdminWithPS(command string) (string, error) { tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("cmdout_%d.txt", os.Getpid())) psCmd := fmt.Sprintf( `Start-Process cmd -Verb RunAs -Wait -WindowStyle Hidden -RedirectStandardOutput "%s" -ArgumentList '/c %s'`, tmpFile, command, ) exec.Command("powershell", "-Command", psCmd).Run() data, err := os.ReadFile(tmpFile) os.Remove(tmpFile) return string(data), err } ``` --- ## 6. 完整示例:Fyne GUI 管理员指令执行器 ```go package main import ( "fmt" "os" "path/filepath" "runtime" "sync" "syscall" "unsafe" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" ) var isAdmin bool func init() { if runtime.GOOS == "windows" { shell32 := syscall.NewLazyDLL("shell32.dll") proc := shell32.NewProc("IsUserAnAdmin") ret, _, _ := proc.Call() isAdmin = ret != 0 } } func runAdminCmd(command string) (string, error) { tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("srm_admin_out_%d.txt", os.Getpid())) fullCmd := fmt.Sprintf(`%s > "%s" 2>&1`, command, tmpFile) cmdLine, _ := syscall.UTF16PtrFromString(`/c ` + fullCmd) type SHELLEXECUTEINFO struct { cbSize, fMask uint32 hwnd syscall.Handle lpVerb, lpFile *uint16 lpParameters *uint16 lpDirectory, _ *uint16 nShow int32 _, _, _, _ uintptr hProcess syscall.Handle } verb, _ := syscall.UTF16PtrFromString("runas") cmdexe, _ := syscall.UTF16PtrFromString("cmd.exe") info := SHELLEXECUTEINFO{ cbSize: uint32(unsafe.Sizeof(SHELLEXECUTEINFO{})), fMask: 0x40, // SEE_MASK_NOCLOSEPROCESS lpVerb: verb, lpFile: cmdexe, lpParameters: cmdLine, nShow: 0, // SW_HIDE } proc := syscall.NewLazyDLL("shell32.dll").NewProc("ShellExecuteExW") ret, _, _ := proc.Call(uintptr(unsafe.Pointer(&info))) if ret == 0 { return "", fmt.Errorf("ShellExecuteEx 失败") } defer syscall.CloseHandle(info.hProcess) syscall.WaitForSingleObject(info.hProcess, syscall.INFINITE) data, err := os.ReadFile(tmpFile) os.Remove(tmpFile) return string(data), err } func main() { myApp := app.New() myWindow := myApp.NewWindow("管理员指令执行器") myWindow.Resize(fyne.NewSize(600, 400)) var mu sync.Mutex cmdEntry := widget.NewEntry() cmdEntry.SetPlaceHolder("输入命令,如: ipconfig /flushdns") statusLabel := widget.NewLabel("当前状态: 用户") if isAdmin { statusLabel.SetText("当前状态: 管理员 ✓") } output := widget.NewMultiLineEntry() output.Disable() progress := widget.NewProgressBarInfinite() progress.Hide() runBtn := widget.NewButton("以管理员身份执行", func() { command := cmdEntry.Text if command == "" { return } go func() { mu.Lock() progress.Show() runBtn.Disable() mu.Unlock() out, err := runAdminCmd(command) mu.Lock() progress.Hide() runBtn.Enable() mu.Unlock() if err != nil { output.SetText("错误: " + err.Error()) } else { output.SetText(out) } }() }) content := container.NewVBox( statusLabel, widget.NewSeparator(), cmdEntry, runBtn, progress, widget.NewLabel("输出:"), output, ) myWindow.SetContent(content) myWindow.ShowAndRun() } ``` --- ## 7. 方法对比 | 方式 | 优点 | 缺点 | |------|------|------| | `ShellExecute("runas")` | 原生 API,稳定 | 只能启动 exe,无法获取输出 | | PowerShell `Start-Process -Verb RunAs -RedirectStandardOutput` | 可获取输出 | 需临时文件,略慢 | | 直接加 `go.mod` 嵌入 Manifest | 程序启动时即提权 | 始终以管理员运行,不够灵活 |