computer-use-ootb-internal 0.0.119__py3-none-any.whl → 0.0.120__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.
@@ -22,6 +22,7 @@ import win32con
22
22
  import psutil # For process/user info
23
23
  from flask import Flask, request, jsonify # For embedded server
24
24
  from waitress import serve # For serving Flask app
25
+ import json # Needed for status reporting
25
26
 
26
27
  # --- Configuration ---
27
28
  _SERVICE_NAME = "OOTBGuardService"
@@ -144,6 +145,95 @@ def log_error(msg, exc_info=False):
144
145
  except Exception as e:
145
146
  logging.warning(f"Could not write error to Windows Event Log: {e}")
146
147
 
148
+ # --- PowerShell Task Scheduler Helpers ---
149
+
150
+ _TASK_NAME_PREFIX = "OOTB_UserLogon_"
151
+
152
+ def run_powershell_command(command, log_output=True):
153
+ """Executes a PowerShell command and handles output/errors. Returns True on success."""
154
+ # Use log_info from the service instance if available, otherwise use root logger
155
+ logger = _service_instance.log_info if _service_instance else logging.info
156
+ error_logger = _service_instance.log_error if _service_instance else logging.error
157
+ logger(f"Executing PowerShell: {command}")
158
+ try:
159
+ # Using encoding important for non-ASCII usernames/paths
160
+ result = subprocess.run(
161
+ ["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
162
+ capture_output=True, text=True, check=True, encoding='utf-8', errors='ignore'
163
+ )
164
+ if log_output and result.stdout:
165
+ logger(f"PowerShell STDOUT:\n{result.stdout.strip()}")
166
+ if log_output and result.stderr:
167
+ logger(f"PowerShell STDERR:\n{result.stderr.strip()}") # Log stderr as info
168
+ return True
169
+ except FileNotFoundError:
170
+ error_logger("'powershell.exe' not found. Cannot manage scheduled tasks.")
171
+ return False
172
+ except subprocess.CalledProcessError as e:
173
+ error_logger(f"PowerShell command failed (Exit Code {e.returncode}):")
174
+ error_logger(f" Command: {e.cmd}")
175
+ if e.stdout: error_logger(f" STDOUT: {e.stdout.strip()}")
176
+ if e.stderr: error_logger(f" STDERR: {e.stderr.strip()}")
177
+ return False
178
+ except Exception as e:
179
+ error_logger(f"Unexpected error running PowerShell: {e}", exc_info=True)
180
+ return False
181
+
182
+ def create_or_update_logon_task(username, task_command, python_executable):
183
+ """Creates or updates a scheduled task to run a command at user logon."""
184
+ logger = _service_instance.log_info if _service_instance else logging.info
185
+ error_logger = _service_instance.log_error if _service_instance else logging.error
186
+ task_name = f"{_TASK_NAME_PREFIX}{username}"
187
+ # Escape single quotes in paths and commands for PowerShell
188
+ safe_python_exe = python_executable.replace("'", "''")
189
+ # Ensure task_command is just the arguments, not the python exe itself
190
+ command_parts = task_command.split(' ', 1)
191
+ if len(command_parts) > 1 and command_parts[0] == python_executable:
192
+ safe_task_command_args = command_parts[1].replace("'", "''")
193
+ else: # Fallback if task_command doesn't start with python_exe
194
+ safe_task_command_args = task_command.replace(python_executable, "").strip().replace("'", "''")
195
+
196
+ safe_task_name = task_name.replace("'", "''")
197
+ safe_username = username.replace("'", "''") # Handle usernames with quotes?
198
+
199
+ action = f"$Action = New-ScheduledTaskAction -Execute '{safe_python_exe}' -Argument '{safe_task_command_args}'"
200
+ trigger = f"$Trigger = New-ScheduledTaskTrigger -AtLogOn -User '{safe_username}'"
201
+ principal = f"$Principal = New-ScheduledTaskPrincipal -UserId '{safe_username}' -LogonType Interactive"
202
+ settings = "$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit ([System.TimeSpan]::Zero) -RunOnlyIfNetworkAvailable:$false"
203
+
204
+ command = f"""
205
+ try {{
206
+ {action}
207
+ {trigger}
208
+ {principal}
209
+ {settings}
210
+ Register-ScheduledTask -TaskName '{safe_task_name}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -ErrorAction Stop
211
+ Write-Host "Scheduled task '{safe_task_name}' registered/updated successfully."
212
+ }} catch {{
213
+ Write-Error "Failed to register/update scheduled task '{safe_task_name}': $_"
214
+ exit 1 # Indicate failure
215
+ }}
216
+ """
217
+ success = run_powershell_command(command)
218
+ if success:
219
+ logger(f"Successfully created/updated scheduled task '{task_name}' for user '{username}'.")
220
+ else:
221
+ error_logger(f"Failed to create/update scheduled task '{task_name}' for user '{username}'.")
222
+ return success
223
+
224
+
225
+ def remove_logon_task(username):
226
+ """Removes the logon scheduled task for a user."""
227
+ logger = _service_instance.log_info if _service_instance else logging.info
228
+ task_name = f"{_TASK_NAME_PREFIX}{username}"
229
+ safe_task_name = task_name.replace("'", "''")
230
+ command = f"Unregister-ScheduledTask -TaskName '{safe_task_name}' -Confirm:$false -ErrorAction SilentlyContinue"
231
+ run_powershell_command(command, log_output=False)
232
+ logger(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
233
+ return True
234
+
235
+ # --- End PowerShell Task Scheduler Helpers ---
236
+
147
237
  class GuardService(win32serviceutil.ServiceFramework):
148
238
  _svc_name_ = _SERVICE_NAME
149
239
  _svc_display_name_ = _SERVICE_DISPLAY_NAME
@@ -352,147 +442,306 @@ class GuardService(win32serviceutil.ServiceFramework):
352
442
 
353
443
  def handle_stop(self, target_user="all_active"):
354
444
  log_info(f"Executing stop OOTB for target '{target_user}'...")
355
- stopped_count = 0
356
- procs_to_stop = self._get_ootb_processes(target_user)
445
+ stop_results = {} # Track results per user {username: (task_status, immediate_status)}
446
+ failed_users = set()
357
447
 
358
- if not procs_to_stop:
359
- log_info("No running OOTB processes found for target.")
360
- return "no_process_found"
448
+ try:
449
+ # --- Get target users and active sessions ---
450
+ active_sessions = {} # user_lower: session_id
451
+ # No need for all_system_users for stop, we only care about active or the specific target
452
+ try:
453
+ sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
454
+ for session in sessions:
455
+ if session['State'] == win32ts.WTSActive:
456
+ try:
457
+ user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
458
+ if user:
459
+ active_sessions[user.lower()] = session['SessionId']
460
+ except Exception as query_err:
461
+ log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
462
+ except Exception as user_enum_err:
463
+ log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
464
+ return "failed_user_enumeration"
465
+
466
+ log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
467
+
468
+ target_users_normalized = set()
469
+ if target_user == "all_active":
470
+ # Target only currently active users for stop all
471
+ target_users_normalized = set(active_sessions.keys())
472
+ log_info(f"Stop targeting all active users: {target_users_normalized}")
473
+ else:
474
+ # Target the specific user, regardless of active status (for task removal)
475
+ normalized_target = target_user.lower()
476
+ target_users_normalized.add(normalized_target)
477
+ log_info(f"Stop targeting specific user: {normalized_target}")
478
+
479
+ if not target_users_normalized:
480
+ log_info("No target users identified for stop.")
481
+ return "failed_no_target_users" # Or success if none were targeted?
361
482
 
362
- for proc in procs_to_stop:
483
+ # --- Process each target user ---
484
+ for user in target_users_normalized:
485
+ task_removed_status = "task_unknown"
486
+ immediate_stop_status = "stop_not_attempted"
487
+ stopped_count = 0
488
+
489
+ log_info(f"Processing stop for user '{user}'...")
490
+
491
+ # 1. Always try to remove the scheduled task
492
+ try:
493
+ # remove_logon_task always returns True for now, just logs attempt
494
+ remove_logon_task(user)
495
+ task_removed_status = "task_removed_attempted"
496
+ except Exception as task_err:
497
+ log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
498
+ task_removed_status = "task_exception"
499
+ failed_users.add(user)
500
+ # Continue to try and stop process if active
501
+
502
+ # 2. If user is active, try to terminate process
503
+ is_active = user in active_sessions
504
+
505
+ if is_active:
506
+ immediate_stop_status = "stop_attempted"
507
+ log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
508
+ # Pass the specific username to _get_ootb_processes
509
+ procs_to_stop = self._get_ootb_processes(user)
510
+
511
+ if not procs_to_stop:
512
+ log_info(f"No running OOTB processes found for active user '{user}'.")
513
+ immediate_stop_status = "stop_skipped_not_running"
514
+ else:
515
+ log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
516
+ for proc in procs_to_stop:
517
+ try:
518
+ pid = proc.pid # Get pid before potential termination
519
+ username = proc.info.get('username', 'unknown_user')
520
+ log_info(f"Terminating process PID={pid}, User={username}")
521
+ proc.terminate()
522
+ try:
523
+ proc.wait(timeout=3)
524
+ log_info(f"Process PID={pid} terminated successfully.")
525
+ stopped_count += 1
526
+ except psutil.TimeoutExpired:
527
+ log_error(f"Process PID={pid} did not terminate gracefully, killing.")
528
+ proc.kill()
529
+ stopped_count += 1
530
+ except psutil.NoSuchProcess:
531
+ log_info(f"Process PID={pid} already terminated.")
532
+ # Don't increment stopped_count here as we didn't stop it now
533
+ except psutil.AccessDenied:
534
+ log_error(f"Access denied trying to terminate process PID={pid}.")
535
+ failed_users.add(user) # Mark user as failed if stop fails
536
+ except Exception as e:
537
+ log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
538
+ failed_users.add(user) # Mark user as failed
539
+
540
+ # Determine status based on how many were found vs stopped
541
+ if user in failed_users:
542
+ immediate_stop_status = f"stop_errors_terminated_{stopped_count}_of_{len(procs_to_stop)}"
543
+ elif stopped_count == len(procs_to_stop):
544
+ immediate_stop_status = f"stop_success_terminated_{stopped_count}"
545
+ else: # Should ideally not happen if NoSuchProcess doesn't count
546
+ immediate_stop_status = f"stop_partial_terminated_{stopped_count}_of_{len(procs_to_stop)}"
547
+
548
+ else: # User not active
549
+ log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
550
+ immediate_stop_status = "stop_skipped_inactive"
551
+
552
+ # Record final results for this user
553
+ stop_results[user] = (task_removed_status, immediate_stop_status)
554
+
555
+
556
+ # --- Consolidate status ---
557
+ total_processed = len(target_users_normalized)
558
+ final_status = "partial_success" if failed_users else "success"
559
+ if not stop_results: final_status = "no_targets_processed"
560
+ if len(failed_users) == total_processed and total_processed > 0 : final_status = "failed"
561
+
562
+ log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
363
563
  try:
364
- username = proc.info.get('username', 'unknown_user')
365
- log_info(f"Terminating process PID={proc.pid}, User={username}")
366
- proc.terminate()
367
- try:
368
- proc.wait(timeout=3)
369
- log_info(f"Process PID={proc.pid} terminated successfully.")
370
- stopped_count += 1
371
- except psutil.TimeoutExpired:
372
- log_error(f"Process PID={proc.pid} did not terminate gracefully, killing.")
373
- proc.kill()
374
- stopped_count += 1
375
- except psutil.NoSuchProcess:
376
- log_info(f"Process PID={proc.pid} already terminated.")
377
- stopped_count +=1
378
- except psutil.AccessDenied:
379
- log_error(f"Access denied trying to terminate process PID={proc.pid}. Service might lack privileges?")
380
- except Exception as e:
381
- log_error(f"Error stopping process PID={proc.pid}: {e}", exc_info=True)
564
+ details = json.dumps(stop_results)
565
+ except Exception:
566
+ details = str(stop_results) # Fallback
567
+ return f"{final_status}::{details}" # Use :: as separator
382
568
 
383
- log_info(f"Finished stopping OOTB. Terminated {stopped_count} process(es).")
384
- return f"success_stopped_{stopped_count}"
569
+ except Exception as e:
570
+ log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
571
+ return "failed_exception"
385
572
 
386
573
 
387
574
  def handle_start(self, target_user="all_active"):
388
575
  log_info(f"Executing start OOTB for target '{target_user}'...")
389
- started_count = 0
390
- target_users_started = set()
391
- users_failed_to_start = set()
576
+ start_results = {} # Track results per user {username: (task_status, immediate_status)}
577
+ failed_users = set()
392
578
 
393
579
  try:
394
- sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
395
- active_sessions = {}
396
-
397
- for session in sessions:
398
- if session['State'] == win32ts.WTSActive:
399
- try:
400
- user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
401
- if user:
402
- normalized_user = user.lower()
403
- active_sessions[normalized_user] = session['SessionId']
404
- except Exception as query_err:
405
- log_error(f"Could not query session {session['SessionId']}: {query_err}")
580
+ # --- Get target users and active sessions ---
581
+ active_sessions = {} # user_lower: session_id
582
+ all_system_users = set() # user_lower
583
+ try:
584
+ # Use psutil for system user list, WTS for active sessions/IDs
585
+ for user_session in psutil.users():
586
+ username_lower = user_session.name.split('\\')[-1].lower()
587
+ all_system_users.add(username_lower)
588
+
589
+ sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
590
+ for session in sessions:
591
+ if session['State'] == win32ts.WTSActive:
592
+ try:
593
+ user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
594
+ if user:
595
+ active_sessions[user.lower()] = session['SessionId']
596
+ except Exception as query_err:
597
+ log_error(f"Could not query session {session['SessionId']}: {query_err}")
598
+ except Exception as user_enum_err:
599
+ log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
600
+ return "failed_user_enumeration"
406
601
 
407
602
  log_info(f"Found active user sessions: {active_sessions}")
408
603
 
409
- target_session_map = {}
604
+ target_users_normalized = set()
410
605
  if target_user == "all_active":
411
- target_session_map = active_sessions
606
+ # If targeting all_active, only target those CURRENTLY active
607
+ target_users_normalized = set(active_sessions.keys())
608
+ log_info(f"Targeting all active users: {target_users_normalized}")
412
609
  else:
413
- normalized_target = target_user.lower()
414
- if normalized_target in active_sessions:
415
- target_session_map[normalized_target] = active_sessions[normalized_target]
416
- else:
417
- log_error(f"Target user '{target_user}' not found in active sessions.")
418
- return "failed_user_not_active"
610
+ normalized_target = target_user.lower()
611
+ # Check if the target user actually exists on the system, even if inactive
612
+ # This check might be complex/unreliable. Rely on task scheduler potentially failing?
613
+ # Let's assume admin provides a valid username for specific targeting.
614
+ # if normalized_target in all_system_users: # Removing this check, assume valid user input
615
+ target_users_normalized.add(normalized_target)
616
+ log_info(f"Targeting specific user: {normalized_target}")
617
+ # else:
618
+ # log_error(f"Target user '{target_user}' does not appear to exist on this system based on psutil.")
619
+ # return "failed_user_does_not_exist"
620
+
621
+ if not target_users_normalized:
622
+ log_info("No target users identified (or none active for 'all_active').")
623
+ # If target was specific user but they weren't found active, still try task?
624
+ # Let's proceed to task creation anyway for specific user case.
625
+ if target_user != "all_active": target_users_normalized.add(target_user.lower())
626
+ if not target_users_normalized:
627
+ return "failed_no_target_users"
628
+
629
+ # --- Check existing processes ---
630
+ # This check is only relevant for immediate start attempt
631
+ running_procs_by_user = {} # user_lower: count
632
+ try:
633
+ current_running = self._get_ootb_processes("all_active") # Check all
634
+ for proc in current_running:
635
+ try:
636
+ proc_username = proc.info.get('username')
637
+ if proc_username:
638
+ user_lower = proc_username.split('\\')[-1].lower()
639
+ running_procs_by_user[user_lower] = running_procs_by_user.get(user_lower, 0) + 1
640
+ except Exception: pass
641
+ except Exception as e:
642
+ log_error(f"Error checking existing processes: {e}")
643
+ log_info(f"Users currently running OOTB: {running_procs_by_user}")
419
644
 
420
- if not target_session_map:
421
- log_info("No target user sessions found to start OOTB in.")
422
- return "failed_no_target_sessions"
645
+ # --- Process each target user ---
646
+ for user in target_users_normalized:
647
+ task_created_status = "task_unknown"
648
+ immediate_start_status = "start_not_attempted"
649
+ token = None # Ensure token is reset/defined
423
650
 
424
- running_procs = self._get_ootb_processes(target_user)
425
- users_already_running = set()
426
- for proc in running_procs:
427
- try:
428
- proc_username = proc.info.get('username')
429
- if proc_username:
430
- users_already_running.add(proc_username.split('\\')[-1].lower())
431
- except Exception:
432
- pass
651
+ log_info(f"Processing start for user '{user}'...")
433
652
 
434
- log_info(f"Users already running OOTB: {users_already_running}")
653
+ # 1. Always try to create/update the scheduled task
654
+ try:
655
+ task_created = create_or_update_logon_task(user, self.ootb_command, self.python_exe)
656
+ task_created_status = "task_success" if task_created else "task_failed"
657
+ except Exception as task_err:
658
+ log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
659
+ task_created_status = "task_exception"
660
+ failed_users.add(user)
661
+ # Continue to potentially try immediate start IF user is active?
662
+ # Or maybe skip if task creation failed badly?
663
+ # Let's skip immediate start if task creation had exception.
664
+ start_results[user] = (task_created_status, immediate_start_status)
665
+ continue
666
+
667
+ # 2. If user is active AND not already running, try immediate start
668
+ is_active = user in active_sessions
669
+ is_running = running_procs_by_user.get(user, 0) > 0
670
+
671
+ if is_active:
672
+ if not is_running:
673
+ immediate_start_status = "start_attempted"
674
+ log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
675
+ try:
676
+ session_id = active_sessions[user]
677
+ token = win32ts.WTSQueryUserToken(session_id)
678
+ env = win32profile.CreateEnvironmentBlock(token, False)
679
+ startup = win32process.STARTUPINFO()
680
+ startup.dwFlags = win32process.STARTF_USESHOWWINDOW
681
+ startup.wShowWindow = win32con.SW_HIDE
682
+ creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
683
+ user_profile_dir = win32profile.GetUserProfileDirectory(token)
684
+
685
+ hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
686
+ token, self.python_exe, self.ootb_command,
687
+ None, None, False, creation_flags, env, user_profile_dir, startup
688
+ )
689
+ log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
690
+ win32api.CloseHandle(hProcess)
691
+ win32api.CloseHandle(hThread)
692
+
693
+ time.sleep(1)
694
+ if psutil.pid_exists(dwPid):
695
+ log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
696
+ immediate_start_status = "start_success"
697
+ else:
698
+ log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
699
+ immediate_start_status = "start_failed_exited"
700
+ failed_users.add(user)
701
+
702
+ except Exception as proc_err:
703
+ log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
704
+ immediate_start_status = "start_failed_exception"
705
+ failed_users.add(user)
706
+ finally:
707
+ if token:
708
+ try: win32api.CloseHandle(token)
709
+ except: pass
710
+ else: # User is active but already running
711
+ log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
712
+ immediate_start_status = "start_skipped_already_running"
713
+ else: # User is not active
714
+ log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
715
+ immediate_start_status = "start_skipped_inactive"
716
+
717
+ # Record final results for this user
718
+ start_results[user] = (task_created_status, immediate_start_status)
719
+
720
+
721
+ # --- Consolidate status ---
722
+ total_processed = len(target_users_normalized)
723
+ final_status = "partial_success" if failed_users else "success"
724
+ if not start_results: final_status = "no_targets_processed"
725
+ # If all processed users failed in some way (either task or start)
726
+ if len(failed_users) == total_processed and total_processed > 0: final_status = "failed"
727
+ # Special case: target was specific user who wasn't found active
728
+ elif total_processed == 1 and target_user != "all_active" and target_user.lower() not in active_sessions:
729
+ user_key = target_user.lower()
730
+ if user_key in start_results and start_results[user_key][0] == "task_success":
731
+ final_status = "success_task_only_user_inactive"
732
+ else:
733
+ final_status = "failed_task_user_inactive"
435
734
 
436
- for user, session_id in target_session_map.items():
437
- token = None
438
- try:
439
- if user in users_already_running:
440
- log_info(f"OOTB already seems to be running for user '{user}'. Skipping start.")
441
- continue
442
-
443
- log_info(f"Attempting to start OOTB for user '{user}' in session {session_id}...")
444
- token = win32ts.WTSQueryUserToken(session_id)
445
- env = win32profile.CreateEnvironmentBlock(token, False)
446
- startup = win32process.STARTUPINFO()
447
- # Simplify startup flags: Run hidden, don't explicitly set desktop
448
- startup.dwFlags = win32process.STARTF_USESHOWWINDOW
449
- startup.wShowWindow = win32con.SW_HIDE
450
- # startup.lpDesktop = 'winsta0\\default' # Removed
451
-
452
- creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
453
- # Define cwd as user's profile directory if possible
454
- user_profile_dir = win32profile.GetUserProfileDirectory(token)
455
-
456
- hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
457
- token, self.python_exe, self.ootb_command,
458
- None, None, False, creation_flags, env,
459
- user_profile_dir, # Set current directory
460
- startup
461
- )
462
- log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}).")
463
-
464
- # Add post-start check
465
- time.sleep(1) # Small delay
466
- if psutil.pid_exists(dwPid):
467
- log_info(f"Process PID {dwPid} confirmed to exist shortly after creation.")
468
- started_count += 1
469
- target_users_started.add(user)
470
- else:
471
- log_error(f"Process PID {dwPid} reported by CreateProcessAsUser does NOT exist shortly after creation. It likely exited immediately.")
472
- users_failed_to_start.add(user)
473
- # Attempt to get exit code? Difficult without waiting.
474
-
475
- win32api.CloseHandle(hProcess)
476
- win32api.CloseHandle(hThread)
477
-
478
- except Exception as proc_err:
479
- log_error(f"Failed to start OOTB for user '{user}' in session {session_id}: {proc_err}", exc_info=True)
480
- users_failed_to_start.add(user)
481
- finally:
482
- if token:
483
- try: win32api.CloseHandle(token)
484
- except: pass
485
-
486
- log_info(f"Finished starting OOTB. Started {started_count} new instance(s). Failed for users: {users_failed_to_start or 'None'}")
487
- if users_failed_to_start:
488
- return f"partial_success_started_{started_count}_failed_for_{len(users_failed_to_start)}"
489
- elif started_count > 0:
490
- return f"success_started_{started_count}"
491
- else:
492
- return "no_action_needed_already_running"
735
+ log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
736
+ # Return detailed results as a JSON string for easier parsing/logging server-side
737
+ try:
738
+ details = json.dumps(start_results)
739
+ except Exception:
740
+ details = str(start_results) # Fallback
741
+ return f"{final_status}::{details}"
493
742
 
