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 }