playing with PATH on Windows
This commit is contained in:
parent
4e0724327e
commit
c88dcf22b6
|
@ -1,3 +1,5 @@
|
|||
*~
|
||||
|
||||
# ---> Go
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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=
|
|
@ -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)
|
||||
*/
|
||||
}
|
|
@ -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…
Reference in New Issue