computer-use-ootb-internal 0.0.169__py3-none-any.whl → 0.0.170__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.
@@ -19,7 +19,10 @@ from fastapi.responses import JSONResponse
19
19
  from fastapi.middleware.cors import CORSMiddleware
20
20
  from computer_use_ootb_internal.computer_use_demo.tools.computer import get_screen_details
21
21
  from computer_use_ootb_internal.run_teachmode_ootb_args import simple_teachmode_sampling_loop
22
+ from computer_use_ootb_internal.computer_use_demo.executor.teachmode_executor import TeachmodeExecutor
22
23
  import uvicorn # Assuming uvicorn is used to run FastAPI
24
+ import concurrent.futures
25
+ import asyncio
23
26
 
24
27
  # --- App Logging Setup ---
25
28
  try:
@@ -395,6 +398,62 @@ async def get_status(request: Request):
395
398
  status_code=200
396
399
  )
397
400
 
401
+ @app.post("/exec_computer_tool")
402
+ async def exec_computer_tool(request: Request):
403
+ logging.info("Received request to /exec_computer_tool")
404
+ try:
405
+ data = await request.json()
406
+
407
+ # Extract parameters from the request
408
+ selected_screen = data.get('selected_screen', 0)
409
+ full_screen_game_mode = data.get('full_screen_game_mode', 0)
410
+ response = data.get('response', {})
411
+
412
+ logging.info(f"Executing TeachmodeExecutor with: screen={selected_screen}, mode={full_screen_game_mode}, response={response}")
413
+
414
+ # Create TeachmodeExecutor in a separate process to avoid event loop conflicts
415
+ # Since TeachmodeExecutor uses asyncio.run() internally, we need to run it in a way
416
+ # that doesn't conflict with FastAPI's event loop
417
+
418
+ def run_executor():
419
+ executor = TeachmodeExecutor(
420
+ selected_screen=selected_screen,
421
+ full_screen_game_mode=full_screen_game_mode
422
+ )
423
+
424
+ results = []
425
+ try:
426
+ for action_result in executor(response):
427
+ results.append(action_result)
428
+ except Exception as exec_error:
429
+ logging.error(f"Error executing action: {exec_error}", exc_info=True)
430
+ return {"error": str(exec_error)}
431
+
432
+ return results
433
+
434
+ # Execute in a thread pool to avoid blocking the event loop
435
+ with concurrent.futures.ThreadPoolExecutor() as pool:
436
+ results = await asyncio.get_event_loop().run_in_executor(pool, run_executor)
437
+
438
+ if isinstance(results, dict) and "error" in results:
439
+ return JSONResponse(
440
+ content={"status": "error", "message": results["error"]},
441
+ status_code=500
442
+ )
443
+
444
+ logging.info(f"Action results: {results}")
445
+
446
+ return JSONResponse(
447
+ content={"status": "success", "results": results},
448
+ status_code=200
449
+ )
450
+ except Exception as e:
451
+ logging.error("Error processing /exec_computer_tool:", exc_info=True)
452
+ return JSONResponse(
453
+ content={"status": "error", "message": f"Internal server error: {str(e)}"},
454
+ status_code=500
455
+ )
456
+
398
457
  def process_input():
399
458
  global shared_state
400
459
  logging.info("process_input thread started.")
@@ -115,20 +115,21 @@ class TeachmodeExecutor:
115
115
  print(f"Error parsing action output: {e}")
116
116
  return None
117
117
 
118
- def _parse_actor_output(self, output_text: str | dict) -> Union[List[Dict[str, Any]], None]:
118
+ def _parse_actor_output(self, action_item: str | dict) -> Union[List[Dict[str, Any]], None]:
119
119
  try:
120
120
  # refine key: value pairs, mapping to the Anthropic's format
121
121
  refined_output = []
122
122
 
123
- action_item = output_text
124
-
123
+ if type(action_item) == str:
124
+ action_item = ast.literal_eval(action_item)
125
+
125
126
  print("[_parse_actor_output] Action Item:", action_item)
126
127
 
127
128
  # sometime showui returns lower case action names
128
129
  action_item["action"] = action_item["action"].upper()
129
130
 
130
131
  if action_item["action"] not in self.supported_action_type:
131
- raise ValueError(f"Action {action_item['action']} not supported. Check the output from ShowUI: {output_text}")
132
+ raise ValueError(f"Action {action_item['action']} not supported. Check the output from Actor: {action_item}")
132
133
  # continue
133
134
 
134
135
  elif action_item["action"] == "CLICK": # 1. click -> mouse_move + left_click
@@ -314,44 +315,3 @@ class TeachmodeExecutor:
314
315
  return bbox
315
316
 
316
317
 
317
-
318
- # def _make_api_tool_result(
319
- # result: ToolResult, tool_use_id: str
320
- # ) -> BetaToolResultBlockParam:
321
- # """Convert an agent ToolResult to an API ToolResultBlockParam."""
322
- # tool_result_content: list[BetaTextBlockParam | BetaImageBlockParam] | str = []
323
- # is_error = False
324
- # if result.error:
325
- # is_error = True
326
- # tool_result_content = _maybe_prepend_system_tool_result(result, result.error)
327
- # else:
328
- # if result.output:
329
- # tool_result_content.append(
330
- # {
331
- # "type": "text",
332
- # "text": _maybe_prepend_system_tool_result(result, result.output),
333
- # }
334
- # )
335
- # if result.base64_image:
336
- # tool_result_content.append(
337
- # {
338
- # "type": "image",
339
- # "source": {
340
- # "type": "base64",
341
- # "media_type": "image/png",
342
- # "data": result.base64_image,
343
- # },
344
- # }
345
- # )
346
- # return {
347
- # "type": "tool_result",
348
- # "content": tool_result_content,
349
- # "tool_use_id": tool_use_id,
350
- # "is_error": is_error,
351
- # }
352
-
353
-
354
- # def _maybe_prepend_system_tool_result(result: ToolResult, result_text: str):
355
- # if result.system:
356
- # result_text = f"<system>{result.system}</system>\n{result_text}"
357
- # return result_text
@@ -384,7 +384,7 @@ class ComputerTool(BaseAnthropicTool):
384
384
  show_click(x, y)
385
385
  time.sleep(0.25)
386
386
  self.marbot_auto_gui.click(x=x, y=y)
387
- self.marbot_auto_gui.click(x=x, y=y)
387
+ # self.marbot_auto_gui.click(x=x, y=y)
388
388
  return ToolResult(output=f"Left click", action_base_type="click")
389
389
 
390
390
  elif action == "mouse_move_windll":
@@ -23,6 +23,7 @@ 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
25
  import json # Needed for status reporting
26
+ import shutil # Added for shutil.which
26
27
 
27
28
  # --- Configuration ---
28
29
  _SERVICE_NAME = "OOTBGuardService"
@@ -143,7 +144,6 @@ class GuardService(win32serviceutil.ServiceFramework):
143
144
  _svc_name_ = _SERVICE_NAME
144
145
  _svc_display_name_ = _SERVICE_DISPLAY_NAME
145
146
  _svc_description_ = _SERVICE_DESCRIPTION
146
- _task_name_prefix = "OOTB_UserLogon_" # Class attribute for task prefix
147
147
 
148
148
  # --- Instance Logging Methods ---
149
149
  def log_info(self, msg):
