14
"golang.org/x/sys/windows"
15
"golang.org/x/sys/windows/registry"
22
HWND_BROADCAST = 0xFFFF
24
WM_SETTINGCHANGE = 0x001A
26
SMTO_ABORTIFHUNG = 0x0002
30
OPERATION_FAILED = 0x06AC
32
Environment = "Environment"
41
if len(os.Args) >= 2 {
52
// Stay silent since ran from an installer
53
if op == NotSpecified {
54
alert("Usage: " + filepath.Base(os.Args[0]) + " [add|remove]\n\nThis utility adds or removes the podman directory to the Windows Path.")
58
// Hidden operation as a workaround for the installer
59
if op == Open && len(os.Args) > 2 {
60
if err := winOpenFile(os.Args[2]); err != nil {
61
os.Exit(OPERATION_FAILED)
66
if err := modify(op); err != nil {
67
os.Exit(OPERATION_FAILED)
71
func modify(op operation) error {
72
exe, err := os.Executable()
76
exe, err = filepath.EvalSymlinks(exe)
80
target := filepath.Dir(exe)
83
return removePathFromRegistry(target)
86
return addPathToRegistry(target)
89
// Appends a directory to the Windows Path stored in the registry
90
func addPathToRegistry(dir string) error {
91
k, _, err := registry.CreateKey(registry.CURRENT_USER, Environment, registry.WRITE|registry.READ)
98
existing, typ, err := k.GetStringValue("Path")
103
// Is this directory already on the windows path?
104
for _, element := range strings.Split(existing, ";") {
105
if strings.EqualFold(element, dir) {
106
// Path already added
111
// If the existing path is empty we don't want to start with a delimiter
112
if len(existing) > 0 {
118
// It's important to preserve the registry key type so that it will be interpreted correctly
119
// EXPAND = evaluate variables in the expression, e.g. %PATH% should be expanded to the system path
120
// STRING = treat the contents as a string literal
121
if typ == registry.EXPAND_SZ {
122
err = k.SetExpandStringValue("Path", existing)
124
err = k.SetStringValue("Path", existing)
128
broadcastEnvironmentChange()
134
// Removes all occurrences of a directory path from the Windows path stored in the registry
135
func removePathFromRegistry(path string) error {
136
k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE)
138
if errors.Is(err, fs.ErrNotExist) {
139
// Nothing to clean up, the Environment registry key does not exist.
147
existing, typ, err := k.GetStringValue("Path")
152
// No point preallocating we can't know how big the array needs to be.
154
var elements []string
155
for _, element := range strings.Split(existing, ";") {
156
if strings.EqualFold(element, path) {
159
elements = append(elements, element)
162
newPath := strings.Join(elements, ";")
163
// Preserve value type (see corresponding comment above)
164
if typ == registry.EXPAND_SZ {
165
err = k.SetExpandStringValue("Path", newPath)
167
err = k.SetStringValue("Path", newPath)
171
broadcastEnvironmentChange()
177
// Sends a notification message to all top level windows informing them the environmental settings have changed.
178
// Applications such as the Windows command prompt and powershell will know to stop caching stale values on
179
// subsequent restarts. Since applications block the sender when receiving a message, we set a 3 second timeout
180
func broadcastEnvironmentChange() {
181
env, _ := syscall.UTF16PtrFromString(Environment)
182
user32 := syscall.NewLazyDLL("user32")
183
proc := user32.NewProc("SendMessageTimeoutW")
186
_, _, _ = proc.Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(env)), SMTO_ABORTIFHUNG, uintptr(millis), 0)
189
// Creates an "error" style pop-up window
190
func alert(caption string) int {
194
user32 := syscall.NewLazyDLL("user32.dll")
195
captionPtr, _ := syscall.UTF16PtrFromString(caption)
196
titlePtr, _ := syscall.UTF16PtrFromString("winpath")
197
ret, _, _ := user32.NewProc("MessageBoxW").Call(
199
uintptr(unsafe.Pointer(captionPtr)),
200
uintptr(unsafe.Pointer(titlePtr)),
206
func winOpenFile(file string) error {
207
verb, _ := syscall.UTF16PtrFromString("open")
208
fileW, _ := syscall.UTF16PtrFromString(file)
209
return windows.ShellExecute(0, verb, fileW, nil, nil, windows.SW_NORMAL)