computer-use-ootb-internal 0.0.120__tar.gz → 0.0.122__tar.gz
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-0.0.120 → computer_use_ootb_internal-0.0.122}/PKG-INFO +1 -1
- computer_use_ootb_internal-0.0.122/deploy.sh +10 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/pyproject.toml +1 -1
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/guard_service.py +270 -234
- computer_use_ootb_internal-0.0.120/deploy.sh +0 -3
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/.gitignore +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/README.md +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/app_teachmode.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/app_teachmode_gradio.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/animation/click_animation.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/animation/test_animation.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/executor/teachmode_executor.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/gui_capture.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/simple_parser/utils.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/uia_tools/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/uia_tools/screenshot_cli.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/gui_parser/uia_tools/screenshot_service.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/llm_utils/llm_utils.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/llm_utils/oai.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/llm_utils/run_litellm.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/gui_agent/llm_utils/run_llm.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/aws_request.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/base.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/bash.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/collection.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/colorful_text.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/computer.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/computer_marbot.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/edit.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/run.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/dependency_check.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/preparation/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/preparation/star_rail_prepare.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/requirements-lite.txt +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/run_teachmode_ootb_args.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.122}/src/computer_use_ootb_internal/service_manager.py +0 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Clean previous builds
|
4
|
+
rm -rf dist
|
5
|
+
|
6
|
+
# Build the package
|
7
|
+
python -m build
|
8
|
+
|
9
|
+
# Upload to PyPI using API key
|
10
|
+
python -m twine upload dist/* -u __token__ -p pypi-AgEIcHlwaS5vcmcCJGNiMDY2MzY0LWU0YWItNGNkZi1iNmJiLWViOTVjYTQ1NDA3NgACKlszLCIyNjgwNjg2Yy1lZDk2LTQxZGYtYjE2MC02ZGRmYWM4YjBlMWQiXQAABiC8l1-r93zvUfQ4W-aqfGCKady5e-94jEFbPA1SFyDgCw
|
@@ -87,150 +87,20 @@ def receive_command():
|
|
87
87
|
return jsonify({"message": f"Command {command_id} received and queued"}), 202 # Accepted
|
88
88
|
|
89
89
|
# --- Helper Functions --- Only logging helpers needed adjustments
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
def get_pip_executable():
|
97
|
-
"""Tries to locate the pip executable in the same environment."""
|
98
|
-
try:
|
99
|
-
current_python = sys.executable
|
100
|
-
log_info(f"get_pip_executable: sys.executable = {current_python}")
|
101
|
-
python_path = pathlib.Path(current_python)
|
102
|
-
# Common location is ../Scripts/pip.exe relative to python.exe
|
103
|
-
pip_path = python_path.parent / "Scripts" / "pip.exe"
|
104
|
-
log_info(f"get_pip_executable: Checking for pip at {pip_path}")
|
105
|
-
|
106
|
-
if pip_path.exists():
|
107
|
-
log_info(f"get_pip_executable: pip.exe found at {pip_path}")
|
108
|
-
# Quote if necessary
|
109
|
-
pip_exe = str(pip_path)
|
110
|
-
if " " in pip_exe and not pip_exe.startswith('"'):
|
111
|
-
pip_exe = f'"{pip_exe}"'
|
112
|
-
return pip_exe
|
113
|
-
else:
|
114
|
-
log_error(f"get_pip_executable: pip.exe NOT found at {pip_path}. Falling back to 'python -m pip'.")
|
115
|
-
# Fallback is intended here
|
116
|
-
pass # Explicitly pass to reach the fallback return outside the else
|
117
|
-
|
118
|
-
except Exception as e:
|
119
|
-
log_error(f"get_pip_executable: Error determining pip path: {e}", exc_info=True)
|
120
|
-
log_error("get_pip_executable: Falling back to 'python -m pip' due to error.")
|
121
|
-
|
122
|
-
# Fallback return statement if 'exists' is false or an exception occurred
|
123
|
-
return f"{get_python_executable()} -m pip"
|
124
|
-
|
125
|
-
def log_info(msg):
|
126
|
-
thread_name = threading.current_thread().name
|
127
|
-
full_msg = f"[{thread_name}] {msg}"
|
128
|
-
logging.info(full_msg)
|
129
|
-
try:
|
130
|
-
# Only log to event log from main service thread or known non-daemon threads if possible
|
131
|
-
# Trying from waitress/flask threads might cause issues.
|
132
|
-
# For simplicity, maybe remove event log integration or make it conditional.
|
133
|
-
if threading.current_thread().name in ["MainThread", "CommandProcessor"]: # Example condition
|
134
|
-
servicemanager.LogInfoMsg(str(full_msg))
|
135
|
-
except Exception as e:
|
136
|
-
logging.warning(f"Could not write info to Windows Event Log: {e}")
|
137
|
-
|
138
|
-
def log_error(msg, exc_info=False):
|
139
|
-
thread_name = threading.current_thread().name
|
140
|
-
full_msg = f"[{thread_name}] {msg}"
|
141
|
-
logging.error(full_msg, exc_info=exc_info)
|
142
|
-
try:
|
143
|
-
if threading.current_thread().name in ["MainThread", "CommandProcessor"]:
|
144
|
-
servicemanager.LogErrorMsg(str(full_msg))
|
145
|
-
except Exception as e:
|
146
|
-
logging.warning(f"Could not write error to Windows Event Log: {e}")
|
147
|
-
|
148
|
-
# --- PowerShell Task Scheduler Helpers ---
|
149
|
-
|
150
|
-
_TASK_NAME_PREFIX = "OOTB_UserLogon_"
|
151
|
-
|
152
|
-
def run_powershell_command(command, log_output=True):
|
153
|
-
"""Executes a PowerShell command and handles output/errors. Returns True on success."""
|
154
|
-
# Use log_info from the service instance if available, otherwise use root logger
|
155
|
-
logger = _service_instance.log_info if _service_instance else logging.info
|
156
|
-
error_logger = _service_instance.log_error if _service_instance else logging.error
|
157
|
-
logger(f"Executing PowerShell: {command}")
|
158
|
-
try:
|
159
|
-
# Using encoding important for non-ASCII usernames/paths
|
160
|
-
result = subprocess.run(
|
161
|
-
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
|
162
|
-
capture_output=True, text=True, check=True, encoding='utf-8', errors='ignore'
|
163
|
-
)
|
164
|
-
if log_output and result.stdout:
|
165
|
-
logger(f"PowerShell STDOUT:\n{result.stdout.strip()}")
|
166
|
-
if log_output and result.stderr:
|
167
|
-
logger(f"PowerShell STDERR:\n{result.stderr.strip()}") # Log stderr as info
|
168
|
-
return True
|
169
|
-
except FileNotFoundError:
|
170
|
-
error_logger("'powershell.exe' not found. Cannot manage scheduled tasks.")
|
171
|
-
return False
|
172
|
-
except subprocess.CalledProcessError as e:
|
173
|
-
error_logger(f"PowerShell command failed (Exit Code {e.returncode}):")
|
174
|
-
error_logger(f" Command: {e.cmd}")
|
175
|
-
if e.stdout: error_logger(f" STDOUT: {e.stdout.strip()}")
|
176
|
-
if e.stderr: error_logger(f" STDERR: {e.stderr.strip()}")
|
177
|
-
return False
|
178
|
-
except Exception as e:
|
179
|
-
error_logger(f"Unexpected error running PowerShell: {e}", exc_info=True)
|
180
|
-
return False
|
181
|
-
|
182
|
-
def create_or_update_logon_task(username, task_command, python_executable):
|
183
|
-
"""Creates or updates a scheduled task to run a command at user logon."""
|
184
|
-
logger = _service_instance.log_info if _service_instance else logging.info
|
185
|
-
error_logger = _service_instance.log_error if _service_instance else logging.error
|
186
|
-
task_name = f"{_TASK_NAME_PREFIX}{username}"
|
187
|
-
# Escape single quotes in paths and commands for PowerShell
|
188
|
-
safe_python_exe = python_executable.replace("'", "''")
|
189
|
-
# Ensure task_command is just the arguments, not the python exe itself
|
190
|
-
command_parts = task_command.split(' ', 1)
|
191
|
-
if len(command_parts) > 1 and command_parts[0] == python_executable:
|
192
|
-
safe_task_command_args = command_parts[1].replace("'", "''")
|
193
|
-
else: # Fallback if task_command doesn't start with python_exe
|
194
|
-
safe_task_command_args = task_command.replace(python_executable, "").strip().replace("'", "''")
|
195
|
-
|
196
|
-
safe_task_name = task_name.replace("'", "''")
|
197
|
-
safe_username = username.replace("'", "''") # Handle usernames with quotes?
|
198
|
-
|
199
|
-
action = f"$Action = New-ScheduledTaskAction -Execute '{safe_python_exe}' -Argument '{safe_task_command_args}'"
|
200
|
-
trigger = f"$Trigger = New-ScheduledTaskTrigger -AtLogOn -User '{safe_username}'"
|
201
|
-
principal = f"$Principal = New-ScheduledTaskPrincipal -UserId '{safe_username}' -LogonType Interactive"
|
202
|
-
settings = "$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit ([System.TimeSpan]::Zero) -RunOnlyIfNetworkAvailable:$false"
|
203
|
-
|
204
|
-
command = f"""
|
205
|
-
try {{
|
206
|
-
{action}
|
207
|
-
{trigger}
|
208
|
-
{principal}
|
209
|
-
{settings}
|
210
|
-
Register-ScheduledTask -TaskName '{safe_task_name}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -ErrorAction Stop
|
211
|
-
Write-Host "Scheduled task '{safe_task_name}' registered/updated successfully."
|
212
|
-
}} catch {{
|
213
|
-
Write-Error "Failed to register/update scheduled task '{safe_task_name}': $_"
|
214
|
-
exit 1 # Indicate failure
|
215
|
-
}}
|
216
|
-
"""
|
217
|
-
success = run_powershell_command(command)
|
218
|
-
if success:
|
219
|
-
logger(f"Successfully created/updated scheduled task '{task_name}' for user '{username}'.")
|
220
|
-
else:
|
221
|
-
error_logger(f"Failed to create/update scheduled task '{task_name}' for user '{username}'.")
|
222
|
-
return success
|
90
|
+
# Move these inside the class later
|
91
|
+
# def get_python_executable(): ...
|
92
|
+
# def get_pip_executable(): ...
|
93
|
+
|
94
|
+
# Define loggers at module level for use before instance exists?
|
95
|
+
# Or handle carefully within instance methods.
|
223
96
|
|
97
|
+
# --- PowerShell Task Scheduler Helpers --- (These will become methods) ---
|
224
98
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
command = f"Unregister-ScheduledTask -TaskName '{safe_task_name}' -Confirm:$false -ErrorAction SilentlyContinue"
|
231
|
-
run_powershell_command(command, log_output=False)
|
232
|
-
logger(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
|
233
|
-
return True
|
99
|
+
# _TASK_NAME_PREFIX = "OOTB_UserLogon_" # Move to class
|
100
|
+
|
101
|
+
# def run_powershell_command(command, log_output=True): ...
|
102
|
+
# def create_or_update_logon_task(username, task_command, python_executable): ...
|
103
|
+
# def remove_logon_task(username): ...
|
234
104
|
|
235
105
|
# --- End PowerShell Task Scheduler Helpers ---
|
236
106
|
|
@@ -238,6 +108,171 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
238
108
|
_svc_name_ = _SERVICE_NAME
|
239
109
|
_svc_display_name_ = _SERVICE_DISPLAY_NAME
|
240
110
|
_svc_description_ = _SERVICE_DESCRIPTION
|
111
|
+
_task_name_prefix = "OOTB_UserLogon_" # Class attribute for task prefix
|
112
|
+
|
113
|
+
# --- Instance Logging Methods ---
|
114
|
+
def log_info(self, msg):
|
115
|
+
thread_name = threading.current_thread().name
|
116
|
+
full_msg = f"[{thread_name}] {msg}"
|
117
|
+
logging.info(full_msg)
|
118
|
+
try:
|
119
|
+
if threading.current_thread().name in ["MainThread", "CommandProcessor"]:
|
120
|
+
servicemanager.LogInfoMsg(str(full_msg))
|
121
|
+
except Exception as e:
|
122
|
+
# Log only to file if event log fails
|
123
|
+
logging.warning(f"(Instance) Could not write info to Windows Event Log: {e}")
|
124
|
+
|
125
|
+
def log_error(self, msg, exc_info=False):
|
126
|
+
thread_name = threading.current_thread().name
|
127
|
+
full_msg = f"[{thread_name}] {msg}"
|
128
|
+
logging.error(full_msg, exc_info=exc_info)
|
129
|
+
try:
|
130
|
+
if threading.current_thread().name in ["MainThread", "CommandProcessor"]:
|
131
|
+
servicemanager.LogErrorMsg(str(full_msg))
|
132
|
+
except Exception as e:
|
133
|
+
logging.warning(f"(Instance) Could not write error to Windows Event Log: {e}")
|
134
|
+
# --- End Instance Logging ---
|
135
|
+
|
136
|
+
# --- Instance Helper Methods (Moved from module level) ---
|
137
|
+
def get_base_python_executable(self):
|
138
|
+
"""Tries to find python.exe instead of pythonservice.exe if running as service."""
|
139
|
+
service_exe = sys.executable
|
140
|
+
self.log_info(f"get_base_python_executable: sys.executable is {service_exe}")
|
141
|
+
# Normalize path for comparison
|
142
|
+
service_exe_lower = service_exe.lower()
|
143
|
+
|
144
|
+
# Check if running as pythonservice.exe
|
145
|
+
if 'pythonservice.exe' in os.path.basename(service_exe_lower):
|
146
|
+
# Construct expected python.exe path in the same directory
|
147
|
+
dir_name = os.path.dirname(service_exe)
|
148
|
+
potential_python_exe = os.path.join(dir_name, 'python.exe')
|
149
|
+
self.log_info(f"get_base_python_executable: Checking for python.exe at {potential_python_exe}")
|
150
|
+
|
151
|
+
if os.path.exists(potential_python_exe):
|
152
|
+
self.log_info(f"get_base_python_executable: Found python.exe at {potential_python_exe}")
|
153
|
+
# Quote if necessary
|
154
|
+
final_exe = potential_python_exe
|
155
|
+
if " " in final_exe and not final_exe.startswith('"'):
|
156
|
+
return f'"{final_exe}"'
|
157
|
+
return final_exe
|
158
|
+
else:
|
159
|
+
self.log_error(f"get_base_python_executable: Could not find python.exe near pythonservice.exe (checked {potential_python_exe}). Falling back to using {service_exe}.")
|
160
|
+
# Fallback to original sys.executable (quoted if needed)
|
161
|
+
if " " in service_exe and not service_exe.startswith('"'):
|
162
|
+
return f'"{service_exe}"'
|
163
|
+
return service_exe
|
164
|
+
else: # Not running as pythonservice.exe, assume sys.executable is correct
|
165
|
+
self.log_info(f"get_base_python_executable: sys.executable is not pythonservice.exe, using it directly.")
|
166
|
+
if " " in service_exe and not service_exe.startswith('"'):
|
167
|
+
return f'"{service_exe}"'
|
168
|
+
return service_exe
|
169
|
+
|
170
|
+
def get_python_executable(self):
|
171
|
+
# This method is now just an alias for clarity if needed elsewhere,
|
172
|
+
# but primary logic uses get_base_python_executable directly.
|
173
|
+
# Keep for potential compatibility if anything still calls it.
|
174
|
+
return self.get_base_python_executable()
|
175
|
+
|
176
|
+
def get_pip_executable(self):
|
177
|
+
"""Tries to locate the pip executable in the same environment."""
|
178
|
+
try:
|
179
|
+
# Use the potentially corrected python path to find pip's location
|
180
|
+
# Note: This assumes pip is relative to python.exe, not pythonservice.exe
|
181
|
+
base_python = self.get_base_python_executable()
|
182
|
+
python_path = pathlib.Path(base_python.strip('"')) # Use unquoted path
|
183
|
+
pip_path = python_path.parent / "Scripts" / "pip.exe"
|
184
|
+
self.log_info(f"get_pip_executable: Checking for pip relative to {python_path.parent} at {pip_path}")
|
185
|
+
|
186
|
+
if pip_path.exists():
|
187
|
+
self.log_info(f"get_pip_executable: pip.exe found at {pip_path}")
|
188
|
+
pip_exe = str(pip_path)
|
189
|
+
if " " in pip_exe and not pip_exe.startswith('"'):
|
190
|
+
pip_exe = f'"{pip_exe}"'
|
191
|
+
return pip_exe
|
192
|
+
else:
|
193
|
+
self.log_error(f"get_pip_executable: pip.exe NOT found at {pip_path}. Falling back to '{base_python} -m pip'.")
|
194
|
+
pass
|
195
|
+
except Exception as e:
|
196
|
+
self.log_error(f"get_pip_executable: Error determining pip path: {e}", exc_info=True)
|
197
|
+
self.log_error(f"get_pip_executable: Falling back to 'python -m pip' due to error.")
|
198
|
+
|
199
|
+
# Fallback uses the potentially corrected python path
|
200
|
+
return f"{self.get_base_python_executable()} -m pip"
|
201
|
+
|
202
|
+
# --- PowerShell Methods (Moved from module level) ---
|
203
|
+
def run_powershell_command(self, command, log_output=True):
|
204
|
+
"""Executes a PowerShell command and handles output/errors. Returns True on success."""
|
205
|
+
self.log_info(f"Executing PowerShell: {command}")
|
206
|
+
try:
|
207
|
+
result = subprocess.run(
|
208
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
|
209
|
+
capture_output=True, text=True, check=True, encoding='utf-8', errors='ignore'
|
210
|
+
)
|
211
|
+
if log_output and result.stdout:
|
212
|
+
self.log_info(f"PowerShell STDOUT:\n{result.stdout.strip()}")
|
213
|
+
if log_output and result.stderr:
|
214
|
+
self.log_info(f"PowerShell STDERR:\n{result.stderr.strip()}")
|
215
|
+
return True
|
216
|
+
except FileNotFoundError:
|
217
|
+
self.log_error("'powershell.exe' not found. Cannot manage scheduled tasks.")
|
218
|
+
return False
|
219
|
+
except subprocess.CalledProcessError as e:
|
220
|
+
self.log_error(f"PowerShell command failed (Exit Code {e.returncode}):")
|
221
|
+
self.log_error(f" Command: {e.cmd}")
|
222
|
+
if e.stdout: self.log_error(f" STDOUT: {e.stdout.strip()}")
|
223
|
+
if e.stderr: self.log_error(f" STDERR: {e.stderr.strip()}")
|
224
|
+
return False
|
225
|
+
except Exception as e:
|
226
|
+
self.log_error(f"Unexpected error running PowerShell: {e}", exc_info=True)
|
227
|
+
return False
|
228
|
+
|
229
|
+
def create_or_update_logon_task(self, username, task_command, python_executable):
|
230
|
+
"""Creates or updates a scheduled task to run a command at user logon."""
|
231
|
+
task_name = f"{self._task_name_prefix}{username}"
|
232
|
+
safe_python_exe = python_executable.replace("'", "''")
|
233
|
+
command_parts = task_command.split(' ', 1)
|
234
|
+
if len(command_parts) > 1 and command_parts[0] == python_executable:
|
235
|
+
safe_task_command_args = command_parts[1].replace("'", "''")
|
236
|
+
else:
|
237
|
+
safe_task_command_args = task_command.replace(python_executable, "").strip().replace("'", "''")
|
238
|
+
|
239
|
+
safe_task_name = task_name.replace("'", "''")
|
240
|
+
safe_username = username.replace("'", "''")
|
241
|
+
|
242
|
+
action = f"$Action = New-ScheduledTaskAction -Execute '{safe_python_exe}' -Argument '{safe_task_command_args}'"
|
243
|
+
trigger = f"$Trigger = New-ScheduledTaskTrigger -AtLogOn -User '{safe_username}'"
|
244
|
+
principal = f"$Principal = New-ScheduledTaskPrincipal -UserId '{safe_username}' -LogonType Interactive"
|
245
|
+
settings = "$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit ([System.TimeSpan]::Zero) -RunOnlyIfNetworkAvailable:$false"
|
246
|
+
|
247
|
+
command = f"""
|
248
|
+
try {{
|
249
|
+
{action}
|
250
|
+
{trigger}
|
251
|
+
{principal}
|
252
|
+
{settings}
|
253
|
+
Register-ScheduledTask -TaskName '{safe_task_name}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -ErrorAction Stop
|
254
|
+
Write-Host "Scheduled task '{safe_task_name}' registered/updated successfully."
|
255
|
+
}} catch {{
|
256
|
+
Write-Error "Failed to register/update scheduled task '{safe_task_name}': $_"
|
257
|
+
exit 1
|
258
|
+
}}
|
259
|
+
"""
|
260
|
+
success = self.run_powershell_command(command)
|
261
|
+
if success:
|
262
|
+
self.log_info(f"Successfully created/updated scheduled task '{task_name}' for user '{username}'.")
|
263
|
+
else:
|
264
|
+
self.log_error(f"Failed to create/update scheduled task '{task_name}' for user '{username}'.")
|
265
|
+
return success
|
266
|
+
|
267
|
+
def remove_logon_task(self, username):
|
268
|
+
"""Removes the logon scheduled task for a user."""
|
269
|
+
task_name = f"{self._task_name_prefix}{username}"
|
270
|
+
safe_task_name = task_name.replace("'", "''")
|
271
|
+
command = f"Unregister-ScheduledTask -TaskName '{safe_task_name}' -Confirm:$false -ErrorAction SilentlyContinue"
|
272
|
+
self.run_powershell_command(command, log_output=False)
|
273
|
+
self.log_info(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
|
274
|
+
return True
|
275
|
+
# --- End Instance Helper Methods ---
|
241
276
|
|
242
277
|
def __init__(self, args):
|
243
278
|
global _service_instance
|
@@ -247,15 +282,18 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
247
282
|
self.server_thread = None
|
248
283
|
self.command_queue = queue.Queue()
|
249
284
|
self.command_processor_thread = None
|
250
|
-
self.session = requests.Session()
|
285
|
+
self.session = requests.Session()
|
251
286
|
|
252
|
-
|
253
|
-
self.
|
287
|
+
# Initialize paths using instance methods that prefer python.exe
|
288
|
+
self.python_exe = self.get_base_python_executable()
|
289
|
+
self.pip_command_base = self.get_pip_executable()
|
290
|
+
# Construct command using the potentially corrected python.exe path
|
254
291
|
self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
|
255
|
-
_service_instance = self
|
292
|
+
_service_instance = self
|
293
|
+
self.log_info(f"Service initialized. OOTB command set to: {self.ootb_command}")
|
256
294
|
|
257
295
|
def SvcStop(self):
|
258
|
-
log_info(f"Service stop requested.")
|
296
|
+
self.log_info(f"Service stop requested.")
|
259
297
|
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
260
298
|
self.is_running = False
|
261
299
|
# Signal the command processor thread to stop
|
@@ -264,7 +302,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
264
302
|
win32event.SetEvent(self.hWaitStop)
|
265
303
|
# Stopping waitress gracefully from another thread is non-trivial.
|
266
304
|
# We rely on the SCM timeout / process termination for now.
|
267
|
-
log_info(f"{_SERVICE_NAME} SvcStop: Stop signaled. Server thread will be terminated by SCM.")
|
305
|
+
self.log_info(f"{_SERVICE_NAME} SvcStop: Stop signaled. Server thread will be terminated by SCM.")
|
268
306
|
|
269
307
|
|
270
308
|
def SvcDoRun(self):
|
@@ -272,51 +310,51 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
272
310
|
servicemanager.PYS_SERVICE_STARTED,
|
273
311
|
(self._svc_name_, ''))
|
274
312
|
try:
|
275
|
-
log_info(f"{_SERVICE_NAME} starting.")
|
313
|
+
self.log_info(f"{_SERVICE_NAME} starting.")
|
276
314
|
# Start the command processor thread
|
277
315
|
self.command_processor_thread = threading.Thread(
|
278
316
|
target=self.process_commands, name="CommandProcessor", daemon=True)
|
279
317
|
self.command_processor_thread.start()
|
280
|
-
log_info("Command processor thread started.")
|
318
|
+
self.log_info("Command processor thread started.")
|
281
319
|
|
282
320
|
# Start the Flask server (via Waitress) in a separate thread
|
283
321
|
self.server_thread = threading.Thread(
|
284
322
|
target=self.run_server, name="WebServerThread", daemon=True)
|
285
323
|
self.server_thread.start()
|
286
|
-
log_info(f"Web server thread started, listening on {_LISTEN_HOST}:{_LISTEN_PORT}.")
|
324
|
+
self.log_info(f"Web server thread started, listening on {_LISTEN_HOST}:{_LISTEN_PORT}.")
|
287
325
|
|
288
|
-
log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
326
|
+
self.log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
289
327
|
# Keep the main service thread alive waiting for stop signal
|
290
328
|
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
291
|
-
log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
329
|
+
self.log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
292
330
|
|
293
331
|
except Exception as e:
|
294
|
-
log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
332
|
+
self.log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
295
333
|
self.SvcStop() # Signal stop if possible
|
296
334
|
finally:
|
297
|
-
log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
335
|
+
self.log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
298
336
|
|
299
337
|
|
300
338
|
def run_server(self):
|
301
339
|
"""Runs the Flask app using Waitress."""
|
302
|
-
log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
340
|
+
self.log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
303
341
|
try:
|
304
342
|
serve(flask_app, host=_LISTEN_HOST, port=_LISTEN_PORT, threads=4)
|
305
|
-
log_info("Waitress server has stopped.") # Should only happen on shutdown
|
343
|
+
self.log_info("Waitress server has stopped.") # Should only happen on shutdown
|
306
344
|
except Exception as e:
|
307
|
-
log_error(f"Web server thread encountered an error: {e}", exc_info=True)
|
345
|
+
self.log_error(f"Web server thread encountered an error: {e}", exc_info=True)
|
308
346
|
# Consider signaling the main thread to stop if the web server fails critically
|
309
347
|
# For now, just log the error.
|
310
348
|
|
311
349
|
|
312
350
|
def process_commands(self):
|
313
351
|
"""Worker thread to process commands from the queue."""
|
314
|
-
log_info("Command processor thread starting.")
|
352
|
+
self.log_info("Command processor thread starting.")
|
315
353
|
while self.is_running:
|
316
354
|
try:
|
317
355
|
item = self.command_queue.get(block=True, timeout=1) # Add timeout to check is_running periodically
|
318
356
|
if item is None:
|
319
|
-
log_info("Command processor received stop signal.")
|
357
|
+
self.log_info("Command processor received stop signal.")
|
320
358
|
break # Exit loop
|
321
359
|
|
322
360
|
command_id, command = item
|
@@ -324,7 +362,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
324
362
|
target = command.get("target_user", "all_active")
|
325
363
|
status = "failed_unknown" # Default
|
326
364
|
|
327
|
-
log_info(f"Dequeued Command ID {command_id}: action='{action}', target='{target}'")
|
365
|
+
self.log_info(f"Dequeued Command ID {command_id}: action='{action}', target='{target}'")
|
328
366
|
|
329
367
|
try:
|
330
368
|
if action == "update":
|
@@ -334,10 +372,10 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
334
372
|
elif action == "start_ootb":
|
335
373
|
status = self.handle_start(target)
|
336
374
|
else:
|
337
|
-
log_error(f"Unknown action in queue: {action}")
|
375
|
+
self.log_error(f"Unknown action in queue: {action}")
|
338
376
|
status = "failed_unknown_action"
|
339
377
|
except Exception as handler_ex:
|
340
|
-
log_error(f"Exception processing Command ID {command_id} ({action}): {handler_ex}", exc_info=True)
|
378
|
+
self.log_error(f"Exception processing Command ID {command_id} ({action}): {handler_ex}", exc_info=True)
|
341
379
|
status = "failed_exception"
|
342
380
|
finally:
|
343
381
|
self.report_command_status(command_id, status)
|
@@ -347,17 +385,17 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
347
385
|
# Timeout occurred, just loop again and check self.is_running
|
348
386
|
continue
|
349
387
|
except Exception as e:
|
350
|
-
log_error(f"Error in command processing loop: {e}", exc_info=True)
|
388
|
+
self.log_error(f"Error in command processing loop: {e}", exc_info=True)
|
351
389
|
if self.is_running:
|
352
390
|
time.sleep(5)
|
353
391
|
|
354
|
-
log_info("Command processor thread finished.")
|
392
|
+
self.log_info("Command processor thread finished.")
|
355
393
|
|
356
394
|
|
357
395
|
def report_command_status(self, command_id, status, details=""):
|
358
396
|
"""Sends command status back to the server."""
|
359
397
|
if not _SERVER_STATUS_REPORT_URL:
|
360
|
-
log_error("No server status report URL configured. Skipping report.")
|
398
|
+
self.log_error("No server status report URL configured. Skipping report.")
|
361
399
|
return
|
362
400
|
|
363
401
|
payload = {
|
@@ -366,41 +404,40 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
366
404
|
"details": details,
|
367
405
|
"machine_id": os.getenv('COMPUTERNAME', 'unknown_guard')
|
368
406
|
}
|
369
|
-
log_info(f"Reporting status for command {command_id}: {status}")
|
407
|
+
self.log_info(f"Reporting status for command {command_id}: {status}")
|
370
408
|
try:
|
371
409
|
response = self.session.post(_SERVER_STATUS_REPORT_URL, json=payload, timeout=15)
|
372
410
|
response.raise_for_status()
|
373
|
-
log_info(f"Status report for command {command_id} accepted by server.")
|
411
|
+
self.log_info(f"Status report for command {command_id} accepted by server.")
|
374
412
|
except requests.exceptions.RequestException as e:
|
375
|
-
log_error(f"Failed to report status for command {command_id}: {e}")
|
413
|
+
self.log_error(f"Failed to report status for command {command_id}: {e}")
|
376
414
|
except Exception as e:
|
377
|
-
log_error(f"Unexpected error reporting status for command {command_id}: {e}", exc_info=True)
|
415
|
+
self.log_error(f"Unexpected error reporting status for command {command_id}: {e}", exc_info=True)
|
378
416
|
|
379
|
-
# --- Command Handlers ---
|
417
|
+
# --- Command Handlers --- Now call self. for helpers
|
380
418
|
|
381
419
|
def handle_update(self):
|
382
|
-
log_info("Executing OOTB update...")
|
420
|
+
self.log_info("Executing OOTB update...")
|
383
421
|
if not self.pip_command_base:
|
384
|
-
log_error("Cannot update: pip command not found.")
|
422
|
+
self.log_error("Cannot update: pip command not found.")
|
385
423
|
return "failed_pip_not_found"
|
386
424
|
|
387
425
|
update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
|
388
|
-
log_info(f"Running update command: {update_command}")
|
426
|
+
self.log_info(f"Running update command: {update_command}")
|
389
427
|
try:
|
390
428
|
result = subprocess.run(update_command, shell=True, capture_output=True, text=True, check=True, timeout=300, encoding='utf-8')
|
391
|
-
log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
429
|
+
self.log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
392
430
|
return "success"
|
393
431
|
except subprocess.CalledProcessError as e:
|
394
|
-
log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
432
|
+
self.log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
395
433
|
return f"failed_exit_{e.returncode}"
|
396
434
|
except subprocess.TimeoutExpired:
|
397
|
-
log_error(f"Update command timed out.")
|
435
|
+
self.log_error(f"Update command timed out.")
|
398
436
|
return "failed_timeout"
|
399
437
|
except Exception as e:
|
400
|
-
log_error(f"Unexpected error during update: {e}", exc_info=True)
|
438
|
+
self.log_error(f"Unexpected error during update: {e}", exc_info=True)
|
401
439
|
return "failed_exception"
|
402
440
|
|
403
|
-
|
404
441
|
def _get_ootb_processes(self, target_user="all_active"):
|
405
442
|
ootb_procs = []
|
406
443
|
target_pid_list = []
|
@@ -412,10 +449,11 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
412
449
|
target_users.add(username.lower())
|
413
450
|
else:
|
414
451
|
target_users.add(target_user.lower())
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
452
|
+
self.log_info(f"Searching for OOTB processes for users: {target_users}")
|
453
|
+
|
454
|
+
# Use the potentially corrected python.exe path for matching
|
455
|
+
python_exe_path_for_check = self.python_exe.strip('"')
|
456
|
+
self.log_info(f"_get_ootb_processes: Checking against python path: {python_exe_path_for_check}")
|
419
457
|
|
420
458
|
for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline', 'exe']):
|
421
459
|
try:
|
@@ -426,22 +464,20 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
426
464
|
|
427
465
|
if proc_username in target_users:
|
428
466
|
cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
|
429
|
-
# Check if the process executable matches our python path AND module is in cmdline
|
430
|
-
if pinfo['exe'] and pinfo['exe'] ==
|
431
|
-
log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
|
467
|
+
# Check if the process executable matches our corrected python path AND module is in cmdline
|
468
|
+
if pinfo['exe'] and pinfo['exe'] == python_exe_path_for_check and _OOTB_MODULE in cmdline:
|
469
|
+
self.log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
|
432
470
|
ootb_procs.append(proc)
|
433
471
|
target_pid_list.append(pinfo['pid'])
|
434
|
-
|
435
472
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
436
473
|
continue
|
437
|
-
log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
|
474
|
+
self.log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
|
438
475
|
except Exception as e:
|
439
|
-
log_error(f"Error enumerating processes: {e}", exc_info=True)
|
476
|
+
self.log_error(f"Error enumerating processes: {e}", exc_info=True)
|
440
477
|
return ootb_procs
|
441
478
|
|
442
|
-
|
443
479
|
def handle_stop(self, target_user="all_active"):
|
444
|
-
log_info(f"Executing stop OOTB for target '{target_user}'...")
|
480
|
+
self.log_info(f"Executing stop OOTB for target '{target_user}'...")
|
445
481
|
stop_results = {} # Track results per user {username: (task_status, immediate_status)}
|
446
482
|
failed_users = set()
|
447
483
|
|
@@ -458,26 +494,26 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
458
494
|
if user:
|
459
495
|
active_sessions[user.lower()] = session['SessionId']
|
460
496
|
except Exception as query_err:
|
461
|
-
log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
|
497
|
+
self.log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
|
462
498
|
except Exception as user_enum_err:
|
463
|
-
log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
|
499
|
+
self.log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
|
464
500
|
return "failed_user_enumeration"
|
465
501
|
|
466
|
-
log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
|
502
|
+
self.log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
|
467
503
|
|
468
504
|
target_users_normalized = set()
|
469
505
|
if target_user == "all_active":
|
470
506
|
# Target only currently active users for stop all
|
471
507
|
target_users_normalized = set(active_sessions.keys())
|
472
|
-
log_info(f"Stop targeting all active users: {target_users_normalized}")
|
508
|
+
self.log_info(f"Stop targeting all active users: {target_users_normalized}")
|
473
509
|
else:
|
474
510
|
# Target the specific user, regardless of active status (for task removal)
|
475
511
|
normalized_target = target_user.lower()
|
476
512
|
target_users_normalized.add(normalized_target)
|
477
|
-
log_info(f"Stop targeting specific user: {normalized_target}")
|
513
|
+
self.log_info(f"Stop targeting specific user: {normalized_target}")
|
478
514
|
|
479
515
|
if not target_users_normalized:
|
480
|
-
log_info("No target users identified for stop.")
|
516
|
+
self.log_info("No target users identified for stop.")
|
481
517
|
return "failed_no_target_users" # Or success if none were targeted?
|
482
518
|
|
483
519
|
# --- Process each target user ---
|
@@ -486,15 +522,15 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
486
522
|
immediate_stop_status = "stop_not_attempted"
|
487
523
|
stopped_count = 0
|
488
524
|
|
489
|
-
log_info(f"Processing stop for user '{user}'...")
|
525
|
+
self.log_info(f"Processing stop for user '{user}'...")
|
490
526
|
|
491
527
|
# 1. Always try to remove the scheduled task
|
492
528
|
try:
|
493
529
|
# remove_logon_task always returns True for now, just logs attempt
|
494
|
-
remove_logon_task(user)
|
530
|
+
self.remove_logon_task(user)
|
495
531
|
task_removed_status = "task_removed_attempted"
|
496
532
|
except Exception as task_err:
|
497
|
-
log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
|
533
|
+
self.log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
|
498
534
|
task_removed_status = "task_exception"
|
499
535
|
failed_users.add(user)
|
500
536
|
# Continue to try and stop process if active
|
@@ -504,37 +540,37 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
504
540
|
|
505
541
|
if is_active:
|
506
542
|
immediate_stop_status = "stop_attempted"
|
507
|
-
log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
|
543
|
+
self.log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
|
508
544
|
# Pass the specific username to _get_ootb_processes
|
509
545
|
procs_to_stop = self._get_ootb_processes(user)
|
510
546
|
|
511
547
|
if not procs_to_stop:
|
512
|
-
log_info(f"No running OOTB processes found for active user '{user}'.")
|
548
|
+
self.log_info(f"No running OOTB processes found for active user '{user}'.")
|
513
549
|
immediate_stop_status = "stop_skipped_not_running"
|
514
550
|
else:
|
515
|
-
log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
|
551
|
+
self.log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
|
516
552
|
for proc in procs_to_stop:
|
517
553
|
try:
|
518
554
|
pid = proc.pid # Get pid before potential termination
|
519
555
|
username = proc.info.get('username', 'unknown_user')
|
520
|
-
log_info(f"Terminating process PID={pid}, User={username}")
|
556
|
+
self.log_info(f"Terminating process PID={pid}, User={username}")
|
521
557
|
proc.terminate()
|
522
558
|
try:
|
523
559
|
proc.wait(timeout=3)
|
524
|
-
log_info(f"Process PID={pid} terminated successfully.")
|
560
|
+
self.log_info(f"Process PID={pid} terminated successfully.")
|
525
561
|
stopped_count += 1
|
526
562
|
except psutil.TimeoutExpired:
|
527
|
-
log_error(f"Process PID={pid} did not terminate gracefully, killing.")
|
563
|
+
self.log_error(f"Process PID={pid} did not terminate gracefully, killing.")
|
528
564
|
proc.kill()
|
529
565
|
stopped_count += 1
|
530
566
|
except psutil.NoSuchProcess:
|
531
|
-
log_info(f"Process PID={pid} already terminated.")
|
567
|
+
self.log_info(f"Process PID={pid} already terminated.")
|
532
568
|
# Don't increment stopped_count here as we didn't stop it now
|
533
569
|
except psutil.AccessDenied:
|
534
|
-
log_error(f"Access denied trying to terminate process PID={pid}.")
|
570
|
+
self.log_error(f"Access denied trying to terminate process PID={pid}.")
|
535
571
|
failed_users.add(user) # Mark user as failed if stop fails
|
536
572
|
except Exception as e:
|
537
|
-
log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
|
573
|
+
self.log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
|
538
574
|
failed_users.add(user) # Mark user as failed
|
539
575
|
|
540
576
|
# Determine status based on how many were found vs stopped
|
@@ -546,7 +582,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
546
582
|
immediate_stop_status = f"stop_partial_terminated_{stopped_count}_of_{len(procs_to_stop)}"
|
547
583
|
|
548
584
|
else: # User not active
|
549
|
-
log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
|
585
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
|
550
586
|
immediate_stop_status = "stop_skipped_inactive"
|
551
587
|
|
552
588
|
# Record final results for this user
|
@@ -559,7 +595,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
559
595
|
if not stop_results: final_status = "no_targets_processed"
|
560
596
|
if len(failed_users) == total_processed and total_processed > 0 : final_status = "failed"
|
561
597
|
|
562
|
-
log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
|
598
|
+
self.log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
|
563
599
|
try:
|
564
600
|
details = json.dumps(stop_results)
|
565
601
|
except Exception:
|
@@ -567,12 +603,12 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
567
603
|
return f"{final_status}::{details}" # Use :: as separator
|
568
604
|
|
569
605
|
except Exception as e:
|
570
|
-
log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
|
606
|
+
self.log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
|
571
607
|
return "failed_exception"
|
572
608
|
|
573
609
|
|
574
610
|
def handle_start(self, target_user="all_active"):
|
575
|
-
log_info(f"Executing start OOTB for target '{target_user}'...")
|
611
|
+
self.log_info(f"Executing start OOTB for target '{target_user}'...")
|
576
612
|
start_results = {} # Track results per user {username: (task_status, immediate_status)}
|
577
613
|
failed_users = set()
|
578
614
|
|
@@ -594,18 +630,18 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
594
630
|
if user:
|
595
631
|
active_sessions[user.lower()] = session['SessionId']
|
596
632
|
except Exception as query_err:
|
597
|
-
log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
633
|
+
self.log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
598
634
|
except Exception as user_enum_err:
|
599
|
-
log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
|
635
|
+
self.log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
|
600
636
|
return "failed_user_enumeration"
|
601
637
|
|
602
|
-
log_info(f"Found active user sessions: {active_sessions}")
|
638
|
+
self.log_info(f"Found active user sessions: {active_sessions}")
|
603
639
|
|
604
640
|
target_users_normalized = set()
|
605
641
|
if target_user == "all_active":
|
606
642
|
# If targeting all_active, only target those CURRENTLY active
|
607
643
|
target_users_normalized = set(active_sessions.keys())
|
608
|
-
log_info(f"Targeting all active users: {target_users_normalized}")
|
644
|
+
self.log_info(f"Targeting all active users: {target_users_normalized}")
|
609
645
|
else:
|
610
646
|
normalized_target = target_user.lower()
|
611
647
|
# Check if the target user actually exists on the system, even if inactive
|
@@ -613,13 +649,13 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
613
649
|
# Let's assume admin provides a valid username for specific targeting.
|
614
650
|
# if normalized_target in all_system_users: # Removing this check, assume valid user input
|
615
651
|
target_users_normalized.add(normalized_target)
|
616
|
-
log_info(f"Targeting specific user: {normalized_target}")
|
652
|
+
self.log_info(f"Targeting specific user: {normalized_target}")
|
617
653
|
# else:
|
618
654
|
# log_error(f"Target user '{target_user}' does not appear to exist on this system based on psutil.")
|
619
655
|
# return "failed_user_does_not_exist"
|
620
656
|
|
621
657
|
if not target_users_normalized:
|
622
|
-
log_info("No target users identified (or none active for 'all_active').")
|
658
|
+
self.log_info("No target users identified (or none active for 'all_active').")
|
623
659
|
# If target was specific user but they weren't found active, still try task?
|
624
660
|
# Let's proceed to task creation anyway for specific user case.
|
625
661
|
if target_user != "all_active": target_users_normalized.add(target_user.lower())
|
@@ -639,8 +675,8 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
639
675
|
running_procs_by_user[user_lower] = running_procs_by_user.get(user_lower, 0) + 1
|
640
676
|
except Exception: pass
|
641
677
|
except Exception as e:
|
642
|
-
log_error(f"Error checking existing processes: {e}")
|
643
|
-
log_info(f"Users currently running OOTB: {running_procs_by_user}")
|
678
|
+
self.log_error(f"Error checking existing processes: {e}")
|
679
|
+
self.log_info(f"Users currently running OOTB: {running_procs_by_user}")
|
644
680
|
|
645
681
|
# --- Process each target user ---
|
646
682
|
for user in target_users_normalized:
|
@@ -648,14 +684,14 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
648
684
|
immediate_start_status = "start_not_attempted"
|
649
685
|
token = None # Ensure token is reset/defined
|
650
686
|
|
651
|
-
log_info(f"Processing start for user '{user}'...")
|
687
|
+
self.log_info(f"Processing start for user '{user}'...")
|
652
688
|
|
653
689
|
# 1. Always try to create/update the scheduled task
|
654
690
|
try:
|
655
|
-
task_created = create_or_update_logon_task(user, self.ootb_command, self.python_exe)
|
691
|
+
task_created = self.create_or_update_logon_task(user, self.ootb_command, self.python_exe)
|
656
692
|
task_created_status = "task_success" if task_created else "task_failed"
|
657
693
|
except Exception as task_err:
|
658
|
-
log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
|
694
|
+
self.log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
|
659
695
|
task_created_status = "task_exception"
|
660
696
|
failed_users.add(user)
|
661
697
|
# Continue to potentially try immediate start IF user is active?
|
@@ -671,7 +707,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
671
707
|
if is_active:
|
672
708
|
if not is_running:
|
673
709
|
immediate_start_status = "start_attempted"
|
674
|
-
log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
|
710
|
+
self.log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
|
675
711
|
try:
|
676
712
|
session_id = active_sessions[user]
|
677
713
|
token = win32ts.WTSQueryUserToken(session_id)
|
@@ -686,21 +722,21 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
686
722
|
token, self.python_exe, self.ootb_command,
|
687
723
|
None, None, False, creation_flags, env, user_profile_dir, startup
|
688
724
|
)
|
689
|
-
log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
|
725
|
+
self.log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
|
690
726
|
win32api.CloseHandle(hProcess)
|
691
727
|
win32api.CloseHandle(hThread)
|
692
728
|
|
693
729
|
time.sleep(1)
|
694
730
|
if psutil.pid_exists(dwPid):
|
695
|
-
log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
|
731
|
+
self.log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
|
696
732
|
immediate_start_status = "start_success"
|
697
733
|
else:
|
698
|
-
log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
|
734
|
+
self.log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
|
699
735
|
immediate_start_status = "start_failed_exited"
|
700
736
|
failed_users.add(user)
|
701
737
|
|
702
738
|
except Exception as proc_err:
|
703
|
-
log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
|
739
|
+
self.log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
|
704
740
|
immediate_start_status = "start_failed_exception"
|
705
741
|
failed_users.add(user)
|
706
742
|
finally:
|
@@ -708,10 +744,10 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
708
744
|
try: win32api.CloseHandle(token)
|
709
745
|
except: pass
|
710
746
|
else: # User is active but already running
|
711
|
-
log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
|
747
|
+
self.log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
|
712
748
|
immediate_start_status = "start_skipped_already_running"
|
713
749
|
else: # User is not active
|
714
|
-
log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
|
750
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
|
715
751
|
immediate_start_status = "start_skipped_inactive"
|
716
752
|
|
717
753
|
# Record final results for this user
|
@@ -732,7 +768,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
732
768
|
else:
|
733
769
|
final_status = "failed_task_user_inactive"
|
734
770
|
|
735
|
-
log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
|
771
|
+
self.log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
|
736
772
|
# Return detailed results as a JSON string for easier parsing/logging server-side
|
737
773
|
try:
|
738
774
|
details = json.dumps(start_results)
|
@@ -741,13 +777,13 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
741
777
|
return f"{final_status}::{details}"
|
742
778
|
|
743
779
|
except Exception as e:
|
744
|
-
log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
|
780
|
+
self.log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
|
745
781
|
return "failed_exception"
|
746
782
|
|
747
783
|
# --- Main Execution Block ---
|
748
784
|
if __name__ == '__main__':
|
749
785
|
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
|
750
|
-
log_info("Starting service in debug mode...")
|
786
|
+
self.log_info("Starting service in debug mode...")
|
751
787
|
print(f"Running Flask server via Waitress on {_LISTEN_HOST}:{_LISTEN_PORT} for debugging...")
|
752
788
|
print("Service logic (command processing) will NOT run in this mode.")
|
753
789
|
print("Use this primarily to test the '/command' endpoint receiving POSTs.")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|