diff options
Diffstat (limited to 'cmd/winpath/main.go')
-rw-r--r-- | cmd/winpath/main.go | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/cmd/winpath/main.go b/cmd/winpath/main.go new file mode 100644 index 000000000..494d1cf3c --- /dev/null +++ b/cmd/winpath/main.go @@ -0,0 +1,184 @@ +//go:build windows +// +build windows + +package main + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + "syscall" + "unsafe" + + "golang.org/x/sys/windows/registry" +) + +type operation int + +const ( + HWND_BROADCAST = 0xFFFF + WM_SETTINGCHANGE = 0x001A + SMTO_ABORTIFHUNG = 0x0002 + ERR_BAD_ARGS = 0x000A + OPERATION_FAILED = 0x06AC + Environment = "Environment" + Add operation = iota + Remove + NotSpecified +) + +func main() { + op := NotSpecified + if len(os.Args) >= 2 { + switch os.Args[1] { + case "add": + op = Add + case "remove": + op = Remove + } + } + + // Stay silent since ran from an installer + if op == NotSpecified { + alert("Usage: " + filepath.Base(os.Args[0]) + " [add|remove]\n\nThis utility adds or removes the podman directory to the Windows Path.") + os.Exit(ERR_BAD_ARGS) + } + + if err := modify(op); err != nil { + os.Exit(OPERATION_FAILED) + } +} + +func modify(op operation) error { + exe, err := os.Executable() + if err != nil { + return err + } + exe, err = filepath.EvalSymlinks(exe) + if err != nil { + return err + } + target := filepath.Dir(exe) + + if op == Remove { + return removePathFromRegistry(target) + } + + return addPathToRegistry(target) +} + +// Appends a directory to the Windows Path stored in the registry +func addPathToRegistry(dir string) error { + k, _, err := registry.CreateKey(registry.CURRENT_USER, Environment, registry.WRITE|registry.READ) + if err != nil { + return err + } + + defer k.Close() + + existing, typ, err := k.GetStringValue("Path") + if err != nil { + return err + } + + // Is this directory already on the windows path? + for _, element := range strings.Split(existing, ";") { + if strings.EqualFold(element, dir) { + // Path already added + return nil + } + } + + // If the existing path is empty we don't want to start with a delimiter + if len(existing) > 0 { + existing += ";" + } + + existing += dir + + // It's important to preserve the registry key type so that it will be interpreted correctly + // EXPAND = evaluate variables in the expression, e.g. %PATH% should be expanded to the system path + // STRING = treat the contents as a string literal + if typ == registry.EXPAND_SZ { + err = k.SetExpandStringValue("Path", existing) + } else { + err = k.SetStringValue("Path", existing) + } + + if err == nil { + broadcastEnvironmentChange() + } + + return err +} + +// Removes all occurences of a directory path from the Windows path stored in the registry +func removePathFromRegistry(path string) error { + k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // Nothing to cleanup, the Environment registry key does not exist. + return nil + } + return err + } + + defer k.Close() + + existing, typ, err := k.GetStringValue("Path") + if err != nil { + return err + } + + var elements []string + for _, element := range strings.Split(existing, ";") { + if strings.EqualFold(element, path) { + continue + } + elements = append(elements, element) + } + + newPath := strings.Join(elements, ";") + // Preserve value type (see corresponding comment above) + if typ == registry.EXPAND_SZ { + err = k.SetExpandStringValue("Path", newPath) + } else { + err = k.SetStringValue("Path", newPath) + } + + if err == nil { + broadcastEnvironmentChange() + } + + return err +} + +// Sends a notification message to all top level windows informing them the environmental setings have changed. +// Applications such as the Windows command prompt and powershell will know to stop caching stale values on +// subsequent restarts. Since applications block the sender when receiving a message, we set a 3 second timeout +func broadcastEnvironmentChange() { + env, _ := syscall.UTF16PtrFromString(Environment) + user32 := syscall.NewLazyDLL("user32") + proc := user32.NewProc("SendMessageTimeoutW") + millis := 3000 + _, _, _ = proc.Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(env)), SMTO_ABORTIFHUNG, uintptr(millis), 0) +} + +// Creates an "error" style pop-up window +func alert(caption string) int { + // Error box style + format := 0x10 + + user32 := syscall.NewLazyDLL("user32.dll") + captionPtr, _ := syscall.UTF16PtrFromString(caption) + titlePtr, _ := syscall.UTF16PtrFromString("winpath") + ret, _, _ := user32.NewProc("MessageBoxW").Call( + uintptr(0), + uintptr(unsafe.Pointer(captionPtr)), + uintptr(unsafe.Pointer(titlePtr)), + uintptr(format)) + + return int(ret) +} |