Skip to content

Commit 702dfb6

Browse files
authored
Merge pull request #58 from dtvem/fix/windows-system-path
fix(init): use System PATH on Windows for correct priority
2 parents 58fcdf1 + 208f13a commit 702dfb6

2 files changed

Lines changed: 142 additions & 36 deletions

File tree

src/cmd/init.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,13 @@ Example:
3535

3636
spinner.Success("Directories created")
3737

38-
// Setup PATH
38+
// Setup PATH - AddToPath handles checking position and moving if needed
3939
shimsDir := path.ShimsDir()
4040

41-
if path.IsInPath(shimsDir) {
42-
ui.Success("PATH is already configured correctly")
43-
ui.Info("Shims directory: %s", ui.Highlight(shimsDir))
44-
} else {
45-
ui.Info("Setting up PATH...")
46-
if err := path.AddToPath(shimsDir); err != nil {
47-
ui.Error("Failed to configure PATH: %v", err)
48-
ui.Info("You can manually add %s to your PATH", shimsDir)
49-
return
50-
}
41+
if err := path.AddToPath(shimsDir); err != nil {
42+
ui.Error("Failed to configure PATH: %v", err)
43+
ui.Info("You can manually add %s to your PATH", shimsDir)
44+
return
5145
}
5246

5347
ui.Success("dtvem initialized successfully!")

src/internal/path/path_windows.go

Lines changed: 137 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/dtvem/dtvem/src/internal/constants"
1515
"github.com/dtvem/dtvem/src/internal/ui"
16+
"golang.org/x/sys/windows"
1617
"golang.org/x/sys/windows/registry"
1718
)
1819

@@ -27,69 +28,180 @@ const (
2728
SMTO_ABORTIFHUNG = 0x0002
2829
)
2930

