github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/cmd/winpath/main.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package main
     5  
     6  import (
     7  	"errors"
     8  	"io/fs"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	"golang.org/x/sys/windows/registry"
    16  )
    17  
    18  type operation int
    19  
    20  const (
    21  	HWND_BROADCAST             = 0xFFFF
    22  	WM_SETTINGCHANGE           = 0x001A
    23  	SMTO_ABORTIFHUNG           = 0x0002
    24  	ERR_BAD_ARGS               = 0x000A
    25  	OPERATION_FAILED           = 0x06AC
    26  	Environment                = "Environment"
    27  	Add              operation = iota
    28  	Remove
    29  	NotSpecified
    30  )
    31  
    32  func main() {
    33  	op := NotSpecified
    34  	if len(os.Args) >= 2 {
    35  		switch os.Args[1] {
    36  		case "add":
    37  			op = Add
    38  		case "remove":
    39  			op = Remove
    40  		}
    41  	}
    42  
    43  	// Stay silent since ran from an installer
    44  	if op == NotSpecified {
    45  		alert("Usage: " + filepath.Base(os.Args[0]) + " [add|remove]\n\nThis utility adds or removes the podman directory to the Windows Path.")
    46  		os.Exit(ERR_BAD_ARGS)
    47  	}
    48  
    49  	if err := modify(op); err != nil {
    50  		os.Exit(OPERATION_FAILED)
    51  	}
    52  }
    53  
    54  func modify(op operation) error {
    55  	exe, err := os.Executable()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	exe, err = filepath.EvalSymlinks(exe)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	target := filepath.Dir(exe)
    64  
    65  	if op == Remove {
    66  		return removePathFromRegistry(target)
    67  	}
    68  
    69  	return addPathToRegistry(target)
    70  }
    71  
    72  // Appends a directory to the Windows Path stored in the registry
    73  func addPathToRegistry(dir string) error {
    74  	k, _, err := registry.CreateKey(registry.CURRENT_USER, Environment, registry.WRITE|registry.READ)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	defer k.Close()
    80  
    81  	existing, typ, err := k.GetStringValue("Path")
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	// Is this directory already on the windows path?
    87  	for _, element := range strings.Split(existing, ";") {
    88  		if strings.EqualFold(element, dir) {
    89  			// Path already added
    90  			return nil
    91  		}
    92  	}
    93  
    94  	// If the existing path is empty we don't want to start with a delimiter
    95  	if len(existing) > 0 {
    96  		existing += ";"
    97  	}
    98  
    99  	existing += dir
   100  
   101  	// It's important to preserve the registry key type so that it will be interpreted correctly
   102  	// EXPAND = evaluate variables in the expression, e.g. %PATH% should be expanded to the system path
   103  	// STRING = treat the contents as a string literal
   104  	if typ == registry.EXPAND_SZ {
   105  		err = k.SetExpandStringValue("Path", existing)
   106  	} else {
   107  		err = k.SetStringValue("Path", existing)
   108  	}
   109  
   110  	if err == nil {
   111  		broadcastEnvironmentChange()
   112  	}
   113  
   114  	return err
   115  }
   116  
   117  // Removes all occurrences of a directory path from the Windows path stored in the registry
   118  func removePathFromRegistry(path string) error {
   119  	k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE)
   120  	if err != nil {
   121  		if errors.Is(err, fs.ErrNotExist) {
   122  			// Nothing to cleanup, the Environment registry key does not exist.
   123  			return nil
   124  		}
   125  		return err
   126  	}
   127  
   128  	defer k.Close()
   129  
   130  	existing, typ, err := k.GetStringValue("Path")
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	var elements []string
   136  	for _, element := range strings.Split(existing, ";") {
   137  		if strings.EqualFold(element, path) {
   138  			continue
   139  		}
   140  		elements = append(elements, element)
   141  	}
   142  
   143  	newPath := strings.Join(elements, ";")
   144  	// Preserve value type (see corresponding comment above)
   145  	if typ == registry.EXPAND_SZ {
   146  		err = k.SetExpandStringValue("Path", newPath)
   147  	} else {
   148  		err = k.SetStringValue("Path", newPath)
   149  	}
   150  
   151  	if err == nil {
   152  		broadcastEnvironmentChange()
   153  	}
   154  
   155  	return err
   156  }
   157  
   158  // Sends a notification message to all top level windows informing them the environmental settings have changed.
   159  // Applications such as the Windows command prompt and powershell will know to stop caching stale values on
   160  // subsequent restarts. Since applications block the sender when receiving a message, we set a 3 second timeout
   161  func broadcastEnvironmentChange() {
   162  	env, _ := syscall.UTF16PtrFromString(Environment)
   163  	user32 := syscall.NewLazyDLL("user32")
   164  	proc := user32.NewProc("SendMessageTimeoutW")
   165  	millis := 3000
   166  	_, _, _ = proc.Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(env)), SMTO_ABORTIFHUNG, uintptr(millis), 0)
   167  }
   168  
   169  // Creates an "error" style pop-up window
   170  func alert(caption string) int {
   171  	// Error box style
   172  	format := 0x10
   173  
   174  	user32 := syscall.NewLazyDLL("user32.dll")
   175  	captionPtr, _ := syscall.UTF16PtrFromString(caption)
   176  	titlePtr, _ := syscall.UTF16PtrFromString("winpath")
   177  	ret, _, _ := user32.NewProc("MessageBoxW").Call(
   178  		uintptr(0),
   179  		uintptr(unsafe.Pointer(captionPtr)),
   180  		uintptr(unsafe.Pointer(titlePtr)),
   181  		uintptr(format))
   182  
   183  	return int(ret)
   184  }