66 "fmt"
77 "os"
88 "strconv"
9+ "syscall"
910 "time"
11+ "unsafe"
1012
1113 "github.com/fosrl/windows/api"
1214 "github.com/fosrl/windows/auth"
@@ -24,6 +26,20 @@ import (
2426 "golang.org/x/sys/windows/svc/mgr"
2527)
2628
29+ var (
30+ user32 = syscall .NewLazyDLL ("user32.dll" )
31+ procMessageBoxW = user32 .NewProc ("MessageBoxW" )
32+ )
33+
34+ const mbOK = 0x00000000
35+
36+ // showMessageBox displays a message box (used when run without UI, e.g. RequestUILaunch failed).
37+ func showMessageBox (text , caption string ) {
38+ textPtr , _ := windows .UTF16PtrFromString (text )
39+ captionPtr , _ := windows .UTF16PtrFromString (caption )
40+ procMessageBoxW .Call (0 , uintptr (unsafe .Pointer (textPtr )), uintptr (unsafe .Pointer (captionPtr )), mbOK )
41+ }
42+
2743func execElevatedManagerServiceInstaller () error {
2844 path , err := os .Executable ()
2945 if err != nil {
@@ -79,15 +95,23 @@ func main() {
7995 if err != nil {
8096 if err == managers .ErrManagerAlreadyRunning {
8197 logger .Info ("Manager service is already running" )
82- // Wait a bit for UI to appear
83- time .Sleep (5 * time .Second )
98+ // Request UI launch so user gets the UI from this run (e.g. first launch raced with another)
99+ time .Sleep (2 * time .Second )
100+ managers .RequestUILaunch ()
84101 return
85102 }
86103 logger .Fatal ("Failed to install manager service: %v" , err )
87104 }
88105 logger .Info ("Manager service installed successfully" )
89- // Wait a bit for service to start and UI to appear
90- time .Sleep (5 * time .Second )
106+ // Wait for service to start and listen on the UI launch pipe, then request UI so first launch shows UI without a second run
107+ time .Sleep (2 * time .Second )
108+ if managers .RequestUILaunch () {
109+ logger .Info ("UI launch requested successfully" )
110+ } else {
111+ // Retry once in case the pipe wasn't ready yet
112+ time .Sleep (2 * time .Second )
113+ managers .RequestUILaunch ()
114+ }
91115 return
92116 }
93117
@@ -113,21 +137,19 @@ func main() {
113137 logger .Info ("Connected to manager service via IPC" )
114138 // Fall through to run UI
115139 } else {
116- // No arguments - check if manager service is running, install/start if needed
117- // This is the normal entry point when user double-clicks the .exe
118- serviceName := config .AppName + "Manager"
140+ // No arguments - normal entry when user double-clicks the .exe.
141+ // Try the named pipe first so standard users never need SCM or UAC when the manager is running.
142+ if managers .RequestUILaunch () {
143+ return
144+ }
119145
120- // Try to connect to service manager
121- // Note: Standard users should be able to connect to query services,
122- // but may need elevation to install/start services
146+ // Pipe connect failed (manager not running or not installed). Use SCM to install/start; may require UAC.
147+ serviceName := config .AppName + "Manager"
123148 m , err := mgr .Connect ()
124149 if err != nil {
125- // If we can't connect to service manager, we can't check service status
126- // This is unusual for standard users - they should be able to connect to query services
127150 if err == windows .ERROR_ACCESS_DENIED {
128151 logger .Info ("Cannot access service manager without admin privileges" )
129152 logger .Info ("Attempting to install/start manager service (will show UAC prompt)..." )
130- // Try to elevate to install/start the service
131153 err = execElevatedManagerServiceInstaller ()
132154 if err != nil {
133155 logger .Fatal ("Failed to install/start manager service: %v\n Please run as administrator to install the service." , err )
@@ -140,7 +162,6 @@ func main() {
140162
141163 service , err := m .OpenService (serviceName )
142164 if err != nil {
143- // Service doesn't exist, need to install it (requires elevation)
144165 logger .Info ("Manager service not found, installing..." )
145166 err = execElevatedManagerServiceInstaller ()
146167 if err != nil {
@@ -155,23 +176,22 @@ func main() {
155176 logger .Fatal ("Failed to query service status: %v" , err )
156177 }
157178
158- // If service is already running, exit - the manager service will automatically
159- // launch the UI for logged-in users. No UAC prompt needed.
160179 if status .State == svc .Running || status .State == svc .StartPending {
161- logger .Info ("Manager service is already running" )
180+ // Service is running but pipe failed earlier; try UI launch once more
181+ if managers .RequestUILaunch () {
182+ return
183+ }
184+ logger .Error ("Could not start Pangolin. Please try again or contact your administrator." )
185+ showMessageBox ("Could not start Pangolin. Please try again or contact your administrator." , "Pangolin" )
162186 return
163187 }
164188
165189 if status .State == svc .Stopped {
166- // Service exists but is stopped, try to start it
167190 logger .Info ("Manager service is stopped, starting..." )
168191 err = service .Start ()
169192 if err != nil {
170- // If we don't have permission to start the service, try to elevate
171193 if err == windows .ERROR_ACCESS_DENIED {
172194 logger .Info ("Need admin privileges to start service, requesting elevation..." )
173- // Use cmd.exe to run net start, which can be elevated to start the service
174- // This will show a UAC prompt if needed
175195 err = elevate .ShellExecute ("cmd.exe" , fmt .Sprintf ("/c net start \" %s\" " , serviceName ), "" , windows .SW_HIDE )
176196 if err != nil && err != windows .ERROR_CANCELLED {
177197 logger .Fatal ("Failed to start manager service (access denied): %v\n Please start the service manually or run as administrator." , err )
@@ -180,9 +200,7 @@ func main() {
180200 logger .Info ("User cancelled elevation, cannot start service" )
181201 return
182202 }
183- // Wait a moment for service to start
184203 time .Sleep (2 * time .Second )
185- // Verify it started
186204 status , err = service .Query ()
187205 if err != nil {
188206 logger .Fatal ("Failed to query service status after start: %v" , err )
@@ -195,14 +213,15 @@ func main() {
195213 logger .Fatal ("Failed to start manager service: %v" , err )
196214 }
197215 } else {
198- // Wait a moment for service to start and launch UI
199216 logger .Info ("Manager service started, UI should appear shortly" )
200217 time .Sleep (2 * time .Second )
201218 }
202219 }
203220
204- // Exit - the manager service will handle launching the UI
205- // The manager service automatically launches UI processes for logged-in users
221+ // After install/start, try UI launch again so user gets the UI without relaunching
222+ if managers .RequestUILaunch () {
223+ return
224+ }
206225 return
207226 }
208227
0 commit comments