Files
SRCC/docs/admin-command-execution.md
T
2026-06-22 11:33:18 +08:00

361 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理员权限执行指令
> 适用于 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 | 程序启动时即提权 | 始终以管理员运行,不够灵活 |