Search code examples
gowinapiffiuacelevated-privileges

Supress "terminal-window" when executing an elevated windows command


An GUI application I wrote executes elevated diskpart /s some_script. This works fine except for the fact that a terminal window is popping open showing the execution of diskpart. I wonder if there is a way to suppress the display of that terminal window.

How to hide command prompt window when using Exec in Golang? suggests three ways to deal with the problem:

  1. hide the command window
  2. prevent the command window
  3. hide the app's command window

The following code implements all three suggestions none of which works on my windows 10 pro machine (i.e. the terminal window pops up in each case).

Any other idea how to solve this or did I got something wrong?

Note: to test 1. and 2. go build -ldflags -H=windowsgui should be used to build following code and for 3. go build only. In the last case the build exe must not be executed from a command line.

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
    "syscall"

    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/layout"
    "fyne.io/fyne/v2/widget"
    "github.com/getlantern/elevate"
    "github.com/gonutz/w32/v2"
)

func init() { log.Default().SetFlags(0) }

// see https://stackoverflow.com/questions/42500570/how-to-hide-command-prompt-window-when-using-exec-in-golang
func main() {
    a := app.New()
    win := a.NewWindow("hide window")
    hideCmd := widget.NewButton("hide command window", hideCmdWindow)
    preventCreation := widget.NewButton("prevent command window", preventCmdWindow)
    hideApp := widget.NewButton("hide app command window", hideAppCmdWindow)
    grid := container.New(layout.NewGridLayout(1), hideCmd, preventCreation, hideApp)
    win.SetContent(grid)
    win.ShowAndRun()
}

func hideCmdWindow() {
    scriptFile := writeScript()
    defer os.RemoveAll(filepath.Dir(scriptFile))
    cmd_path := "C:\\Windows\\system32\\cmd.exe"
    cmd := elevate.Command(cmd_path, "/c", "diskpart", "/s", scriptFile)
    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
    bb, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("can't execute diskpart with script: %v", err)
    }
    fmt.Println(string(bb))
}

func preventCmdWindow() {
    scriptFile := writeScript()
    defer os.RemoveAll(filepath.Dir(scriptFile))
    cmd := elevate.Command("diskpart", "/s", scriptFile)
    cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
    bb, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("can't execute diskpart with script: %v", err)
    }
    fmt.Println(string(bb))
}

const (
    noWinGuiFlag        = "build without -ldflags=\"-H windowsgui\""
    executedFromConsole = "don't execute the program from a command line"
)

func hideAppCmdWindow() {
    console := w32.GetConsoleWindow()
    // probably CRASHES here if build with -H flag
    if console == 0 {
        fmt.Println("no console attached:", noWinGuiFlag)
        os.Exit(0)
    }
    // http://stackoverflow.com/questions/9009333/how-to-check-if-the-program-is-run-from-a-console
    _, consoleProcID := w32.GetWindowThreadProcessId(console)
    if w32.GetCurrentProcessId() == consoleProcID {
        w32.ShowWindowAsync(console, w32.SW_HIDE)
    }
    if w32.GetCurrentProcessId() != consoleProcID {
        fmt.Println("app-id != console-id:", executedFromConsole)
        os.Exit(0)
    }
    scriptFile := writeScript()
    defer os.RemoveAll(filepath.Dir(scriptFile))
    cmd := elevate.Command("diskpart", "/s", scriptFile)
    bb, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatalf("can't execute diskpart with script: %v", err)
    }
    fmt.Println(string(bb))
}

const (
    scriptName     = "list_disks"
    diskpartScript = "list\n"
)

// writeScript creates a simple diskpart script and stores it in a
// temporary directory.  The full filename of that script is returned.
func writeScript() string {
    wd, err := os.MkdirTemp("", "")
    if err != nil {
        log.Fatalf("can't create working directory: %v", err)
    }

    scriptFile := filepath.Join(wd, scriptName)
    err = os.WriteFile(scriptFile, []byte(diskpartScript), 0644)
    if err != nil {
        log.Fatalf("can't write diskpart-script: %v", err)
    }
    return scriptFile
}

Solution

  • As suggested by kostix adapting elevating by process token (windows.OpenProcessToken) and using the the show-flag of windows.ShellExecute solved the problem.