494
743
  except Exception as e:
495
- log_error(f"Error during start OOTB process: {e}", exc_info=True)
744
+ log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
496
745
  return "failed_exception"
497
746
 
498
747
  # --- Main Execution Block ---
@@ -10,6 +10,7 @@ import time
10
10
  # Constants need to match guard_service.py
11
11
  _SERVICE_NAME = "OOTBGuardService"
12
12
  _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
13
+ _TASK_NAME_PREFIX = "OOTB_UserLogon_" # Must match guard_service.py
13
14
 
14
15
  def is_admin():
15
16
  """Check if the script is running with administrative privileges."""
@@ -104,6 +105,41 @@ def run_service_command(command_args, check_errors=True):
104
105
  print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
105
106
  return False
106
107
 
108
+ # --- Add cleanup helpers ---
109
+ def _run_powershell_cleanup_command(command):
110
+ """Executes a PowerShell command specifically for cleanup, ignoring most errors."""
111
+ if platform.system() != "Windows": return True # Skip on non-windows
112
+ print(f"Executing PowerShell Cleanup: {command}")
113
+ try:
114
+ # Use check=False, don't capture output unless needed for debug
115
+ subprocess.run(
116
+ ["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
117
+ check=False, # Don't throw error if command fails (e.g., no tasks found)
118
+ stdout=subprocess.DEVNULL, # Suppress stdout
119
+ stderr=subprocess.DEVNULL # Suppress stderr
120
+ )
121
+ return True # Assume success for cleanup flow
122
+ except Exception as e:
123
+ print(f"Warning: PowerShell cleanup command failed: {e}", file=sys.stderr)
124
+ return False # Indicate potential issue
125
+
126
+ def _cleanup_scheduled_tasks():
127
+ """Removes all OOTB user logon scheduled tasks."""
128
+ print("Attempting to remove any existing OOTB user logon scheduled tasks...")
129
+ # Use -like operator and wildcard
130
+ # Use try-catch within PowerShell for robustness
131
+ command = f"""
132
+ $tasks = Get-ScheduledTask | Where-Object {{ $_.TaskName -like '{_TASK_NAME_PREFIX}*' }}
133
+ if ($tasks) {{
134
+ Write-Host "Found $($tasks.Count) OOTB logon tasks to remove."
135
+ $tasks | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
136
+ Write-Host "OOTB logon task removal attempted."
137
+ }} else {{
138
+ Write-Host "No OOTB logon tasks found to remove."
139
+ }}
140
+ """
141
+ _run_powershell_cleanup_command(command)
142
+ # --- End cleanup helpers ---
107
143
 
108
144
  def install_and_start():
109
145
  """Installs and starts the Guard Service."""
@@ -133,17 +169,24 @@ def install_and_start():
133
169
 
134
170
 
135
171
  def stop_and_remove():
136
- """Stops and removes the Guard Service."""
172
+ """Stops and removes the Guard Service and associated scheduled tasks."""
137
173
  print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
138
174
  # Run stop first, ignore errors (check_errors=False)
139
175
  run_service_command(['stop'], check_errors=False)
140
176
  time.sleep(2) # Give service time to stop
141
177
 
142
178
  print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
143
- if run_service_command(['remove']):
144
- print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully.")
179
+ remove_success = run_service_command(['remove']) # Check if removal command itself failed
180
+
181
+ # Always attempt task cleanup, even if service removal had issues
182
+ _cleanup_scheduled_tasks()
183
+
184
+ if remove_success:
185
+ print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully. Associated logon tasks cleanup attempted.")
145
186
  else:
146
- print(f"\nService '{_SERVICE_NAME}' removal failed.", file=sys.stderr)
187
+ print(f"\nService '{_SERVICE_NAME}' removal command failed.", file=sys.stderr)
188
+ # Make sure to mention cleanup was still attempted
189
+ print(f" Associated logon tasks cleanup attempted.", file=sys.stderr)
147
190
  print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
148
191
 
149
192
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computer-use-ootb-internal
3
- Version: 0.0.119
3
+ Version: 0.0.120
4
4
  Summary: Computer Use OOTB
5
5
  Author-email: Siyuan Hu <siyuan.hu.sg@gmail.com>
6
6
  Requires-Python: >=3.11
@@ -3,10 +3,10 @@ computer_use_ootb_internal/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4Tv
3
3
  computer_use_ootb_internal/app_teachmode.py,sha256=PClMS7X6zRGzY7YPOV6Zxkfv5BajLVqmBiv-mOBVxkw,22164
4
4
  computer_use_ootb_internal/app_teachmode_gradio.py,sha256=cmFpBrkdlZxOQADWveVdIaaNqaBD8IVs-xNLJogU7F8,7909
5
5
  computer_use_ootb_internal/dependency_check.py,sha256=y8RMEP6RXQzTgU1MS_1piBLtz4J-Hfn9RjUZg59dyvo,1333
6
- computer_use_ootb_internal/guard_service.py,sha256=d0tZgzjmS8UQGHx2Yf5HuMtRxdJIgy2zLpUTFQF5JF8,24378
6
+ computer_use_ootb_internal/guard_service.py,sha256=1CiueJvILdqPB1Ncxq9lUIkhUk7yZ-AgVBEwnbmkCNU,39691
7
7
  computer_use_ootb_internal/requirements-lite.txt,sha256=5DAHomz4A_P2BmTIXNkNqkHbnIF0AyZ4_1XAlb1LaYs,290
8
8
  computer_use_ootb_internal/run_teachmode_ootb_args.py,sha256=7Dj0iY4GG7P03tRKYJ2x9Yvt-PE-b7uyjCAed3SaF3Y,7086
9
- computer_use_ootb_internal/service_manager.py,sha256=xbLIxQ4jzMr8AdZdxKw-QcAGm2Y86vudDzSf3hGjOM4,7595
9
+ computer_use_ootb_internal/service_manager.py,sha256=SD8jzfn0VVXBOr_nP6zmBWSC2TzrU_sp2e5JJkSlQFU,9734
10
10
  computer_use_ootb_internal/computer_use_demo/animation/click_animation.py,sha256=tP1gsayFy-CKk10UlrE9RlexwlHWiHQUe5Ogg4vQvSg,3234
11
11
  computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif,sha256=4LfwsfFQnREXrNRs32aJU2jO65JXianJoL_8q7-8elg,30966
12
12
  computer_use_ootb_internal/computer_use_demo/animation/test_animation.py,sha256=2R1u98OLKYalSZ5nt5vvyZ71FL5R5vLv-n8zM8jVdV8,1183
@@ -34,7 +34,7 @@ computer_use_ootb_internal/computer_use_demo/tools/run.py,sha256=xhXdnBK1di9muaO
34
34
  computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py,sha256=L8qfvtUkPPQGt92N-2Zfw5ZTDBzLsDps39uMnX3_uSA,6857
35
35
  computer_use_ootb_internal/preparation/__init__.py,sha256=AgtGHcBpiTkxJjF0xwcs3yyQ6SyUvhL3G0vD2XO-zJw,63
36
36
  computer_use_ootb_internal/preparation/star_rail_prepare.py,sha256=s1VWszcTnJAKxqCHFlaOEwPkqVSrkiFx_yKpWSnSbHs,2649
37
- computer_use_ootb_internal-0.0.119.dist-info/METADATA,sha256=9FKAgkb-tPT3TpODW9nONQUvFwGnX7NwXOJvBKZGAAk,1048
38
- computer_use_ootb_internal-0.0.119.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
- computer_use_ootb_internal-0.0.119.dist-info/entry_points.txt,sha256=bXfyAU_qq-G1EiEgAQEioXvgEdRCFxaTooqdDD9Y4OA,258
40
- computer_use_ootb_internal-0.0.119.dist-info/RECORD,,
37
+ computer_use_ootb_internal-0.0.120.dist-info/METADATA,sha256=51EX1JLqwmtsEHPfkcQ4Ie2exLtQdAMh6YTigZuhxhU,1048
38
+ computer_use_ootb_internal-0.0.120.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ computer_use_ootb_internal-0.0.120.dist-info/entry_points.txt,sha256=bXfyAU_qq-G1EiEgAQEioXvgEdRCFxaTooqdDD9Y4OA,258
40
+ computer_use_ootb_internal-0.0.120.dist-info/RECORD,,