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.
- computer_use_ootb_internal/guard_service.py +369 -120
- computer_use_ootb_internal/service_manager.py +47 -4
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/METADATA +1 -1
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/RECORD +6 -6
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/WHEEL +0 -0
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/entry_points.txt +0 -0
@@ -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
|
-
|
356
|
-
|
445
|
+
stop_results = {} # Track results per user {username: (task_status, immediate_status)}
|
446
|
+
failed_users = set()
|
357
447
|
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
-
|
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
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
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
|
-
|
384
|
-
|
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
|
-
|
390
|
-
|
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
|
395
|
-
active_sessions = {}
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
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
|
-
|
604
|
+
target_users_normalized = set()
|
410
605
|
if target_user == "all_active":
|
411
|
-
|
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
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
-
|
421
|
-
|
422
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
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
|
-
|
144
|
-
|
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__':
|
{computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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.
|
38
|
-
computer_use_ootb_internal-0.0.
|
39
|
-
computer_use_ootb_internal-0.0.
|
40
|
-
computer_use_ootb_internal-0.0.
|
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,,
|
{computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.120.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|