361 lines
9.1 KiB
Markdown
361 lines
9.1 KiB
Markdown
# 管理员权限执行指令
|
||
|
||
> 适用于 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 | 程序启动时即提权 | 始终以管理员运行,不够灵活 |
|