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.
- computer_use_ootb_internal/app_teachmode.py +59 -0
- computer_use_ootb_internal/computer_use_demo/executor/teachmode_executor.py +5 -45
- computer_use_ootb_internal/computer_use_demo/tools/computer.py +1 -1
- computer_use_ootb_internal/guard_service.py +110 -186
- computer_use_ootb_internal/launch_ootb_elevated.ps1 +63 -0
- computer_use_ootb_internal/service_manager.py +257 -98
- {computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/METADATA +1 -1
- {computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/RECORD +10 -9
- {computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/WHEEL +0 -0
- {computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/entry_points.txt +0 -0
@@ -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,
|
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
|
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
|
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 =
|
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
|
762
|
-
immediate_start_status = "
|
763
|
-
self.log_info(f"Internal trigger: User '{user}' is active and not running. Attempting
|
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
|
758
|
+
return final_status
|
769
759
|
|
770
760
|
try:
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
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
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
#
|
786
|
-
|
787
|
-
|
788
|
-
#
|
789
|
-
|
790
|
-
f"
|
791
|
-
f
|
792
|
-
f
|
793
|
-
f
|
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
|
-
#
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
lpApplicationName = None # Must be None
|
804
|
-
|
805
|
-
|
806
|
-
#
|
807
|
-
|
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
|
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
|
-
|
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
|
822
|
-
#
|
823
|
-
immediate_start_status = "
|
824
|
-
final_status = "
|
825
|
-
|
826
|
-
except
|
827
|
-
|
828
|
-
|
829
|
-
|
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
|
-
|
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
|
-
#
|
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,
|
861
|
+
script_path = os.path.join(base_dir, script_name)
|
958
862
|
if os.path.exists(script_path):
|
959
|
-
self.log_info(f"Found
|
960
|
-
#
|
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"
|
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
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
print("
|
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
|
-
|
64
|
-
|
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: {
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
print(
|
86
|
-
|
87
|
-
|
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"
|
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
|
-
|
109
|
-
|
110
|
-
|
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
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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"
|
124
|
-
return False
|
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
|
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
|
-
#
|
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
|
-
|
142
|
-
|
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
|
-
#
|
148
|
-
|
149
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
start_success =
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
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
|
168
|
-
print(f"\nService '{_SERVICE_NAME}'
|
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
|
173
|
-
print(f"Attempting to
|
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
|
-
|
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
|
-
|
179
|
-
|
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
|
185
|
-
|
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
|
-
|
188
|
-
|
189
|
-
print(f"
|
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__':
|
{computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
39
|
-
computer_use_ootb_internal-0.0.
|
40
|
-
computer_use_ootb_internal-0.0.
|
41
|
-
computer_use_ootb_internal-0.0.
|
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,,
|
{computer_use_ootb_internal-0.0.169.dist-info → computer_use_ootb_internal-0.0.170.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|