Files
SRCC/docs/admin-command-execution.md
T

361 lines
9.1 KiB
Markdown
Raw Normal View History

2026-06-22 11:33:18 +08:00
# 管理员权限执行指令
> 适用于 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
### 方式 BPowerShell 方式(兼容性好,但依赖 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 | 程序启动时即提权 | 始终以管理员运行,不够灵活 |