b1.0
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
# 管理员权限执行指令
|
||||
|
||||
> 适用于 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 | 程序启动时即提权 | 始终以管理员运行,不够灵活 |
|
||||
Reference in New Issue
Block a user