7
  • exec.Command() works for executing C:\Windows\System32\notepad.exe
  • But exec.Command() doesn't work for executing C:\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 executing C:\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
TylerH
  • 20,799
  • 66
  • 75
  • 101
Sachin Joseph
  • 18,928
  • 4
  • 42
  • 62
  • Shot in the dark hypothesis - but does the redacted `` contain a space within it? – selbie Apr 14 '22 at 01:29
  • @selbie no it doesn't – Sachin Joseph Apr 14 '22 at 01:31
  • @SachinJoseph not sure but curious, your error is `file does not exist` so you can debug by using `filepath.Abs` or `filepath.WalkDir` to get the paths in your expected directory, see if anything is different there and use that path instead, if that helps. – code0x00 Apr 14 '22 at 08:26
  • @code0x00 this is caused by a bug in the Windows implementation of Go. See my [answer](https://stackoverflow.com/a/71874620/1724702). – Sachin Joseph Apr 14 '22 at 16:11

1 Answers1

6

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 to os.Exec().
    Important: In os.StartProcess(), the argv slice will become os.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
  • Another approach to work around this bug is to (create and) execute a .cmd file (for example) which would (correctly resolve and) execute the file with reparse points. See this (and also this directory) for an example.
Sachin Joseph
  • 18,928
  • 4
  • 42
  • 62