playing with PATH on Windows
This commit is contained in:
		
							부모
							
								
									4e0724327e
								
							
						
					
					
						커밋
						c88dcf22b6
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,5 @@ | |||||||
|  | *~ | ||||||
|  | 
 | ||||||
| # ---> Go | # ---> Go | ||||||
| # Binaries for programs and plugins | # Binaries for programs and plugins | ||||||
| *.exe | *.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) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		불러오는 중...
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user