'In Go, why does exec.Command() fail but os.StartProcess() succeed launching "winget.exe"?
exec.Command()
works for executingC:\Windows\System32\notepad.exe
- But
exec.Command()
doesn't work for executingC:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
. Fails with the error message:exec: "C:\\Users\\<username>\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe": file does not exist
- However,
os.StartProcess()
works for executingC:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe
Can someone tell me why?
This code fragment does not work. winget.exe
isn't launched.
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\\WindowsApps\\winget.exe")
cmd := exec.Command(wingetPath, "--version")
err := cmd.Start()
fmt.Println(err)
// exec: "C:\\Users\\<username>\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe": file does not exist
But this works:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\\WindowsApps\\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
_, err := os.StartProcess(wingetPath, []string{wingetPath, "--version"}, procAttr)
fmt.Println(err)
// <nil>
Go version:
> go version
go version go1.18 windows/amd64
Solution 1:[1]
Bug in Golang
So apparently this is a bug in the Windows implementation of Go
and has been filed on GitHub many times - the oldest I could find is this issue which was filed years ago.
The bug is caused by the fact that exec.Command()
internally uses os.Stat()
which does not read files with reparse points correctly. os.Lstat()
can.
Windows Store apps use App Execution Aliases, which are essentially zero-byte files with reparse points. This post has some additional details.
Workarounds
- Workaround is to use
os.StartProces()
- a lower level API which can be a bit painful to use especially when compared toos.Exec()
.
Important: Inos.StartProcess()
, the argv slice will becomeos.Args
in the new process, so you should normally pass the program name as the first argument:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
"Microsoft\\WindowsApps\\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
/*
To redirect IO, pass in stdin, stdout, stderr as required
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
*/
args = []string { "install", "git.git" }
// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
proc, err := os.StartProcess(wingetPath,
append([]string{wingetPath}, arg...), procAttr)
fmt.Println(err) // nil
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |