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
|
# ---> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user