AJ ONeal
5 years ago
6 changed files with 298 additions and 0 deletions
@ -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