playing with PATH on Windows
This commit is contained in:
		
							parent
							
								
									4e0724327e
								
							
						
					
					
						commit
						c88dcf22b6
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | ||||
| *~ | ||||
| 
 | ||||
| # ---> Go | ||||
| # Binaries for programs and plugins | ||||
| *.exe | ||||
|  | ||||
							
								
								
									
										63
									
								
								winpath/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								winpath/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| # winpath | ||||
| 
 | ||||
| An example of getting, setting, and broadcasting PATHs on Windows. | ||||
| 
 | ||||
| This requires the `unsafe` package to use a syscall with special message poitners to update `PATH` without a reboot. | ||||
| It will also build without `unsafe`. | ||||
| 
 | ||||
| ```bash | ||||
| go build -tags unsafe -o winpath.exe | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| winpath show | ||||
| 
 | ||||
|         %USERPROFILE%\AppData\Local\Microsoft\WindowsApps | ||||
|         C:\Users\me\AppData\Local\Programs\Microsoft VS Code\bin | ||||
|         %USERPROFILE%\go\bin | ||||
|         C:\Users\me\AppData\Roaming\npm | ||||
|         C:\Users\me\AppData\Local\Keybase\ | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| winpath append C:\someplace\special | ||||
| 
 | ||||
| 	Run the following for changes to take affect immediately: | ||||
| 	PATH %PATH%;C:\someplace\special | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| winpath prepend C:\someplace\special | ||||
| 
 | ||||
| 	Run the following for changes to take affect immediately: | ||||
| 	PATH C:\someplace\special;%PATH% | ||||
| ``` | ||||
| 
 | ||||
| ```bash | ||||
| winpath remove C:\someplace\special | ||||
| ``` | ||||
| 
 | ||||
| # Special Considerations | ||||
| 
 | ||||
| Giving away the secret sauce right here: | ||||
| 
 | ||||
| * `HWND_BROADCAST` | ||||
| * `WM_SETTINGCHANGE` | ||||
| 
 | ||||
| This is essentially the snippet you need to have the HKCU and HKLM Environment registry keys propagated without rebooting: | ||||
| 
 | ||||
