Files
podman/cmd/winpath/main.go
Jason T. Greene 3ac5999f85 Switch to a new installer approach using a path manipulation helper
Fixes #11089 - cleanup PATH on MSI uninstall
Additionally fixes scenarios where the path can be overwritten by setx
Also removes the console flash, since the helper is built as a silent gui
Helper executable can be rerun by user to repair PATHs broken by other tools
Utilizes executable location instead of passed parameters to remove delicate escaping requirements

[NO NEW TESTS NEEDED]

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
2021-12-23 00:29:10 -06:00

185 lines
4.3 KiB
Go

//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)
}