computer-use-ootb-internal 0.0.109.post2__py3-none-any.whl → 0.0.111__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,40 @@
1
- """
2
- Test script to verify cursor animation is working
3
- """
4
- import asyncio
5
- import sys
6
- import time
7
- from pathlib import Path
8
- from computer_use_ootb_internal.computer_use_demo.tools.computer import ComputerTool
9
-
10
- async def test_animations():
11
-
12
- # Initialize the computer tool
13
- computer = ComputerTool()
14
-
15
- # Test mouse move animation
16
- print("Testing mouse move animation...")
17
- await computer(action="mouse_move_windll", coordinate=(500, 500))
18
- print("Waiting 2 seconds...")
19
- await asyncio.sleep(2)
20
-
21
- # Test click animation
22
- print("Testing click animation...")
23
- await computer(action="left_click_windll", coordinate=(700, 300))
24
- print("Waiting 2 seconds...")
25
- await asyncio.sleep(2)
26
-
27
- # Test another move
28
- print("Testing move and click sequence...")
29
- await computer(action="mouse_move_windll", coordinate=(300, 300))
30
- await asyncio.sleep(1)
31
- await computer(action="left_click_windll", coordinate=(300, 300))
32
-
33
- # Wait for animations to complete
34
- print("Waiting for animations to complete...")
35
- await asyncio.sleep(3)
36
-
37
- print("Test completed")
38
-
39
- if __name__ == "__main__":
1
+ """
2
+ Test script to verify cursor animation is working
3
+ """
4
+ import asyncio
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from computer_use_ootb_internal.computer_use_demo.tools.computer import ComputerTool
9
+
10
+ async def test_animations():
11
+
12
+ # Initialize the computer tool
13
+ computer = ComputerTool()
14
+
15
+ # Test mouse move animation
16
+ print("Testing mouse move animation...")
17
+ await computer(action="mouse_move_windll", coordinate=(500, 500))
18
+ print("Waiting 2 seconds...")
19
+ await asyncio.sleep(2)
20
+
21
+ # Test click animation
22
+ print("Testing click animation...")
23
+ await computer(action="left_click_windll", coordinate=(700, 300))
24
+ print("Waiting 2 seconds...")
25
+ await asyncio.sleep(2)
26
+
27
+ # Test another move
28
+ print("Testing move and click sequence...")
29
+ await computer(action="mouse_move_windll", coordinate=(300, 300))
30
+ await asyncio.sleep(1)
31
+ await computer(action="left_click_windll", coordinate=(300, 300))
32
+
33
+ # Wait for animations to complete
34
+ print("Waiting for animations to complete...")
35
+ await asyncio.sleep(3)
36
+
37
+ print("Test completed")
38
+
39
+ if __name__ == "__main__":
40
40
  asyncio.run(test_animations())
