@@ -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.
3134func 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 ("\n Proceed? [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 ("\n Re-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