| ```go | ||||
| 	HWND_BROADCAST   := uintptr(0xffff) | ||||
| 	WM_SETTINGCHANGE := uintptr(0x001A) | ||||
| 	_, _, err := syscall. | ||||
| 		NewLazyDLL("user32.dll"). | ||||
| 		NewProc("SendMessageW"). | ||||
| 		Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ENVIRONMENT")))) | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| * `os.Getenv("COMSPEC")` | ||||
| * `os.Getenv("SHELL")` | ||||
| 
 | ||||
| If you check `SHELL` and it isn't empty, then you're probably in MINGW or some such. | ||||
| If that's empty but `COMSPEC` isn't, you can be reasonably sure that you're in cmd.exe or Powershell. | ||||
							
								
								
									
										5
									
								
								winpath/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								winpath/go.mod
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| module git.coolaj86.com\coolaj86\go-examples\winpath | ||||
| 
 | ||||
| go 1.12 | ||||
| 
 | ||||
| require golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 | ||||
							
								
								
									
										2
									
								
								winpath/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								winpath/go.sum
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= | ||||
| golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
							
								
								
									
										197
									
								
								winpath/winpath.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								winpath/winpath.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,197 @@ | ||||
| // +build windows | ||||
| 
 | ||||
| // We both need to | ||||
| //   * use the registry editor directly to avoid possible PATH  truncation | ||||
| //     ( https://stackoverflow.com/questions/9546324/adding-directory-to-path-environment-variable-in-windows ) | ||||
| //     ( https://superuser.com/questions/387619/overcoming-the-1024-character-limit-with-setx ) | ||||
| //   * explicitly send WM_SETTINGCHANGE | ||||
| //     ( https://github.com/golang/go/issues/18680#issuecomment-275582179 ) | ||||
| //   * also install as a service | ||||
| //     ( https://github.com/golang/sys/blob/master/windows/svc/example/install.go ) | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/sys/windows/registry" | ||||
| ) | ||||
| 
 | ||||
| var sendmsg func() | ||||
| 
 | ||||
| func usage() { | ||||
| 	fmt.Fprintf(os.Stderr, "Usage: winpath show|append|prepend|remove <path>\n") | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	fmt.Println("PATH:", os.Getenv("PATH")) | ||||
| 	//fpath, err := exec.LookPath("reg") | ||||
| 	//fmt.Println("LookPath(\"reg\"):", fpath, err) | ||||
| 
 | ||||
| 	shell := os.Getenv("SHELL") | ||||
| 	if "" == shell { | ||||
| 		if strings.HasSuffix(os.Getenv("COMSPEC"), "/cmd.exe") { | ||||
| 			shell = "cmd" | ||||
| 		} | ||||
| 	} | ||||
| 	fmt.Println("SHELL?", shell) | ||||
| 	fmt.Println("os.PathListSeparator:", string(os.PathListSeparator)) | ||||
| 	// WM_SETTING_CHANGE | ||||
| 	// https://gist.github.com/microo8/c1b9525efab9bb462adf9d123e855c52 | ||||
| 	// os.Setenv("PATH") | ||||
| 
 | ||||
| 	// TODO --system to add to the system PATH rather than the user PATH | ||||
| 	if len(os.Args) < 2 { | ||||
| 		usage() | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	action := os.Args[1] | ||||
| 
 | ||||
| 	paths, err := Paths() | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 
 | ||||
| 	if "show" == action { | ||||
| 		if len(os.Args) > 2 { | ||||
| 			usage() | ||||
| 		} | ||||
| 		fmt.Println() | ||||
| 		for i := range paths { | ||||
| 			fmt.Println("\t" + paths[i]) | ||||
| 		} | ||||
| 		fmt.Println() | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if len(os.Args) < 3 { | ||||
| 		usage() | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	pathname := os.Args[2] | ||||
| 	abspath, err := filepath.Abs(pathname) | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 	if "remove" != action { | ||||
| 		stat, err := os.Stat(pathname) | ||||
| 		if nil != err { | ||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 			os.Exit(2) | ||||
| 		} | ||||
| 		if !stat.IsDir() { | ||||
| 			fmt.Fprintf(os.Stderr, "%q is not a directory (folder)\n", pathname) | ||||
| 			os.Exit(2) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	index := -1 | ||||
| 	for i := range paths { | ||||
| 		if pathname == paths[i] { | ||||
| 			index = i | ||||
| 			break | ||||
| 		} | ||||
| 		if abspath == paths[i] { | ||||
| 			index = i | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	switch action { | ||||
| 	default: | ||||
| 		usage() | ||||
| 		os.Exit(1) | ||||
| 	case "append": | ||||
| 		if index >= 0 { | ||||
| 			fmt.Fprintf(os.Stderr, "%q is already in PATH at position %d. Remove it first to re-order.\n", pathname, index) | ||||
| 			os.Exit(3) | ||||
| 		} | ||||
| 		paths = append(paths, pathname) | ||||
| 		fmt.Println("Run this to cause settings to take affect immediately:") | ||||
| 		fmt.Println("\tPATH %PATH%;" + pathname) | ||||
| 	case "prepend": | ||||
| 		if index >= 0 { | ||||
| 			fmt.Fprintf(os.Stderr, "%q is already in PATH at position %d. Remove it first to re-order.\n", pathname, index) | ||||
| 			os.Exit(3) | ||||
| 		} | ||||
| 		paths = append([]string{pathname}, paths...) | ||||
| 		fmt.Println("Run this to cause settings to take affect immediately:") | ||||
| 		fmt.Println("\tPATH " + pathname + ";%PATH%") | ||||
| 	case "remove": | ||||
| 		if index < 0 { | ||||
| 			fmt.Fprintf(os.Stderr, "%q is NOT in PATH.\n", pathname) | ||||
| 			os.Exit(3) | ||||
| 		} | ||||
| 		oldpaths := paths | ||||
| 		paths = []string{} | ||||
| 		for i := range oldpaths { | ||||
| 			if i != index { | ||||
| 				paths = append(paths, oldpaths[i]) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.SET_VALUE) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(4) | ||||
| 	} | ||||
| 	defer k.Close() | ||||
| 	// ";" on Windows | ||||
| 	err = k.SetStringValue(`Path`, strings.Join(paths, string(os.PathListSeparator))) | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(4) | ||||
| 	} | ||||
| 	err = k.Close() | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(4) | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	err = os.Setenv(`PATH`, strings.Join(paths, string(os.PathListSeparator))) | ||||
| 	if nil != err { | ||||
| 		fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		os.Exit(4) | ||||
| 	} | ||||
| 	if nil != sendmsg { | ||||
| 		fmt.Println("Open a new Terminal for the updated PATH") | ||||
| 		sendmsg() | ||||
| 	} else { | ||||
| 		fmt.Println("You'll need to reboot for setting to take affect") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Paths() ([]string, error) { | ||||
| 	k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.QUERY_VALUE) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer k.Close() | ||||
| 
 | ||||
| 	// This is case insensitive (PATH, Path, path) | ||||
| 	s, _, err := k.GetStringValue("Path") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	// ";" on Windows | ||||
| 	return strings.Split(s, string(os.PathListSeparator)), nil | ||||
| 
 | ||||
| 	/* | ||||
| 		shstr := loader() | ||||
| 		if "" == shstr { | ||||
| 			shstr = "NO_SHELL_DETECTED" | ||||
| 		} | ||||
| 		if "" == s { | ||||
| 			s = "NO_WINREG_PATH" | ||||
| 		} | ||||
| 
 | ||||
| 		return fmt.Sprintf("%s\n%s", s, shstr) | ||||
| 	*/ | ||||
| } | ||||
							
								
								
									
										29
									
								
								winpath/winpath_unsafe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								winpath/winpath_unsafe.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| // +build windows,unsafe | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| 	"fmt" | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	HWND_BROADCAST   = uintptr(0xffff) | ||||
| 	WM_SETTINGCHANGE = uintptr(0x001A) | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	sendmsg = func() { | ||||
| 		//x, y, err := syscall. | ||||
| 		_, _, err := syscall. | ||||
| 			NewLazyDLL("user32.dll"). | ||||
| 			NewProc("SendMessageW"). | ||||
| 			Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ENVIRONMENT")))) | ||||
| 		//fmt.Fprintf(os.Stderr, "%d, %d, %s\n", x, y, err) | ||||
| 		if nil != err { | ||||
| 			fmt.Fprintf(os.Stderr, "%s\n", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user