@@ -0,0 +1,436 @@
1
+ # src/computer_use_ootb_internal/guard_service.py
2
+ import sys
3
+ import os
4
+ import time
5
+ import logging
6
+ import subprocess
7
+ import pathlib
8
+ import ctypes
9
+ import requests # For server polling
10
+ import servicemanager # From pywin32
11
+ import win32serviceutil # From pywin32
12
+ import win32service # From pywin32
13
+ import win32event # From pywin32
14
+ import win32api # From pywin32
15
+ import win32process # From pywin32
16
+ import win32security # From pywin32
17
+ import win32profile # From pywin32
18
+ import win32ts # From pywin32 (Terminal Services API)
19
+ import win32con # Added import
20
+ import psutil # For process/user info
21
+
22
+ # --- Configuration ---
23
+ # Internal service name
24
+ _SERVICE_NAME = "OOTBGuardService"
25
+ # Display name in Windows Services MMC
26
+ _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
27
+ # Description in Windows Services MMC
28
+ _SERVICE_DESCRIPTION = "Background service for OOTB monitoring and remote management."
29
+ # Package name for updates
30
+ _PACKAGE_NAME = "computer-use-ootb-internal"
31
+ # Main module to start/stop for users
32
+ _OOTB_MODULE = "computer_use_ootb_internal.app_teachmode"
33
+ # Server endpoint to poll for commands (replace with your actual URL)
34
+ _SERVER_COMMAND_URL = "https://52.160.105.102/api/guard/commands" # EXAMPLE URL - Verify protocol/port/path
35
+ # How often to poll the server (in seconds)
36
+ _POLLING_INTERVAL = 60
37
+ # Placeholder for a machine identifier or API key for the server
38
+ _MACHINE_ID = "YOUR_MACHINE_ID_OR_API_KEY" # EXAMPLE - Implement actual ID/auth
39
+ # Log file location (consider using Windows Event Log instead for production)
40
+ _LOG_FILE = pathlib.Path(os.environ['PROGRAMDATA']) / "OOTBGuardService" / "guard.log"
41
+ # --- End Configuration ---
42
+
43
+ # Ensure log directory exists
44
+ _LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
45
+
46
+ logging.basicConfig(
47
+ filename=_LOG_FILE,
48
+ level=logging.INFO,
49
+ format='%(asctime)s %(levelname)s %(message)s'
50
+ )
51
+
52
+ def get_python_executable():
53
+ """Gets the quoted path to the current python executable."""
54
+ python_exe = sys.executable
55
+ if " " in python_exe and not python_exe.startswith('"'):
56
+ python_exe = f'"{python_exe}"'
57
+ return python_exe
58
+
59
+ def get_pip_executable():
60
+ """Tries to locate the pip executable in the same environment."""
61
+ python_path = pathlib.Path(sys.executable)
62
+ pip_path = python_path.parent / "Scripts" / "pip.exe"
63
+ if pip_path.exists():
64
+ pip_exe = str(pip_path)
65
+ if " " in pip_exe and not pip_exe.startswith('"'):
66
+ pip_exe = f'"{pip_exe}"'
67
+ return pip_exe
68
+ else:
69
+ logging.warning("pip.exe not found in Scripts directory. Falling back to 'python -m pip'.")
70
+ return f"{get_python_executable()} -m pip"
71
+
72
+ def log_info(msg):
73
+ logging.info(msg)
74
+ try:
75
+ servicemanager.LogInfoMsg(str(msg)) # Also log to Windows Event Log Application channel
76
+ except Exception as e:
77
+ logging.warning(f"Could not write to Windows Event Log: {e}") # Avoid crashing service if event log fails
78
+
79
+ def log_error(msg, exc_info=False):
80
+ logging.error(msg, exc_info=exc_info)
81
+ try:
82
+ servicemanager.LogErrorMsg(str(msg))
83
+ except Exception as e:
84
+ logging.warning(f"Could not write error to Windows Event Log: {e}")
85
+
86
+ class GuardService(win32serviceutil.ServiceFramework):
87
+ _svc_name_ = _SERVICE_NAME
88
+ _svc_display_name_ = _SERVICE_DISPLAY_NAME
89
+ _svc_description_ = _SERVICE_DESCRIPTION
90
+
91
+ def __init__(self, args):
92
+ win32serviceutil.ServiceFramework.__init__(self, args)
93
+ self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
94
+ self.is_running = True
95
+ self.python_exe = get_python_executable()
96
+ self.pip_command_base = get_pip_executable()
97
+ self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
98
+ self.session = requests.Session() # Reuse session for polling
99
+
100
+ def SvcStop(self):
101
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
102
+ win32event.SetEvent(self.hWaitStop)
103
+ self.is_running = False
104
+ log_info(f"{_SERVICE_NAME} is stopping.")
105
+
106
+ def SvcDoRun(self):
107
+ servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
108
+ servicemanager.PYS_SERVICE_STARTED,
109
+ (self._svc_name_, ''))
110
+ self.main_loop()
111
+ # If main_loop exits cleanly (shouldn't happen with while self.is_running)
112
+ log_info(f"{_SERVICE_NAME} main loop exited.")
113
+
114
+
115
+ def main_loop(self):
116
+ log_info(f"{_SERVICE_NAME} started. Polling {_SERVER_COMMAND_URL} every {_POLLING_INTERVAL}s.")
117
+ while self.is_running:
118
+ try:
119
+ self.poll_server_for_commands()
120
+ except Exception as e:
121
+ log_error(f"Error in main loop polling cycle: {e}", exc_info=True)
122
+
123
+ # Wait for stop event or timeout
124
+ rc = win32event.WaitForSingleObject(self.hWaitStop, _POLLING_INTERVAL * 1000)
125
+ if rc == win32event.WAIT_OBJECT_0:
126
+ # Stop event signaled
127
+ break
128
+ log_info(f"{_SERVICE_NAME} is shutting down main loop.")
129
+
130
+
131
+ def poll_server_for_commands(self):
132
+ log_info(f"Polling server for commands...")
133
+ try:
134
+ headers = {'Authorization': f'Bearer {_MACHINE_ID}'} # Example auth
135
+ # Add machine identifier if needed by server
136
+ params = {'machine_id': os.getenv('COMPUTERNAME', 'unknown')}
137
+ response = self.session.get(_SERVER_COMMAND_URL, headers=headers, params=params, timeout=30)
138
+ response.raise_for_status() # Raise exception for bad status codes
139
+
140
+ commands = response.json() # Expecting a list of command objects
141
+ if not commands:
142
+ # log_info("No commands received.") # Reduce log noise
143
+ return
144
+
145
+ log_info(f"Received {len(commands)} command(s). Processing...")
146
+ for command in commands:
147
+ action = command.get("action")
148
+ target = command.get("target_user", "all_active") # Default to all
149
+ command_id = command.get("command_id", "N/A") # Optional: for reporting status
150
+
151
+ log_info(f"Processing Command ID {command_id}: action='{action}', target='{target}'")
152
+ status = "failed" # Default status
153
+ try:
154
+ if action == "update":
155
+ status = self.handle_update()
156
+ elif action == "stop_ootb":
157
+ status = self.handle_stop(target)
158
+ elif action == "start_ootb":
159
+ status = self.handle_start(target)
160
+ else:
161
+ log_error(f"Unknown action received: {action}")
162
+ status = "unknown_action"
163
+ except Exception as handler_ex:
164
+ log_error(f"Error executing action '{action}' for command {command_id}: {handler_ex}", exc_info=True)
165
+ status = "execution_error"
166
+
167
+ # TODO: Add mechanism to report command completion/failure back to server
168
+ # Example: self.report_command_status(command_id, status)
169
+ log_info(f"Finished processing Command ID {command_id}: Status='{status}'")
170
+
171
+ except requests.exceptions.RequestException as e:
172
+ log_error(f"Failed to poll server: {e}")
173
+ except Exception as e:
174
+ log_error(f"Error processing server commands: {e}", exc_info=True)
175
+
176
+
177
+ def handle_update(self):
178
+ log_info("Executing OOTB update...")
179
+ if not self.pip_command_base:
180
+ log_error("Cannot update: pip command not found.")
181
+ return "failed_pip_not_found"
182
+
183
+ update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
184
+ log_info(f"Running update command: {update_command}")
185
+ try:
186
+ # Run update command
187
+ result = subprocess.run(update_command, shell=True, capture_output=True, text=True, check=True, timeout=300)
188
+ log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
189
+ return "success"
190
+ except subprocess.CalledProcessError as e:
191
+ log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
192
+ return f"failed_exit_{e.returncode}"
193
+ except subprocess.TimeoutExpired:
194
+ log_error(f"Update command timed out.")
195
+ return "failed_timeout"
196
+ except Exception as e:
197
+ log_error(f"Unexpected error during update: {e}", exc_info=True)
198
+ return "failed_exception"
199
+
200
+
201
+ def _get_ootb_processes(self, target_user="all_active"):
202
+ """Finds OOTB processes, optionally filtering by username."""
203
+ ootb_procs = []
204
+ target_pid_list = []
205
+ try:
206
+ # Get a list of usernames we are interested in
207
+ target_users = set()
208
+ if target_user == "all_active":
209
+ for user_session in psutil.users():
210
+ # Normalize username (remove domain if present)
211
+ username = user_session.name.split('\\')[-1]
212
+ target_users.add(username.lower())
213
+ else:
214
+ target_users.add(target_user.lower())
215
+
216
+ log_info(f"Searching for OOTB processes for users: {target_users}")
217
+
218
+ for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline']):
219
+ try:
220
+ pinfo = proc.info
221
+ # Normalize process username
222
+ proc_username = pinfo['username']
223
+ if proc_username:
224
+ proc_username = proc_username.split('\\')[-1].lower()
225
+
226
+ # Check if process user is one of the targets
227
+ if proc_username in target_users:
228
+ # Check if command line matches our OOTB app pattern
229
+ cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
230
+ # Simple check: does it contain python executable and the module name?
231
+ if (self.python_exe.strip('"') in cmdline) and (_OOTB_MODULE in cmdline):
232
+ log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
233
+ ootb_procs.append(proc)
234
+ target_pid_list.append(pinfo['pid'])
235
+
236
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
237
+ continue # Process might have died or we lack permissions
238
+ log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
239
+ except Exception as e:
240
+ log_error(f"Error enumerating processes: {e}", exc_info=True)
241
+ return ootb_procs
242
+
243
+
244
+ def handle_stop(self, target_user="all_active"):
245
+ log_info(f"Executing stop OOTB for target '{target_user}'...")
246
+ stopped_count = 0
247
+ procs_to_stop = self._get_ootb_processes(target_user)
248
+
249
+ if not procs_to_stop:
250
+ log_info("No running OOTB processes found for target.")
251
+ return "no_process_found"
252
+
253
+ for proc in procs_to_stop:
254
+ try:
255
+ username = proc.info.get('username', 'unknown_user')
256
+ log_info(f"Terminating process PID={proc.pid}, User={username}")
257
+ proc.terminate() # Ask nicely first
258
+ try:
259
+ proc.wait(timeout=3) # Wait a bit
260
+ log_info(f"Process PID={proc.pid} terminated successfully.")
261
+ stopped_count += 1
262
+ except psutil.TimeoutExpired:
263
+ log_warning(f"Process PID={proc.pid} did not terminate gracefully, killing.")
264
+ proc.kill()
265
+ stopped_count += 1
266
+ except psutil.NoSuchProcess:
267
+ log_info(f"Process PID={proc.pid} already terminated.")
268
+ stopped_count +=1 # Count it if it disappeared
269
+ except psutil.AccessDenied:
270
+ log_error(f"Access denied trying to terminate process PID={proc.pid}. Service might lack privileges?")
271
+ except Exception as e:
272
+ log_error(f"Error stopping process PID={proc.pid}: {e}", exc_info=True)
273
+
274
+ log_info(f"Finished stopping OOTB. Terminated {stopped_count} process(es).")
275
+ return f"success_stopped_{stopped_count}"
276
+
277
+
278
+ def handle_start(self, target_user="all_active"):
279
+ log_info(f"Executing start OOTB for target '{target_user}'...")
280
+ started_count = 0
281
+ target_users_started = set()
282
+ users_failed_to_start = set()
283
+
284
+ try:
285
+ sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
286
+ active_sessions = {} # Store user: session_id for active sessions
287
+
288
+ for session in sessions:
289
+ # Look for Active sessions, potentially disconnected ones too?
290
+ # For now, only WTSActive
291
+ if session['State'] == win32ts.WTSActive:
292
+ try:
293
+ user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
294
+ if user: # Filter out system sessions etc.
295
+ normalized_user = user.lower()
296
+ active_sessions[normalized_user] = session['SessionId']
297
+ except Exception as query_err:
298
+ log_warning(f"Could not query session {session['SessionId']}: {query_err}")
299
+
300
+ log_info(f"Found active user sessions: {active_sessions}")
301
+
302
+ target_session_map = {} # user:session_id
303
+ if target_user == "all_active":
304
+ target_session_map = active_sessions
305
+ else:
306
+ normalized_target = target_user.lower()
307
+ if normalized_target in active_sessions:
308
+ target_session_map[normalized_target] = active_sessions[normalized_target]
309
+ else:
310
+ log_warning(f"Target user '{target_user}' not found in active sessions.")
311
+ return "failed_user_not_active"
312
+
313
+ if not target_session_map:
314
+ log_info("No target user sessions found to start OOTB in.")
315
+ return "failed_no_target_sessions"
316
+
317
+ # Check if OOTB is already running for the target users
318
+ running_procs = self._get_ootb_processes(target_user)
319
+ users_already_running = set()
320
+ for proc in running_procs:
321
+ try:
322
+ proc_username = proc.info.get('username')
323
+ if proc_username:
324
+ users_already_running.add(proc_username.split('\\')[-1].lower())
325
+ except Exception:
326
+ pass # Ignore errors getting username here
327
+
328
+ log_info(f"Users already running OOTB: {users_already_running}")
329
+
330
+ for user, session_id in target_session_map.items():
331
+ token = None # Ensure token is reset/defined
332
+ try:
333
+ if user in users_already_running:
334
+ log_info(f"OOTB already seems to be running for user '{user}'. Skipping start.")
335
+ continue
336
+
337
+ log_info(f"Attempting to start OOTB for user '{user}' in session {session_id}...")
338
+
339
+ # Get user token
340
+ token = win32ts.WTSQueryUserToken(session_id)
341
+
342
+ # Create environment block for the user
343
+ env = win32profile.CreateEnvironmentBlock(token, False)
344
+
345
+ # Create startup info
346
+ startup = win32process.STARTUPINFO()
347
+ startup.dwFlags = win32process.STARTF_USESHOWWINDOW
348
+ # Attempt to show window on user's desktop
349
+ # Requires Service to have "Allow service to interact with desktop" checked
350
+ # AND Interactive Services Detection service running (often disabled now).
351
+ # May be better to run hidden (SW_HIDE) or default.
352
+ startup.wShowWindow = win32con.SW_SHOW
353
+ startup.lpDesktop = 'winsta0\\default' # Try targeting default interactive desktop
354
+
355
+ # Create process as user
356
+ # Needs SeAssignPrimaryTokenPrivilege, SeIncreaseQuotaPrivilege for service account.
357
+ creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
358
+
359
+ hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
360
+ token, # User token
361
+ self.python_exe, # Application name (python executable)
362
+ self.ootb_command, # Command line
363
+ None, # Process attributes
364
+ None, # Thread attributes
365
+ False, # Inherit handles
366
+ creation_flags, # Creation flags
367
+ env, # Environment
368
+ None, # Current directory (use default)
369
+ startup # Startup info
370
+ )
371
+ log_info(f"Successfully started OOTB for user '{user}' (PID: {dwPid}).")
372
+ started_count += 1
373
+ target_users_started.add(user)
374
+ # Close handles immediately
375
+ win32api.CloseHandle(hProcess)
376
+ win32api.CloseHandle(hThread)
377
+
378
+ except Exception as proc_err:
379
+ log_error(f"Failed to start OOTB for user '{user}' in session {session_id}: {proc_err}", exc_info=True)
380
+ users_failed_to_start.add(user)
381
+ finally:
382
+ # Ensure token handle is always closed if obtained
383
+ if token:
384
+ try: win32api.CloseHandle(token)
385
+ except: pass
386
+
387
+
388
+ log_info(f"Finished starting OOTB. Started {started_count} new instance(s). Failed for users: {users_failed_to_start or 'None'}")
389
+ if users_failed_to_start:
390
+ return f"partial_success_started_{started_count}_failed_for_{len(users_failed_to_start)}"
391
+ elif started_count > 0:
392
+ return f"success_started_{started_count}"
393
+ else:
394
+ return "no_action_needed_already_running"
395
+
396
+ except Exception as e:
397
+ log_error(f"Error during start OOTB process: {e}", exc_info=True)
398
+ return "failed_exception"
399
+
400
+
401
+ # This block is essential for the service framework to handle
402
+ # command-line arguments like 'install', 'start', 'stop', 'remove', 'debug'.
403
+ if __name__ == '__main__':
404
+ # Add logic to allow debugging from command line easily
405
+ if len(sys.argv) > 1 and sys.argv[1] == 'debug':
406
+ log_info("Starting service in debug mode...")
407
+ service_instance = GuardService(sys.argv)
408
+ service_instance.is_running = True # Ensure loop runs
409
+ try:
410
+ # Run the main loop directly for debugging
411
+ service_instance.main_loop()
412
+ # Simulate stop signal for clean exit in debug
413
+ # service_instance.SvcStop() # Or let it run until Ctrl+C?
414
+ except KeyboardInterrupt:
415
+ log_info("Debug mode interrupted by user.")
416
+ service_instance.SvcStop() # Attempt clean stop
417
+ log_info("Debug mode finished.")
418
+ elif len(sys.argv) == 1:
419
+ # Called without arguments, run as a service instance via SCM
420
+ try:
421
+ servicemanager.Initialize()
422
+ servicemanager.PrepareToHostSingle(GuardService)
423
+ servicemanager.StartServiceCtrlDispatcher()
424
+ except win32service.error as details:
425
+ import winerror
426
+ if details.winerror == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
427
+ print(f"Error: Cannot connect to Service Control Manager.")
428
+ print(f"Use 'python {os.path.basename(__file__)} install|start|stop|remove|debug'")
429
+ else:
430
+ print(f"Error preparing service: {details}")
431
+ except Exception as e:
432
+ print(f"Unexpected error initializing service: {e}")
433
+
434
+ else:
435
+ # Called with install/start/stop/remove args, let ServiceFramework handle them
436
+ win32serviceutil.HandleCommandLine(GuardService)
@@ -0,0 +1,142 @@
1
+ # src/computer_use_ootb_internal/service_manager.py
2
+ import sys
3
+ import os
4
+ import inspect
5
+ import subprocess
6
+ import ctypes
7
+ import platform
8
+ import time
9
+
10
+ # Constants need to match guard_service.py
11
+ _SERVICE_NAME = "OOTBGuardService"
12
+ _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
13
+
14
+ def is_admin():
15
+ """Check if the script is running with administrative privileges."""
16
+ if platform.system() != "Windows":
17
+ return False # Only applicable on Windows
18
+ try:
19
+ return ctypes.windll.shell32.IsUserAnAdmin()
20
+ except:
21
+ return False
22
+
23
+ def get_service_module_path():
24
+ """Gets the absolute path to the guard_service.py module."""
25
+ # Find the path relative to this script's location
26
+ # This assumes service_manager.py and guard_service.py are in the same installed package directory
27
+ try:
28
+ current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
29
+ service_module = os.path.join(current_dir, "guard_service.py")
30
+ if not os.path.exists(service_module):
31
+ raise FileNotFoundError(f"guard_service.py not found adjacent to service_manager.py in {current_dir}")
32
+ return service_module
33
+ except Exception as e:
34
+ # Fallback if inspect fails (e.g., in some frozen environments)
35
+ # Try finding it relative to the script itself? Unreliable.
36
+ # Let's try sys.prefix - might work in standard venv/conda installs
37
+ try:
38
+ # sys.prefix points to the environment root (e.g., C:\path\to\env)
39
+ # Package likely installed in Lib\site-packages\<package_name>
40
+ # This depends heavily on installation layout
41
+ package_name = __name__.split('.')[0] # Should be 'computer_use_ootb_internal'
42
+ site_packages_path = os.path.join(sys.prefix, 'Lib', 'site-packages')
43
+ module_dir = os.path.join(site_packages_path, package_name)
44
+ service_module = os.path.join(module_dir, "guard_service.py")
45
+ if os.path.exists(service_module):
46
+ print(f"Warning: Found service module via sys.prefix fallback: {service_module}")
47
+ return service_module
48
+ else:
49
+ raise FileNotFoundError(f"guard_service.py not found via inspect or sys.prefix fallback (checked {module_dir})")
50
+ except Exception as fallback_e:
51
+ raise FileNotFoundError(f"Could not find guard_service.py using inspect ({e}) or sys.prefix ({fallback_e}). Check installation.")
52
+
53
+
54
+ def run_service_command(command_args, check_errors=True):
55
+ """Runs the guard_service.py script with specified command-line args."""
56
+ if not is_admin():
57
+ print("Error: Administrative privileges are required to manage the service.", file=sys.stderr)
58
+ print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
59
+ return False
60
+
61
+ try:
62
+ python_exe = sys.executable # Use the same python that's running this script
63
+ service_script = get_service_module_path()
64
+ except FileNotFoundError as e:
65
+ print(f"Error: {e}", file=sys.stderr)
66
+ return False
67
+
68
+ # Quote paths if they contain spaces
69
+ if " " in python_exe and not python_exe.startswith('"'):
70
+ python_exe = f'"{python_exe}"'
71
+ if " " in service_script and not service_script.startswith('"'):
72
+ service_script = f'"{service_script}"'
73
+
74
+ # Construct command using list to avoid shell quoting issues
75
+ cmd = [sys.executable, get_service_module_path()] + command_args
76
+ print(f"Executing command: {' '.join(cmd)}")
77
+
78
+ try:
79
+ # Run the command. Use shell=False with list of args.
80
+ # Capture output to check for specific errors if needed, but print it too.
81
+ result = subprocess.run(cmd, capture_output=True, text=True, check=check_errors, encoding='utf-8')
82
+ if result.stdout:
83
+ print("Command STDOUT:")
84
+ print(result.stdout)
85
+ if result.stderr:
86
+ print("Command STDERR:")
87
+ print(result.stderr)
88
+ print(f"Command {' '.join(command_args)} executed successfully.")
89
+ return True
90
+ except FileNotFoundError as e:
91
+ print(f"Error: Could not find Python executable or service script during execution.", file=sys.stderr)
92
+ print(f" Details: {e}", file=sys.stderr)
93
+ return False
94
+ except subprocess.CalledProcessError as e:
95
+ print(f"Error executing service command {' '.join(command_args)} (Exit Code {e.returncode}).", file=sys.stderr)
96
+ if e.stdout:
97
+ print("Subprocess STDOUT:")
98
+ print(e.stdout)
99
+ if e.stderr:
100
+ print("Subprocess STDERR:")
101
+ print(e.stderr)
102
+ return False
103
+ except Exception as e:
104
+ print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
105
+ return False
106
+
107
+
108
+ def install_and_start():
109
+ """Installs and starts the Guard Service."""
110
+ print(f"Attempting to install/update service: '{_SERVICE_NAME}' ('{_SERVICE_DISPLAY_NAME}')")
111
+ # Use 'update' which handles install or updates display name/path if needed
112
+ if run_service_command(['--startup', 'auto', 'update']): # Set startup to auto, then update
113
+ print(f"\nAttempting to start service: '{_SERVICE_NAME}' (waiting a few seconds first)")
114
+ time.sleep(3) # Give SCM time to register the update
115
+ if run_service_command(['start']):
116
+ print(f"\nService '{_SERVICE_NAME}' installed/updated and started successfully.")
117
+ else:
118
+ print(f"\nWarning: Service '{_SERVICE_NAME}' installed/updated but failed to start.", file=sys.stderr)
119
+ print(f" Check service logs ('C:\ProgramData\OOTBGuardService\guard.log') or Windows Event Viewer for details.", file=sys.stderr)
120
+ print(f" You may need to start it manually via 'services.msc' or `net start {_SERVICE_NAME}`.")
121
+ else:
122
+ print(f"\nService '{_SERVICE_NAME}' installation/update failed.", file=sys.stderr)
123
+
124
+
125
+ def stop_and_remove():
126
+ """Stops and removes the Guard Service."""
127
+ print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
128
+ # Run stop first, ignore errors (check_errors=False)
129
+ run_service_command(['stop'], check_errors=False)
130
+ time.sleep(2) # Give service time to stop
131
+
132
+ print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
133
+ if run_service_command(['remove']):
134
+ print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully.")
135
+ else:
136
+ print(f"\nService '{_SERVICE_NAME}' removal failed.", file=sys.stderr)
137
+ print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
138
+
139
+ if __name__ == '__main__':
140
+ # Allow calling functions directly for testing if needed
141
+ print("This script provides service management commands.")
142
+ print("Use 'ootb-install-service' or 'ootb-remove-service' as Administrator.")