@@ -708,19 +708,9 @@ class GuardService(win32serviceutil.ServiceFramework):
708
708
 
709
709
 
710
710
  try:
711
- # 1. Ensure scheduled task exists (still useful fallback/persistence)
712
- try:
713
- task_created = self.create_or_update_logon_task(user)
714
- task_created_status = "task_success" if task_created else "task_failed"
715
- except Exception as task_err:
716
- self.log_error(f"Internal trigger: Exception creating/updating task for {user}: {task_err}", exc_info=True)
717
- task_created_status = "task_exception"
718
- # Don't necessarily fail the whole operation yet
719
-
720
711
  # 2. Check if user is active
721
712
  active_sessions = {} # Re-check active sessions specifically for this user
722
713
  session_id = None
723
- token = None
724
714
  is_active = False
725
715
  try:
726
716
  sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
@@ -741,7 +731,7 @@ class GuardService(win32serviceutil.ServiceFramework):
741
731
  if not is_active:
742
732
  self.log_info(f"Internal trigger: User '{user}' is not active. Skipping immediate start.")
743
733
  immediate_start_status = "start_skipped_inactive"
744
- final_status = task_created_status # Status depends only on task creation
734
+ final_status = "skipped_inactive" # Simplified status
745
735
  return final_status # Exit early if inactive
746
736
 
747
737
  # 3. Check if already running for this active user
@@ -758,80 +748,100 @@ class GuardService(win32serviceutil.ServiceFramework):
758
748
  self.log_error(f"Internal trigger: Error checking existing processes for {user}: {e}")
759
749
  # Continue and attempt start despite error?
760
750
 
761
- # 4. Attempt immediate start (User is active and not running)
762
- immediate_start_status = "start_attempted"
763
- self.log_info(f"Internal trigger: User '{user}' is active and not running. Attempting immediate start via PowerShell elevation...")
764
-
751
+ # 4. Attempt immediate start using CreateProcessAsUser to run helper script
752
+ immediate_start_status = "start_attempted_helper_script"
753
+ self.log_info(f"Internal trigger: User '{user}' is active and not running. Attempting start via helper script...")
754
+
765
755
  if not self.target_executable_path:
766
756
  self.log_error("Internal trigger: Cannot start process - target executable path not found.")
767
757
  final_status = "failed_exe_not_found"
768
- return final_status # Exit early if executable missing
758
+ return final_status
769
759
 
770
760
  try:
771
- token = win32ts.WTSQueryUserToken(session_id)
772
- env = win32profile.CreateEnvironmentBlock(token, False)
773
- startup = win32process.STARTUPINFO()
774
- # Use CREATE_NEW_CONSOLE to make the PS window visible and potentially the UAC prompt
775
- creation_flags = win32con.CREATE_NEW_CONSOLE
776
-
777
- # Command to run PowerShell, which then elevates the target executable
761
+ # Find helper script path
762
+ helper_script_path = self._find_helper_script("launch_ootb_elevated.ps1")
763
+ if not helper_script_path:
764
+ self.log_error("Internal trigger: Cannot start process - helper script not found.")
765
+ final_status = "failed_helper_script_not_found"
766
+ return final_status
767
+
768
+ # Get PowerShell executable path
769
+ powershell_executable = self._find_powershell_executable()
770
+ if not powershell_executable:
771
+ self.log_error("Internal trigger: Cannot start process - powershell.exe not found.")
772
+ final_status = "failed_powershell_not_found"
773
+ return final_status
774
+
775
+ # Prepare arguments for the helper script (paths need quoting if they contain spaces)
778
776
  target_exe_unquoted = self.target_executable_path.strip('"')
779
- target_exe_for_ps = target_exe_unquoted.replace("'", "''") # Escape single quotes for PS
780
- target_exe_dir_for_ps = os.path.dirname(target_exe_unquoted).replace("'", "''")
781
-
782
- # Build the second argument for cmd.exe /K, as used in the successful test command
783
- # Format: "<target_exe>" --port <port> --target_user ''<user>''
784
- cmd_k_second_arg_content = f'""{target_exe_for_ps}"" --port {calculated_port} --target_user ''{user}'''
785
- # Escape single quotes within this second arg for the outer PowerShell command string
786
- cmd_k_second_arg_for_ps_command = cmd_k_second_arg_content.replace("'", "''")
787
-
788
- # Build the full Start-Process command string matching the working test command
789
- start_process_cmd = (
790
- f"Start-Process -FilePath cmd.exe "
791
- f"-ArgumentList @('/K', '{cmd_k_second_arg_for_ps_command}') "
792
- f"-WorkingDirectory '{target_exe_dir_for_ps}' "
793
- f"-Verb RunAs"
777
+ target_exe_dir = os.path.dirname(target_exe_unquoted) # Use directory of unquoted path
778
+
779
+ # Quote paths/args for command line - IMPORTANT: Use double quotes for PowerShell args
780
+ helper_script_arg = f'"{helper_script_path}"' if " " in helper_script_path else helper_script_path
781
+ target_exe_arg = f'"{target_exe_unquoted}"' if " " in target_exe_unquoted else target_exe_unquoted
782
+ working_dir_arg = f'"{target_exe_dir}"' if " " in target_exe_dir else target_exe_dir
783
+ # Username might need quoting if it contains special characters, double quote should be safe
784
+ target_user_arg = f'"{user}"'
785
+
786
+ # Construct the command line to execute powershell with the helper script and args
787
+ lpCommandLine = (
788
+ f'"{powershell_executable}" -NoProfile -ExecutionPolicy Bypass -NoExit '
789
+ f'-File {helper_script_arg} '
790
+ f'-TargetExePath {target_exe_arg} '
791
+ f'-Port {calculated_port} '
792
+ f'-TargetUser {target_user_arg} '
793
+ f'-WorkingDirectory {working_dir_arg}'
794
794
  )
795
795
 
796
- # Launch powershell.exe directly, tell it to run the Start-Process command, and keep window open
797
- # Use outer single quotes for -Command argument value to simplify inner quoting
798
- # No need to escape inner double quotes when using outer single quotes
799
- # Escape any literal single quotes within start_process_cmd itself (shouldn't be needed here but good practice)
800
- start_process_cmd_escaped_for_single_quotes = start_process_cmd.replace("'", "''")
801
- lpCommandLine = f'powershell.exe -NoProfile -ExecutionPolicy Bypass -NoExit -Command \'{start_process_cmd_escaped_for_single_quotes}\''
802
-
803
- lpApplicationName = None # Must be None if using lpCommandLine
804
- cwd = os.path.dirname(target_exe_unquoted) if os.path.dirname(target_exe_unquoted) else None
805
-
806
- # Log details before call
807
- self.log_info(f"Internal trigger: Launching PowerShell directly for elevation:")
796
+ # Use CreateProcessAsUser to launch this command in the user's session
797
+ token = win32ts.WTSQueryUserToken(session_id)
798
+ env = win32profile.CreateEnvironmentBlock(token, False)
799
+ startup = win32process.STARTUPINFO()
800
+ # Ensure the window is visible on the user's desktop
801
+ startup.lpDesktop = "winsta0\\default"
802
+ creation_flags = win32con.CREATE_NEW_CONSOLE | win32con.NORMAL_PRIORITY_CLASS
803
+ lpApplicationName = None # Must be None when using lpCommandLine
804
+ # Working directory for powershell itself? Let it default or use script dir?
805
+ # Let's use the script's directory as CWD for powershell.exe
806
+ cwd = os.path.dirname(helper_script_path) # Use directory of unquoted path
807
+
808
+ self.log_info(f"Internal trigger: Launching helper script via CreateProcessAsUser:")
808
809
  self.log_info(f" lpCommandLine: {lpCommandLine}")
809
- self.log_info(f" lpCurrentDirectory: {cwd if cwd else 'Default'}")
810
-
810
+ self.log_info(f" lpCurrentDirectory: {cwd}")
811
+
812
+ # Execute the command in the user's session
811
813
  hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
812
814
  token, lpApplicationName, lpCommandLine, None, None, False,
813
815
  creation_flags, env, cwd, startup
814
816
  )