30-
// AddToPath adds the shims directory to the user's PATH on Windows
31+
// AddToPath adds the shims directory to the System PATH on Windows.
32+
// This requires administrator privileges. If not elevated, it will prompt
33+
// the user to re-run with elevation.
3134
func AddToPath(shimsDir string) error {
32-
// Check if already in PATH
33-
if IsInPath(shimsDir) {
34-
ui.Info("%s is already in your PATH", shimsDir)
35+
// Check current System PATH status
36+
needsUpdate, action, err := checkSystemPath(shimsDir)
37+
if err != nil {
38+
return err
39+
}
40+
41+
if !needsUpdate {
42+
ui.Success("%s is already at the beginning of your System PATH", shimsDir)
3543
return nil
3644
}
3745

38-
// Prompt user for confirmation
39-
ui.Header("PATH Setup Required")
40-
ui.Info("dtvem needs to add the shims directory to your PATH")
41-
ui.Info("Directory: %s", ui.Highlight(shimsDir))
42-
ui.Info("This will modify your user PATH environment variable")
43-
fmt.Printf("\nProceed? [Y/n]: ")
46+
// Check if we have admin privileges
47+
if !isAdmin() {
48+
return promptForElevation(shimsDir, action)
49+
}
50+
51+
// We have admin privileges - proceed with modification
52+
return modifySystemPath(shimsDir, action)
53+
}
54+
55+
// checkSystemPath checks if the shims directory needs to be added/moved in System PATH
56+
// Returns: needsUpdate, action ("add" or "move"), error
57+
func checkSystemPath(shimsDir string) (bool, string, error) {
58+
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Environment`, registry.QUERY_VALUE)
59+
if err != nil {
60+
return false, "", fmt.Errorf("failed to open System PATH registry key: %w", err)
61+
}
62+
defer func() { _ = key.Close() }()
63+
64+
currentPath, _, err := key.GetStringValue("Path")
65+
if err != nil && !errors.Is(err, registry.ErrNotExist) {
66+
return false, "", fmt.Errorf("failed to read System PATH: %w", err)
67+
}
68+
69+
paths := strings.Split(currentPath, ";")
70+
foundAt := -1
71+
72+
for i, p := range paths {
73+
trimmed := strings.TrimSpace(p)
74+
if strings.EqualFold(trimmed, shimsDir) {
75+
foundAt = i
76+
break
77+
}
78+
}
79+
80+
if foundAt == 0 {
81+
return false, "", nil // Already at beginning
82+
} else if foundAt > 0 {
83+
return true, "move", nil // Exists but not at beginning
84+
}
85+
return true, "add", nil // Not in PATH
86+
}
87+
88+
// isAdmin checks if the current process has administrator privileges
89+
func isAdmin() bool {
90+
_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
91+
if err != nil {
92+
return false
93+
}
94+
return true
95+
}
96+
97+
// promptForElevation prompts the user to re-run dtvem init with admin privileges
98+
func promptForElevation(shimsDir, action string) error {
99+
if action == "move" {
100+
ui.Header("PATH Fix Required (Administrator)")
101+
ui.Warning("%s is in your System PATH but not at the beginning", shimsDir)
102+
ui.Info("It needs to be first to take priority over other installations")
103+
} else {
104+
ui.Header("PATH Setup Required (Administrator)")
105+
ui.Info("dtvem needs to add the shims directory to your System PATH")
106+
ui.Info("Directory: %s", ui.Highlight(shimsDir))
107+
}
108+
109+
ui.Info("")
110+
ui.Info("On Windows, System PATH takes priority over User PATH.")
111+
ui.Info("Modifying System PATH requires administrator privileges.")
112+
113+
fmt.Printf("\nRe-run with administrator privileges? [Y/n]: ")
44114

45115
var response string
46116
_, _ = fmt.Scanln(&response)
47117
response = strings.ToLower(strings.TrimSpace(response))
48118

49119
if response != "" && response != constants.ResponseY && response != constants.ResponseYes {
50-
ui.Warning("PATH not modified. You can add it manually later by running: dtvem init")
120+
ui.Warning("PATH not modified. You can run 'dtvem init' again later.")
51121
return nil
52122
}
53123

54-
// Get current user PATH from registry
55-
key, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.QUERY_VALUE|registry.SET_VALUE)
124+
// Re-launch with elevation
125+
return relaunchElevated()
126+
}
127+
128+
// relaunchElevated re-launches the current executable with administrator privileges
129+
func relaunchElevated() error {
130+
exe, err := os.Executable()
131+
if err != nil {
132+
return fmt.Errorf("failed to get executable path: %w", err)
133+
}
134+
135+
cwd, err := os.Getwd()
136+
if err != nil {
137+
return fmt.Errorf("failed to get working directory: %w", err)
138+
}
139+
140+
// Use ShellExecute with "runas" verb to request elevation
141+
verb := windows.StringToUTF16Ptr("runas")
142+
exePath := windows.StringToUTF16Ptr(exe)
143+
args := windows.StringToUTF16Ptr("init")
144+
dir := windows.StringToUTF16Ptr(cwd)
145+
146+
err = windows.ShellExecute(0, verb, exePath, args, dir, windows.SW_SHOWNORMAL)
147+
if err != nil {
148+
return fmt.Errorf("failed to elevate: %w", err)
149+
}
150+
151+
ui.Info("Elevated process launched. Please complete the setup in the new window.")
152+
return nil
153+
}
154+
155+
// modifySystemPath modifies the System PATH (requires admin privileges)
156+
func modifySystemPath(shimsDir, action string) error {
157+
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Environment`, registry.QUERY_VALUE|registry.SET_VALUE)
56158
if err != nil {
57-
return fmt.Errorf("failed to open registry key: %w", err)
159+
return fmt.Errorf("failed to open System PATH registry key for writing: %w", err)
58160
}
59161
defer func() { _ = key.Close() }()
60162

61163
currentPath, _, err := key.GetStringValue("Path")
62164
if err != nil && !errors.Is(err, registry.ErrNotExist) {
63-
return fmt.Errorf("failed to read current PATH: %w", err)
165+
return fmt.Errorf("failed to read System PATH: %w", err)
64166
}
65167

66-
// Check if already present (double-check)
168+
// Parse and filter current PATH entries
67169
paths := strings.Split(currentPath, ";")
170+
var filteredPaths []string
171+
68172
for _, p := range paths {
69-
if strings.EqualFold(strings.TrimSpace(p), shimsDir) {
70-
ui.Info("%s is already in your registry PATH", shimsDir)
71-
return nil
173+
trimmed := strings.TrimSpace(p)
174+
if trimmed == "" {
175+
continue
176+
}
177+
// Skip if it's the shims dir (we'll prepend it)
178+
if strings.EqualFold(trimmed, shimsDir) {
179+
continue
72180
}
181+
filteredPaths = append(filteredPaths, trimmed)
73182
}
74183

75-
// Prepend the shims directory to the BEGINNING for priority
184+
// Build new PATH with shimsDir at the beginning
76185
newPath := shimsDir
77-
if currentPath != "" {
78-
newPath += ";" + currentPath
186+
if len(filteredPaths) > 0 {
187+
newPath += ";" + strings.Join(filteredPaths, ";")
79188
}
80189

81190
// Write back to registry
82191
err = key.SetStringValue("Path", newPath)
83192
if err != nil {
84-
return fmt.Errorf("failed to update PATH in registry: %w", err)
193+
return fmt.Errorf("failed to update System PATH in registry: %w", err)
85194
}
86195

87196
// Broadcast WM_SETTINGCHANGE to notify running processes
88197
broadcastSettingChange()
89198

90-
ui.Success("Added %s to your PATH", shimsDir)
199+
if action == "move" {
200+
ui.Success("Moved %s to the beginning of your System PATH", shimsDir)
201+
} else {
202+
ui.Success("Added %s to your System PATH", shimsDir)
203+
}
91204
ui.Warning("Please restart your terminal for the changes to take effect")
92-
ui.Info("You can verify by running: echo %%PATH%%")
93205

94206
return nil
95207
}

0 commit comments

Comments
 (0)