815
- # We get the PID of cmd.exe/powershell.exe here, not the final elevated process.
816
- # We can't easily track the elevated process PID across the UAC boundary.
817
- self.log_info(f"Internal trigger: CreateProcessAsUser call to initiate elevation succeeded for user '{user}' (Launcher PID: {dwPid}). UAC prompt expected if needed.")
817
+ self.log_info(f"Internal trigger: CreateProcessAsUser call for helper script succeeded (Launcher PID: {dwPid}). Helper script should now run and trigger elevation.")
818
818
  win32api.CloseHandle(hProcess)
819
819
  win32api.CloseHandle(hThread)
820
820
 
821
- # Assume success if the call didn't raise an exception.
822
- # We cannot easily verify if the *elevated* process actually started.
823
- immediate_start_status = "start_elevation_initiated"
824
- final_status = "success_elevation_initiated" # Report success based on initiating the elevation
825
-
826
- except Exception as proc_err:
827
- self.log_error(f"Internal trigger: Exception during CreateProcessAsUser for elevation attempt (user '{user}'): {proc_err}", exc_info=True)
828
- immediate_start_status = "start_failed_exception"
829
- final_status = "failed_start_exception"
821
+ # Assume success if CreateProcessAsUser succeeded.
822
+ # The actual success depends on the user interacting with UAC triggered by the helper.
823
+ immediate_start_status = "start_success_helper_launched"
824
+ final_status = "success_helper_launched"
825
+
826
+ except FileNotFoundError as fnf_err:
827
+ # This might now be powershell.exe OR the helper script
828
+ self.log_error(f"Internal trigger: File not found during helper script launch: {fnf_err}")
829
+ immediate_start_status = "start_failed_file_not_found"
830
+ final_status = "failed_file_not_found"
831
+ except Exception as launch_err:
832
+ self.log_error(f"Internal trigger: Exception during CreateProcessAsUser for helper script (user '{user}'): {launch_err}", exc_info=True)
833
+ immediate_start_status = "start_failed_launch_exception"
834
+ final_status = "failed_launch_exception"
830
835
  finally:
831
- if token: win32api.CloseHandle(token)
832
-
836
+ # Ensure token exists and is not None before attempting to close
837
+ if 'token' in locals() and token is not None:
838
+ try:
839
+ win32api.CloseHandle(token)
840
+ except: # Ignore errors closing handle
841
+ pass
842
+
833
843
  # Combine results (mostly determined by start attempt now)
834
- # Example: final_status = f"{task_created_status}_{immediate_start_status}"
844
+ # Status is now primarily determined by the helper script launch attempt
835
845
  return final_status
836
846
 
837
847
  except Exception as e:
@@ -839,133 +849,47 @@ class GuardService(win32serviceutil.ServiceFramework):
839
849
  return "failed_trigger_exception"
840
850
 
841
851
 
842
- def create_or_update_logon_task(self, username):
843
- """Creates/updates task to trigger the internal signal script on session connect."""
844
- if not self.signal_script_path:
845
- self.log_error(f"Cannot create task for {username}: Signal script path is not set.")
846
- return False
847
- if not sys.executable:
848
- self.log_error(f"Cannot create task for {username}: sys.executable is not found.")
849
- return False
850
-
851
- # Use the python executable that the service itself is running under
852
- python_exe = sys.executable
853
- if ' ' in python_exe and not python_exe.startswith('"'):
854
- python_exe = f'"{python_exe}"'
855
-
856
- task_name = f"OOTB_UserConnect_{username}"
857
- # Action: Run python.exe with the signal script and username argument
858
- action_executable = python_exe
859
- # Ensure script path is quoted if needed
860
- script_arg = self.signal_script_path # Should be quoted already by _find_signal_script
861
- # Username might need quoting if it contains spaces, though unlikely
862
- user_arg = username # Keep simple for now
863
- action_arguments = f'{script_arg} "{user_arg}"' # Pass username as quoted arg
864
- safe_action_executable = action_executable.replace("'", "''") # Escape for PS
865
- safe_action_arguments = action_arguments.replace("'", "''") # Escape for PS
866
-
867
- # Working directory for the script (likely its own directory)
868
- try:
869
- script_dir = os.path.dirname(self.signal_script_path.strip('"'))
870
- if not script_dir: script_dir = "."
871
- safe_working_directory = script_dir.replace("'", "''")
872
- working_directory_setting = f"$action.WorkingDirectory = '{safe_working_directory}'"
873
- except Exception as e:
874
- self.log_error(f"Error determining working directory for signal script task: {e}. WD will not be set.")
875
- working_directory_setting = "# Could not set WorkingDirectory"
876
-
877
- # PowerShell command construction
878
- ps_command = f"""
879
- $taskName = "{task_name}"
880
- $principal = New-ScheduledTaskPrincipal -UserId "{username}" -LogonType Interactive
881
-
882
- # Action: Run python signal script
883
- $action = New-ScheduledTaskAction -Execute '{safe_action_executable}' -Argument '{safe_action_arguments}'
884
- {working_directory_setting}
885
-
886
- # Trigger: On session connect (Event ID 21)
887
- $logName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
888
- $source = 'Microsoft-Windows-TerminalServices-LocalSessionManager'
889
- $eventIDs = @(21, 25)
890
- $trigger = New-ScheduledTaskTrigger -Event -LogName $logName -Source $source -EventId $eventIDs[0]
891
- # Optional Delay: -Delay 'PT15S'
892
- # $trigger.Delay = 'PT15S'
893
-
894
- $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Days 9999) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
895
- $description = "Triggers OOTB Guard Service for user {username} upon session connect via internal signal." # Updated description
896
-
897
- # Unregister existing task first (force)
898
- Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
899
-
900
- # Register the new task
901
- Register-ScheduledTask -TaskName $taskName -Principal $principal -Action $action -Trigger $trigger -Settings $settings -Description $description -Force
902
- """
903
- self.log_info(f"Attempting to create/update task '{task_name}' for user '{username}' to run signal script.")
904
- try:
905
- success = self.run_powershell_command(ps_command)
906
- if success:
907
- self.log_info(f"Successfully ran PowerShell command to create/update task '{task_name}'.")
908
- return True
909
- else:
910
- self.log_error(f"PowerShell command failed to create/update task '{task_name}'. See previous logs.")
911
- return False
912
- except Exception as e:
913
- self.log_error(f"Failed to create/update scheduled task '{task_name}' for user '{username}': {e}", exc_info=True)
914
- return False
915
-
916
- def run_powershell_command(self, command, log_output=True):
917
- """Executes a PowerShell command and handles output/errors. Returns True on success."""
918
- self.log_info(f"Executing PowerShell: {command}")
919
- try:
920
- result = subprocess.run(
921
- ["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
922
- capture_output=True, text=True, check=True, encoding='utf-8', errors='ignore'
923
- )
924
- if log_output and result.stdout:
925
- self.log_info(f"PowerShell STDOUT:\n{result.stdout.strip()}")
926
- if log_output and result.stderr:
927
- # Log stderr as info, as some commands write status here (like unregister task not found)
928
- self.log_info(f"PowerShell STDERR:\n{result.stderr.strip()}")
929
- return True
930
- except FileNotFoundError:
931
- self.log_error("'powershell.exe' not found. Cannot manage scheduled tasks.")
932
- return False
933
- except subprocess.CalledProcessError as e:
934
- # Log error but still return False, handled by caller
935
- self.log_error(f"PowerShell command failed (Exit Code {e.returncode}):")
936
- self.log_error(f" Command: {e.cmd}")
937
- if e.stdout: self.log_error(f" STDOUT: {e.stdout.strip()}")
938
- if e.stderr: self.log_error(f" STDERR: {e.stderr.strip()}")
939
- return False
940
- except Exception as e:
941
- self.log_error(f"Unexpected error running PowerShell: {e}", exc_info=True)
942
- return False
943
-
944
- def remove_logon_task(self, username):
945
- """Removes the logon scheduled task for a user."""
946
- task_name = f"{self._task_name_prefix}{username}"
947
- safe_task_name = task_name.replace("'", "''")
948
- command = f"Unregister-ScheduledTask -TaskName '{safe_task_name}' -Confirm:$false -ErrorAction SilentlyContinue"
949
- self.run_powershell_command(command, log_output=False)
950
- self.log_info(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
951
- return True
952
-
953
852
  def _find_signal_script(self):
954
853
  """Finds the signal_connection.py script relative to this service file."""
854
+ return self._find_helper_script("signal_connection.py") # Reuse helper finding logic
855
+
856
+ def _find_helper_script(self, script_name):
857
+ """Finds a helper script relative to this service file."""
955
858
  try:
859
+ # Use __file__ which should be reliable when run as a service via pythonservice.exe
956
860
  base_dir = os.path.dirname(os.path.abspath(__file__))
957
- script_path = os.path.join(base_dir, "signal_connection.py")
861
+ script_path = os.path.join(base_dir, script_name)
958
862
  if os.path.exists(script_path):
959
- self.log_info(f"Found signal script at: {script_path}")
960
- # Quote if needed?
961
- if " " in script_path and not script_path.startswith('"'):
962
- return f'"{script_path}"'
863
+ self.log_info(f"Found helper script '{script_name}' at: {script_path}")
864
+ # Return unquoted path for potential quoting later
963
865
  return script_path
964
866
  else:
965
- self.log_error(f"Signal script signal_connection.py not found near {base_dir}")
867
+ self.log_error(f"Helper script '{script_name}' not found near {base_dir}")
966
868
  return None
967
869
  except Exception as e:
968
- self.log_error(f"Error finding signal script: {e}")
870
+ self.log_error(f"Error finding helper script '{script_name}': {e}")
871
+ return None
872
+
873
+ def _find_powershell_executable(self):
874
+ """Finds powershell.exe, preferring the system path."""
875
+ try:
876
+ # Check System32 first
877
+ system32_path = os.path.join(os.environ.get('SystemRoot', 'C:\\Windows'), 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
878
+ if os.path.exists(system32_path):
879
+ self.log_info(f"Found powershell.exe at: {system32_path}")
880
+ return system32_path
881
+ else:
882
+ # Fallback to checking PATH using shutil.which (requires Python 3.3+)
883
+ # Make sure to import shutil at the top of the file if not already present
884
+ powershell_path = shutil.which("powershell.exe")
885
+ if powershell_path:
886
+ self.log_info(f"Found powershell.exe via PATH: {powershell_path}")
887
+ return powershell_path
888
+ else:
889
+ self.log_error("powershell.exe not found in System32 or PATH.")
890
+ return None
891
+ except Exception as e:
892
+ self.log_error(f"Error finding powershell.exe: {e}")
969
893
  return None
970
894
 
971
895
  # --- Main Execution Block ---
@@ -0,0 +1,63 @@
1
+ # launch_ootb_elevated.ps1
2
+ param(
3
+ [Parameter(Mandatory=$true)]
4
+ [string]$TargetExePath,
5
+
6
+ [Parameter(Mandatory=$true)]
7
+ [string]$Port,
8
+
9
+ [Parameter(Mandatory=$true)]
10
+ [string]$TargetUser,
11
+
12
+ [Parameter(Mandatory=$true)]
13
+ [string]$WorkingDirectory
14
+ )
15
+
16
+ try {
17
+ Write-Host "--- OOTB Elevation Helper ---"
18
+ Write-Host "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
19
+ Write-Host "Received parameters:"
20
+ Write-Host " Target Exe Path : $TargetExePath"
21
+ Write-Host " Port : $Port"
22
+ Write-Host " Target User : $TargetUser"
23
+ Write-Host " Working Dir : $WorkingDirectory"
24
+ Write-Host ""
25
+
26
+ # Validate paths
27
+ if (-not (Test-Path -Path $TargetExePath -PathType Leaf)) {
28
+ throw "Target executable not found at '$TargetExePath'"
29
+ }
30
+ if (-not (Test-Path -Path $WorkingDirectory -PathType Container)) {
31
+ throw "Working directory not found at '$WorkingDirectory'"
32
+ }
33
+
34
+ # Construct the argument list string for the target executable
35
+ # Ensure target user is single-quoted for the argument parser
36
+ $argumentList = "--port $Port --target_user '$TargetUser'"
37
+
38
+ Write-Host "Constructed ArgumentList for Target Exe: $argumentList"
39
+ Write-Host "Executing elevated process..."
40
+ Write-Host "Command: Start-Process -FilePath `$TargetExePath` -ArgumentList `$argumentList` -WorkingDirectory `$WorkingDirectory` -Verb RunAs"
41
+ Write-Host "--- Waiting for UAC prompt if necessary ---"
42
+
43
+ # Execute the command to launch the target exe elevated directly
44
+ Start-Process -FilePath $TargetExePath -ArgumentList $argumentList -WorkingDirectory $WorkingDirectory -Verb RunAs
45
+
46
+ Write-Host "--- OOTB Elevation Helper: Start-Process command executed. ---"
47
+ # The calling powershell window (started by CreateProcessAsUser with -NoExit) will remain open.
48
+
49
+ } catch {
50
+ Write-Error "--- OOTB Elevation Helper Error ---"
51
+ Write-Error "Error launching elevated process: $($_.Exception.Message)"
52
+ Write-Error "Script Parameters:"
53
+ Write-Error " Target Exe Path : $TargetExePath"
54
+ Write-Error " Port : $Port"
55
+ Write-Error " Target User : $TargetUser"
56
+ Write-Error " Working Dir : $WorkingDirectory"
57
+ Write-Host "Press Enter to exit..."
58
+ Read-Host # Keep window open on error
59
+ Exit 1
60
+ }
61
+
62
+ # Exit gracefully if successful
63
+ Exit 0
@@ -6,11 +6,14 @@ import subprocess
6
6
  import ctypes
7
7
  import platform
8
8
  import time
9
+ import json
10
+ import shutil
9
11
 
10
12
  # Constants need to match guard_service.py
11
13
  _SERVICE_NAME = "OOTBGuardService"
12
14
  _SERVICE_DISPLAY_NAME = "OOTB Guard Service"
13
15
  _TASK_NAME_PREFIX = "OOTB_UserLogon_" # Must match guard_service.py
16
+ _SHORTCUT_NAME = "OOTB AutoStart Signal.lnk" # Name for the startup shortcut
14
17
 
15
18
  def is_admin():
16
19
  """Check if the script is running with administrative privileges."""
@@ -51,142 +54,298 @@ def get_service_module_path():
51
54
  except Exception as fallback_e:
52
55
  raise FileNotFoundError(f"Could not find guard_service.py using inspect ({e}) or sys.prefix ({fallback_e}). Check installation.")
53
56
 
54
-
55
- def run_service_command(command_args, check_errors=True):
56
- """Runs the guard_service.py script with specified command-line args."""
57
- if not is_admin():
58
- print("Error: Administrative privileges are required to manage the service.", file=sys.stderr)
59
- print("Please run this command from an Administrator Command Prompt or PowerShell.", file=sys.stderr)
60
- return False
61
-
57
+ def _run_command(cmd_list, check_errors=True, capture_output=False, verbose=True):
58
+ """Helper to run an external command (like sc.exe)."""
59
+ if verbose:
60
+ # Safely join for printing, handle potential non-string elements just in case
61
+ print(f"Executing command: {' '.join(map(str, cmd_list))}")
62
62
  try:
63
- python_exe = sys.executable # Use the same python that's running this script
64
- service_script = get_service_module_path()
63
+ result = subprocess.run(
64
+ cmd_list,
65
+ capture_output=capture_output,
66
+ text=True,
67
+ check=check_errors,
68
+ encoding='utf-8',
69
+ errors='ignore'
70
+ )
71
+ if capture_output and verbose:
72
+ if result.stdout: print(f" CMD STDOUT: {result.stdout.strip()}")
73
+ if result.stderr: print(f" CMD STDERR: {result.stderr.strip()}")
74
+ return result if capture_output else (result.returncode == 0)
65
75
  except FileNotFoundError as e:
66
- print(f"Error: {e}", file=sys.stderr)
67
- return False
68
-
69
- # Quote paths if they contain spaces
70
- if " " in python_exe and not python_exe.startswith('"'):
71
- python_exe = f'"{python_exe}"'
72
- if " " in service_script and not service_script.startswith('"'):
73
- service_script = f'"{service_script}"'
76
+ print(f"Error: Command not found during execution: {cmd_list[0]}", file=sys.stderr)
77
+ print(f" Details: {e}", file=sys.stderr)
78
+ return None if capture_output else False
79
+ except subprocess.CalledProcessError as e:
80
+ # Don't print error if check_errors was False and it failed
81
+ if check_errors and verbose:
82
+ print(f"Error executing command {' '.join(map(str, cmd_list))} (Exit Code {e.returncode}).", file=sys.stderr)
83
+ if e.stdout: print(f"Subprocess STDOUT:", file=sys.stderr); print(e.stdout, file=sys.stderr)
84
+ if e.stderr: print(f"Subprocess STDERR:", file=sys.stderr); print(e.stderr, file=sys.stderr)
85
+ return None if capture_output else False
86
+ except Exception as e:
87
+ if verbose:
88
+ print(f"An unexpected error occurred running command: {e}", file=sys.stderr)
89
+ return None if capture_output else False
74
90
 
75
- # Construct command using list to avoid shell quoting issues
76
- cmd = [sys.executable, get_service_module_path()] + command_args
77
- print(f"Executing command: {' '.join(cmd)}")
91
+ def _get_startup_folder():
92
+ """Gets the current user's Startup folder path."""
93
+ try:
94
+ # Use CSIDL value for Startup folder
95
+ # Requires pywin32
96
+ import win32com.client
97
+ shell = win32com.client.Dispatch("WScript.Shell")
98
+ # SHGetSpecialFolderPath requires integer CSIDL, Startup=7
99
+ # Using shell.SpecialFolders is usually easier
100
+ startup_path = shell.SpecialFolders("Startup")
101
+ if startup_path and os.path.isdir(startup_path):
102
+ print(f"Found Startup folder: {startup_path}")
103
+ return startup_path
104
+ else:
105
+ print("Error: Could not resolve Startup folder path via WScript.Shell.", file=sys.stderr)
106
+ return None
107
+ except ImportError:
108
+ print("Error: pywin32com is required to find Startup folder. Cannot manage startup shortcut.", file=sys.stderr)
109
+ return None
110
+ except Exception as e:
111
+ print(f"Error finding Startup folder: {e}", file=sys.stderr)
112
+ return None
78
113
 
114
+ def _find_pythonw_executable():
115
+ """Finds pythonw.exe in the same directory as sys.executable."""
79
116
  try:
80
- # Run the command. Use shell=False with list of args.
81
- # Capture output to check for specific errors if needed, but print it too.
82
- result = subprocess.run(cmd, capture_output=True, text=True, check=check_errors, encoding='utf-8')
83
- if result.stdout:
84
- print("Command STDOUT:")
85
- print(result.stdout)
86
- if result.stderr:
87
- print("Command STDERR:")
88
- print(result.stderr)
89
- print(f"Command {' '.join(command_args)} executed successfully.")
90
- return True
91
- except FileNotFoundError as e:
92
- print(f"Error: Could not find Python executable or service script during execution.", file=sys.stderr)
93
- print(f" Details: {e}", file=sys.stderr)
94
- return False
95
- except subprocess.CalledProcessError as e:
96
- print(f"Error executing service command {' '.join(command_args)} (Exit Code {e.returncode}).", file=sys.stderr)
97
- if e.stdout:
98
- print("Subprocess STDOUT:")
99
- print(e.stdout)
100
- if e.stderr:
101
- print("Subprocess STDERR:")
102
- print(e.stderr)
103
- return False
117
+ python_dir = os.path.dirname(sys.executable)
118
+ pythonw_path = os.path.join(python_dir, "pythonw.exe")
119
+ if os.path.exists(pythonw_path):
120
+ return pythonw_path
121
+ else:
122
+ print(f"Warning: pythonw.exe not found next to sys.executable ({sys.executable}). Console window might flash on login.", file=sys.stderr)
123
+ # Fallback to python.exe if pythonw not found
124
+ return sys.executable
104
125
  except Exception as e:
105
- print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
126
+ print(f"Error finding pythonw.exe: {e}. Using sys.executable as fallback.", file=sys.stderr)
127
+ return sys.executable
128
+
129
+ def _create_startup_shortcut():
130
+ """Creates a shortcut in the Startup folder to run signal_connection.py."""
131
+ if not is_admin(): # Should already be checked, but double-check
132
+ print("Admin privileges needed to potentially write shortcut.")
133
+ return False
134
+
135
+ startup_dir = _get_startup_folder()
136
+ if not startup_dir:
106
137
  return False
107
138
 
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}")
139
+ shortcut_path = os.path.join(startup_dir, _SHORTCUT_NAME)
140
+ python_launcher = _find_pythonw_executable()
141
+ signal_script = None
113
142
  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
143
+ # Find signal_connection.py relative to this script (service_manager.py)
144
+ script_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
145
+ signal_script = os.path.join(script_dir, "signal_connection.py")
146
+ if not os.path.exists(signal_script):
147
+ print(f"Error: signal_connection.py not found in {script_dir}", file=sys.stderr)
148
+ return False
122
149
  except Exception as e:
123
- print(f"Warning: PowerShell cleanup command failed: {e}", file=sys.stderr)
124
- return False # Indicate potential issue
150
+ print(f"Error finding signal_connection.py: {e}", file=sys.stderr)
151
+ return False
152
+
153
+ # Target command for the shortcut
154
+ target_cmd = f'"{python_launcher}"' # Quote launcher path
155
+ # Quote script path
156
+ target_cmd += f' "{signal_script}"'
157
+ # Username argument - %USERNAME% will be expanded by shell when shortcut runs
158
+ target_cmd += ' %USERNAME%'
159
+
160
+ print(f"Creating Startup shortcut:")
161
+ print(f" Shortcut : {shortcut_path}")
162
+ print(f" Target : {target_cmd}")
163
+
164
+ # Use PowerShell to create the shortcut
165
+ # Escape paths and arguments for the PowerShell command string
166
+ ps_shortcut_path = shortcut_path.replace("'", "''")
167
+ ps_target_cmd = target_cmd.replace("'", "''")
168
+ ps_working_dir = os.path.dirname(signal_script).replace("'", "''") # Use script's dir as working dir
169
+ ps_icon_location = python_launcher.replace("'", "''") # Use python icon
170
+
171
+ ps_command = f"""
172
+ $ws = New-Object -ComObject WScript.Shell
173
+ $s = $ws.CreateShortcut('{ps_shortcut_path}')
174
+ $s.TargetPath = '{ps_target_cmd.split()[0]}' # Executable part
175
+ $s.Arguments = '{ps_target_cmd.split(' ', 1)[1] if ' ' in ps_target_cmd else ''}' # Arguments part
176
+ $s.WorkingDirectory = '{ps_working_dir}'
177
+ $s.IconLocation = '{ps_icon_location}'
178
+ $s.WindowStyle = 7 # Minimized
179
+ $s.Description = 'Triggers OOTB Guard Service connection check on login'
180
+ $s.Save()
181
+ Write-Host 'Shortcut created successfully.'
182
+ """
183
+
184
+ return _run_command([sys.executable, "-NoProfile", "-NonInteractive", "-Command", ps_command])
185
+
186
+ def _remove_startup_shortcut():
187
+ """Removes the OOTB startup shortcut if it exists."""
188
+ if not is_admin(): return False # Need admin to potentially delete
189
+ startup_dir = _get_startup_folder()
190
+ if not startup_dir:
191
+ return False
192
+
193
+ shortcut_path = os.path.join(startup_dir, _SHORTCUT_NAME)
194
+ print(f"Attempting to remove Startup shortcut: {shortcut_path}")
195
+ if os.path.exists(shortcut_path):
196
+ try:
197
+ os.remove(shortcut_path)
198
+ print("Shortcut removed successfully.")
199
+ return True
200
+ except OSError as e:
201
+ print(f"Error removing shortcut: {e}", file=sys.stderr)
202
+ # Fallback attempt with PowerShell just in case of permission issues
203
+ ps_shortcut_path = shortcut_path.replace("'", "''")
204
+ ps_command = f"Remove-Item -Path '{ps_shortcut_path}' -Force -ErrorAction SilentlyContinue"
205
+ return _run_command([sys.executable, "-NoProfile", "-NonInteractive", "-Command", ps_command], verbose=False)
206
+ except Exception as e:
207
+ print(f"Unexpected error removing shortcut: {e}", file=sys.stderr)
208
+ return False
209
+ else:
210
+ print("Shortcut not found, no removal needed.")
211
+ return True
125
212
 
126
213
  def _cleanup_scheduled_tasks():
127
- """Removes all OOTB user logon scheduled tasks."""
128
- print("Attempting to remove any existing OOTB user logon scheduled tasks...")
214
+ """Removes all OOTB user logon scheduled tasks (legacy cleanup)."""
215
+ print("Attempting legacy cleanup of OOTB user logon scheduled tasks...")
129
216
  # Use -like operator and wildcard
130
- # Use try-catch within PowerShell for robustness
217
+ # Need _TASK_NAME_PREFIX defined at module level
131
218
  command = f"""
132
219
  $tasks = Get-ScheduledTask | Where-Object {{ $_.TaskName -like '{_TASK_NAME_PREFIX}*' }}
133
220
  if ($tasks) {{
134
- Write-Host "Found $($tasks.Count) OOTB logon tasks to remove."
221
+ Write-Host "Found $($tasks.Count) legacy OOTB logon tasks to remove."
135
222
  $tasks | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
136
- Write-Host "OOTB logon task removal attempted."
223
+ Write-Host "Legacy OOTB logon task removal attempted."
137
224
  }} else {{
138
- Write-Host "No OOTB logon tasks found to remove."
225
+ Write-Host "No legacy OOTB logon tasks found to remove."
139
226
  }}
140
227
  """
141
- _run_powershell_cleanup_command(command)
142
- # --- End cleanup helpers ---
228
+ # Use the generic _run_command helper, specifying powershell.exe
229
+ _run_command(['powershell.exe', '-NoProfile', '-NonInteractive', '-Command', command], check_errors=False, verbose=False)
143
230
 
144
231
  def install_and_start():
145
232
  """Installs and starts the Guard Service."""
146
233
  print(f"Attempting to install service: '{_SERVICE_NAME}' ('{_SERVICE_DISPLAY_NAME}')")
147
- # Call 'install' command first.
148
- # We pass check_errors=True to stop if installation fails fundamentally.
149
- install_success = run_service_command(['--startup', 'auto', 'install'], check_errors=True)
234
+ # Step 1: Run the Python script to register the service class with SCM
235
+ install_success = False
236
+ try:
237
+ python_exe = sys.executable
238
+ service_script = get_service_module_path()
239
+ # Quote paths
240
+ python_exe_quoted = f'"{python_exe}"' if " " in python_exe else python_exe
241
+ service_script_quoted = f'"{service_script}"' if " " in service_script else service_script
242
+ # Use list for subprocess
243
+ install_cmd = [sys.executable, service_script, '--startup', 'auto', 'install']
244
+ print(f"Executing registration command: {' '.join(install_cmd)}")
245
+ # We need to check output/return code carefully
246
+ result = subprocess.run(install_cmd, capture_output=True, text=True, check=False, encoding='utf-8')
247
+ if result.stdout: print(f" Registration STDOUT: {result.stdout.strip()}")
248
+ if result.stderr: print(f" Registration STDERR: {result.stderr.strip()}")
249
+ # Check common success/already-installed messages or just return code 0?
250
+ # win32serviceutil often returns 0 even if service exists. Let's assume 0 is okay.
251
+ if result.returncode == 0:
252
+ print("Service registration command executed (might indicate success or already installed).")
253
+ install_success = True
254
+ else:
255
+ print(f"Service registration command failed (Exit Code {result.returncode}).", file=sys.stderr)
256
+ install_success = False
257
+
258
+ except FileNotFoundError as e:
259
+ print(f"Error finding Python or service script for registration: {e}", file=sys.stderr)
260
+ install_success = False
261
+ except Exception as e:
262
+ print(f"Unexpected error during service registration: {e}", file=sys.stderr)
263
+ install_success = False
150
264
 
151
265
  if install_success:
152
- # Note: Even if install_success is True, pywin32 might have printed internal errors
153
- # like 'service already installed'. We proceed to start anyway in that case.
154
- print(f"\nInstallation command finished. Attempting to start service: '{_SERVICE_NAME}' (waiting a few seconds first)")
155
- time.sleep(3) # Give SCM time to register the install/update
156
- start_success = run_service_command(['start'], check_errors=True)
157
-
158
- if start_success:
159
- # Similar caveat: start might succeed according to subprocess, but pywin32 could print internal errors.
160
- print(f"\nService '{_SERVICE_NAME}' install command executed and start command executed.")
161
- print(f"Please verify service status in 'services.msc' and check logs.")
266
+ print(f"\nRegistration command finished. Attempting to start service using 'sc start'...")
267
+ time.sleep(2) # Give SCM time
268
+ # Step 2: Use sc start
269
+ start_cmd = ['sc', 'start', _SERVICE_NAME]
270
+ start_success = _run_command(start_cmd, check_errors=False) # Don't fail script if start fails
271
+
272
+ # Optional: Query status after attempting start
273
+ time.sleep(3) # Wait a bit longer
274
+ query_cmd = ['sc', 'query', _SERVICE_NAME]
275
+ query_result = _run_command(query_cmd, capture_output=True, check_errors=False, verbose=False)
276
+ service_running = False
277
+ if query_result and query_result.stdout:
278
+ if "RUNNING" in query_result.stdout:
279
+ service_running = True
280
+ print(f"Service '{_SERVICE_NAME}' confirmed running.")
281
+ else:
282
+ print(f"Service '{_SERVICE_NAME}' status check returned:\n{query_result.stdout.strip()}")
162
283
  else:
163
- # This path is taken if run_service_command returned False (subprocess error occurred)
164
- print(f"\nService '{_SERVICE_NAME}' installed/updated but the 'start' command failed with an error.", file=sys.stderr)
284
+ print("Warning: Could not query service status after start attempt.")
285
+
286
+ if start_success and service_running:
287
+ print(f"\nService '{_SERVICE_NAME}' installed/updated and started successfully.")
288
+ # Step 3: Create startup shortcut only if service started
289
+ print("Creating startup shortcut...")
290
+ _create_startup_shortcut()
291
+ elif start_success and not service_running:
292
+ print(f"\nService '{_SERVICE_NAME}' installed/updated. 'sc start' command succeeded but service is not in RUNNING state.", file=sys.stderr)
293
+ print(" Check logs or try starting manually.", file=sys.stderr)
294
+ else: # start_success was False
295
+ print(f"\nService '{_SERVICE_NAME}' installed/updated but 'sc start' command failed.", file=sys.stderr)
165
296
  print(f" Check output above, service logs ('C:\ProgramData\OOTBGuardService\guard_post_mode.log'), or Windows Event Viewer.", file=sys.stderr)
166
297
  else:
167
- # This path is taken if the initial 'install' command failed critically (subprocess error)
168
- print(f"\nService '{_SERVICE_NAME}' installation failed critically. See errors above.", file=sys.stderr)
169
-
298
+ # This path is taken if the initial registration command failed critically
299
+ print(f"\nService '{_SERVICE_NAME}' registration failed critically. See errors above.", file=sys.stderr)
170
300
 
171
301
  def stop_and_remove():
172
- """Stops and removes the Guard Service and associated scheduled tasks."""
173
- print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
302
+ """Stops and removes the Guard Service and associated resources."""
303
+ print(f"Attempting to remove Startup shortcut...")
304
+ _remove_startup_shortcut()
305
+
306
+ print(f"\nAttempting to stop service using 'sc stop'...")
174
307
  # Run stop first, ignore errors (check_errors=False)
175
- run_service_command(['stop'], check_errors=False)
176
- time.sleep(2) # Give service time to stop
308
+ # run_service_command(['stop'], check_errors=False)
309
+ stop_cmd = ['sc', 'stop', _SERVICE_NAME]
310
+ _run_command(stop_cmd, check_errors=False)
311
+ time.sleep(3) # Give service time to stop
177
312
 
178
- print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
179
- remove_success = run_service_command(['remove']) # Check if removal command itself failed
313
+ # Optional: Check if stopped
314
+ query_cmd = ['sc', 'query', _SERVICE_NAME]
315
+ query_result = _run_command(query_cmd, capture_output=True, check_errors=False, verbose=False)
316
+ service_exists = True # Assume it exists unless query fails specifically
317
+ if query_result:
318
+ if query_result.stderr and ("failed" in query_result.stderr.lower() or "does not exist" in query_result.stderr.lower()):
319
+ service_exists = False
320
+ print("Service does not appear to exist before removal attempt.")
321
+ elif query_result.stdout and "STOPPED" in query_result.stdout:
322
+ print("Service confirmed stopped.")
323
+ elif query_result.stdout:
324
+ print(f"Warning: Service state after stop attempt: {query_result.stdout.strip()}")
325
+ else:
326
+ print("Warning: Could not query service state after stop attempt.")
327
+
328
+ print(f"\nAttempting to remove service using 'sc delete'...")
329
+ # remove_success = run_service_command(['remove']) # Check if removal command itself failed
330
+ delete_cmd = ['sc', 'delete', _SERVICE_NAME]
331
+ delete_success = _run_command(delete_cmd, check_errors=False) # Ignore error if not found
180
332
 
181
333
  # Always attempt task cleanup, even if service removal had issues
182
334
  _cleanup_scheduled_tasks()
183
335
 
184
- if remove_success:
185
- print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully. Associated logon tasks cleanup attempted.")
336
+ if delete_success:
337
+ # Check if it really doesn't exist now
338
+ time.sleep(1)
339
+ query_result_after = _run_command(query_cmd, capture_output=True, check_errors=False, verbose=False)
340
+ if query_result_after and query_result_after.stderr and ("failed" in query_result_after.stderr.lower() or "does not exist" in query_result_after.stderr.lower()):
341
+ print(f"\nService '{_SERVICE_NAME}' stopped (if running), removed successfully. Startup shortcut and logon tasks cleanup attempted.")
342
+ else:
343
+ print(f"\nService '{_SERVICE_NAME}' stop attempted. 'sc delete' command ran but service might still exist. Please check manually.", file=sys.stderr)
344
+ print(f" Startup shortcut and logon tasks cleanup attempted.", file=sys.stderr)
186
345
  else:
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)
346
+ # This might happen if sc delete failed for permission reasons etc.
347
+ print(f"\n'sc delete {_SERVICE_NAME}' command failed to execute properly.", file=sys.stderr)
348
+ print(f" Startup shortcut and logon tasks cleanup attempted.", file=sys.stderr)
190
349
  print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
191
350
 
192
351
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computer-use-ootb-internal
3
- Version: 0.0.169
3
+ Version: 0.0.170
4
4
  Summary: Computer Use OOTB
5
5
  Author-email: Siyuan Hu <siyuan.hu.sg@gmail.com>
6
6
  Requires-Python: >=3.11
@@ -1,17 +1,18 @@
1
1
  computer_use_ootb_internal/README.md,sha256=FxpW95lyub2iX73ZDfK6ML7SdEKg060H5I6Grub7li4,31
2
2
  computer_use_ootb_internal/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
3
- computer_use_ootb_internal/app_teachmode.py,sha256=R4kLoII4dkRkOMVWZUIhD3h39nQkrnbYShQuT8Q966I,24363
3
+ computer_use_ootb_internal/app_teachmode.py,sha256=842v79pjUvX1yck6n4uel45ysZTcsWXoskwcfEzSgw4,26866
4
4
  computer_use_ootb_internal/dependency_check.py,sha256=y8RMEP6RXQzTgU1MS_1piBLtz4J-Hfn9RjUZg59dyvo,1333
5
- computer_use_ootb_internal/guard_service.py,sha256=FZsztgGP4fG2U7aj8KhlbRE_gUoLuGnCW8393bMbpFs,54217
5
+ computer_use_ootb_internal/guard_service.py,sha256=B-eNrs97lhCBsPixspcRNZJHkKYuN4IbwUnpB0W8fsw,49381
6
+ computer_use_ootb_internal/launch_ootb_elevated.ps1,sha256=_WWVbxOU1Ow8MpOC9GUYV0B-DYEnLGaRCTkT9rNGuYY,2426
6
7
  computer_use_ootb_internal/requirements-lite.txt,sha256=5DAHomz4A_P2BmTIXNkNqkHbnIF0AyZ4_1XAlb1LaYs,290
7
8
  computer_use_ootb_internal/run_teachmode_ootb_args.py,sha256=ZYVxaOMxOOTZt2S-xmJcZqXmYqqIPl57tgheMO8OOBU,7910
8
- computer_use_ootb_internal/service_manager.py,sha256=_aFUtvSbovX73PY1QE01RkaqGV6swJaStrOwcg_Df94,9928
9
+ computer_use_ootb_internal/service_manager.py,sha256=1P8FbCUNIGg650xlayCQcVmSy6ELlU8ZeqqpD7Nmg_M,18364
9
10
  computer_use_ootb_internal/signal_connection.py,sha256=e6eGByhb2Gx8HHJHrHM3HvchobSUmba1y7-YRB6L59E,1821
10
11
  computer_use_ootb_internal/test_click_0425.py,sha256=uZtP6DsPVRFonKMYlbe9eHmPY6hH5y8xWgr2KxHC8E4,1808
11
12
  computer_use_ootb_internal/computer_use_demo/animation/click_animation.py,sha256=tP1gsayFy-CKk10UlrE9RlexwlHWiHQUe5Ogg4vQvSg,3234
12
13
  computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif,sha256=4LfwsfFQnREXrNRs32aJU2jO65JXianJoL_8q7-8elg,30966
13
14
  computer_use_ootb_internal/computer_use_demo/animation/test_animation.py,sha256=SOJz2yffXTkjuAHqk0IZLcMriR0KQYTo7W1b8wGyRGY,1222
14
- computer_use_ootb_internal/computer_use_demo/executor/teachmode_executor.py,sha256=5fbEin5SDpchuCe6lEtRPK9JbO4YRqqIsD6LijJTrD8,16692
15
+ computer_use_ootb_internal/computer_use_demo/executor/teachmode_executor.py,sha256=dP5rbO1tVxwu7GJ_LS_lGBtI_GK473_aGVnU0sfrU2o,15300
15
16
  computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/__init__.py,sha256=h2CNeuACklxVpJC65QR8_6AvSybEZLmeO45hY_-lLBs,61
16
17
  computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/gui_capture.py,sha256=CxFJbsSb68ERKH7-C4RaaZy7FIhhzrzGx5qQJ4C37cA,13907
17
18
  computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/utils.py,sha256=OOVxy4Qlbk5q-X9kXFXqt6AmuOMl6FWWqtH269DvJJA,10005
@@ -28,14 +29,14 @@ computer_use_ootb_internal/computer_use_demo/tools/base.py,sha256=XKG8UpjTvG3-Pl
28
29
  computer_use_ootb_internal/computer_use_demo/tools/bash.py,sha256=rHetQ80_v-TTi-1oxIA7ncFEwJxFTh8FJCErIoZbGeY,4236
29
30
  computer_use_ootb_internal/computer_use_demo/tools/collection.py,sha256=8RzHLobL44_Jjt8ltXS6I8XJlEAQOfc75dmnDUaHE-8,922
30
31
  computer_use_ootb_internal/computer_use_demo/tools/colorful_text.py,sha256=cvlmnhAImDTwoRRwhT5au7mNFhfAD7ZfeoDEVdVzDKw,892
31
- computer_use_ootb_internal/computer_use_demo/tools/computer.py,sha256=XUFNoKkRYNrCEvNuIAhhByIYkLjEM-vWUeW0Dn_VtQA,27356
32
+ computer_use_ootb_internal/computer_use_demo/tools/computer.py,sha256=3Fx6jZRhFrLoUWa9eVEZaM7LIv1pALvLqsZXn7pXii4,27358
32
33
  computer_use_ootb_internal/computer_use_demo/tools/computer_marbot.py,sha256=zZuWz9ArfP3Zss-afnscrPkgCtB5UWbCy7HwAOvO2bo,5970
33
34
  computer_use_ootb_internal/computer_use_demo/tools/edit.py,sha256=b0PwUitxckHCQqFP3ZwlthWdqNkn7WETeTHeB6-o98c,11486
34
35
  computer_use_ootb_internal/computer_use_demo/tools/run.py,sha256=xhXdnBK1di9muaO44CEirL9hpGy3NmKbjfMpyeVmn8Y,1595
35
36
  computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py,sha256=L8qfvtUkPPQGt92N-2Zfw5ZTDBzLsDps39uMnX3_uSA,6857
36
37
  computer_use_ootb_internal/preparation/__init__.py,sha256=AgtGHcBpiTkxJjF0xwcs3yyQ6SyUvhL3G0vD2XO-zJw,63
37
38
  computer_use_ootb_internal/preparation/star_rail_prepare.py,sha256=RAriQxrv55csBNBm0m8CKyd_RW8k1tXx0kdJAcOpYlg,4734
38
- computer_use_ootb_internal-0.0.169.dist-info/METADATA,sha256=Vz03DAAV20UTQtUOw8USm5xCGL9kVSyY3150vQW5Mz4,910
39
- computer_use_ootb_internal-0.0.169.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
40
- computer_use_ootb_internal-0.0.169.dist-info/entry_points.txt,sha256=-AbmawU7IRQuDZihgVMVDrFoY4E6rnXYOUB-5vSeBKs,93
41
- computer_use_ootb_internal-0.0.169.dist-info/RECORD,,
39
+ computer_use_ootb_internal-0.0.170.dist-info/METADATA,sha256=LjpaJCl3ruC43TTqHE85N__WL-f2Iv92SgGK3B3TGdw,910
40
+ computer_use_ootb_internal-0.0.170.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ computer_use_ootb_internal-0.0.170.dist-info/entry_points.txt,sha256=-AbmawU7IRQuDZihgVMVDrFoY4E6rnXYOUB-5vSeBKs,93
42
+ computer_use_ootb_internal-0.0.170.dist-info/RECORD,,