computer-use-ootb-internal 0.0.119__py3-none-any.whl → 0.0.121__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- computer_use_ootb_internal/guard_service.py +474 -225
- computer_use_ootb_internal/service_manager.py +47 -4
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/METADATA +1 -1
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/RECORD +6 -6
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/WHEEL +0 -0
- {computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/entry_points.txt +0 -0
@@ -22,6 +22,7 @@ import win32con
|
|
22
22
|
import psutil # For process/user info
|
23
23
|
from flask import Flask, request, jsonify # For embedded server
|
24
24
|
from waitress import serve # For serving Flask app
|
25
|
+
import json # Needed for status reporting
|
25
26
|
|
26
27
|
# --- Configuration ---
|
27
28
|
_SERVICE_NAME = "OOTBGuardService"
|
@@ -86,68 +87,158 @@ def receive_command():
|
|
86
87
|
return jsonify({"message": f"Command {command_id} received and queued"}), 202 # Accepted
|
87
88
|
|
88
89
|
# --- Helper Functions --- Only logging helpers needed adjustments
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if pip_path.exists():
|
106
|
-
log_info(f"get_pip_executable: pip.exe found at {pip_path}")
|
107
|
-
# Quote if necessary
|
108
|
-
pip_exe = str(pip_path)
|
109
|
-
if " " in pip_exe and not pip_exe.startswith('"'):
|
110
|
-
pip_exe = f'"{pip_exe}"'
|
111
|
-
return pip_exe
|
112
|
-
else:
|
113
|
-
log_error(f"get_pip_executable: pip.exe NOT found at {pip_path}. Falling back to 'python -m pip'.")
|
114
|
-
# Fallback is intended here
|
115
|
-
pass # Explicitly pass to reach the fallback return outside the else
|
116
|
-
|
117
|
-
except Exception as e:
|
118
|
-
log_error(f"get_pip_executable: Error determining pip path: {e}", exc_info=True)
|
119
|
-
log_error("get_pip_executable: Falling back to 'python -m pip' due to error.")
|
120
|
-
|
121
|
-
# Fallback return statement if 'exists' is false or an exception occurred
|
122
|
-
return f"{get_python_executable()} -m pip"
|
123
|
-
|
124
|
-
def log_info(msg):
|
125
|
-
thread_name = threading.current_thread().name
|
126
|
-
full_msg = f"[{thread_name}] {msg}"
|
127
|
-
logging.info(full_msg)
|
128
|
-
try:
|
129
|
-
# Only log to event log from main service thread or known non-daemon threads if possible
|
130
|
-
# Trying from waitress/flask threads might cause issues.
|
131
|
-
# For simplicity, maybe remove event log integration or make it conditional.
|
132
|
-
if threading.current_thread().name in ["MainThread", "CommandProcessor"]: # Example condition
|
133
|
-
servicemanager.LogInfoMsg(str(full_msg))
|
134
|
-
except Exception as e:
|
135
|
-
logging.warning(f"Could not write info to Windows Event Log: {e}")
|
136
|
-
|
137
|
-
def log_error(msg, exc_info=False):
|
138
|
-
thread_name = threading.current_thread().name
|
139
|
-
full_msg = f"[{thread_name}] {msg}"
|
140
|
-
logging.error(full_msg, exc_info=exc_info)
|
141
|
-
try:
|
142
|
-
if threading.current_thread().name in ["MainThread", "CommandProcessor"]:
|
143
|
-
servicemanager.LogErrorMsg(str(full_msg))
|
144
|
-
except Exception as e:
|
145
|
-
logging.warning(f"Could not write error to Windows Event Log: {e}")
|
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.
|
96
|
+
|
97
|
+
# --- PowerShell Task Scheduler Helpers --- (These will become methods) ---
|
98
|
+
|
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): ...
|
104
|
+
|
105
|
+
# --- End PowerShell Task Scheduler Helpers ---
|
146
106
|
|
147
107
|
class GuardService(win32serviceutil.ServiceFramework):
|
148
108
|
_svc_name_ = _SERVICE_NAME
|
149
109
|
_svc_display_name_ = _SERVICE_DISPLAY_NAME
|
150
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_python_executable(self):
|
138
|
+
python_exe = sys.executable
|
139
|
+
if " " in python_exe and not python_exe.startswith('"'):
|
140
|
+
python_exe = f'"{python_exe}"'
|
141
|
+
return python_exe
|
142
|
+
|
143
|
+
def get_pip_executable(self):
|
144
|
+
"""Tries to locate the pip executable in the same environment."""
|
145
|
+
try:
|
146
|
+
current_python = sys.executable
|
147
|
+
self.log_info(f"get_pip_executable: sys.executable = {current_python}")
|
148
|
+
python_path = pathlib.Path(current_python)
|
149
|
+
pip_path = python_path.parent / "Scripts" / "pip.exe"
|
150
|
+
self.log_info(f"get_pip_executable: Checking for pip at {pip_path}")
|
151
|
+
|
152
|
+
if pip_path.exists():
|
153
|
+
self.log_info(f"get_pip_executable: pip.exe found at {pip_path}")
|
154
|
+
pip_exe = str(pip_path)
|
155
|
+
if " " in pip_exe and not pip_exe.startswith('"'):
|
156
|
+
pip_exe = f'"{pip_exe}"'
|
157
|
+
return pip_exe
|
158
|
+
else:
|
159
|
+
self.log_error(f"get_pip_executable: pip.exe NOT found at {pip_path}. Falling back to 'python -m pip'.")
|
160
|
+
pass
|
161
|
+
|
162
|
+
except Exception as e:
|
163
|
+
self.log_error(f"get_pip_executable: Error determining pip path: {e}", exc_info=True)
|
164
|
+
self.log_error("get_pip_executable: Falling back to 'python -m pip' due to error.")
|
165
|
+
|
166
|
+
return f"{self.get_python_executable()} -m pip"
|
167
|
+
|
168
|
+
# --- PowerShell Methods (Moved from module level) ---
|
169
|
+
def run_powershell_command(self, command, log_output=True):
|
170
|
+
"""Executes a PowerShell command and handles output/errors. Returns True on success."""
|
171
|
+
self.log_info(f"Executing PowerShell: {command}")
|
172
|
+
try:
|
173
|
+
result = subprocess.run(
|
174
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
|
175
|
+
capture_output=True, text=True, check=True, encoding='utf-8', errors='ignore'
|
176
|
+
)
|
177
|
+
if log_output and result.stdout:
|
178
|
+
self.log_info(f"PowerShell STDOUT:\n{result.stdout.strip()}")
|
179
|
+
if log_output and result.stderr:
|
180
|
+
self.log_info(f"PowerShell STDERR:\n{result.stderr.strip()}")
|
181
|
+
return True
|
182
|
+
except FileNotFoundError:
|
183
|
+
self.log_error("'powershell.exe' not found. Cannot manage scheduled tasks.")
|
184
|
+
return False
|
185
|
+
except subprocess.CalledProcessError as e:
|
186
|
+
self.log_error(f"PowerShell command failed (Exit Code {e.returncode}):")
|
187
|
+
self.log_error(f" Command: {e.cmd}")
|
188
|
+
if e.stdout: self.log_error(f" STDOUT: {e.stdout.strip()}")
|
189
|
+
if e.stderr: self.log_error(f" STDERR: {e.stderr.strip()}")
|
190
|
+
return False
|
191
|
+
except Exception as e:
|
192
|
+
self.log_error(f"Unexpected error running PowerShell: {e}", exc_info=True)
|
193
|
+
return False
|
194
|
+
|
195
|
+
def create_or_update_logon_task(self, username, task_command, python_executable):
|
196
|
+
"""Creates or updates a scheduled task to run a command at user logon."""
|
197
|
+
task_name = f"{self._task_name_prefix}{username}"
|
198
|
+
safe_python_exe = python_executable.replace("'", "''")
|
199
|
+
command_parts = task_command.split(' ', 1)
|
200
|
+
if len(command_parts) > 1 and command_parts[0] == python_executable:
|
201
|
+
safe_task_command_args = command_parts[1].replace("'", "''")
|
202
|
+
else:
|
203
|
+
safe_task_command_args = task_command.replace(python_executable, "").strip().replace("'", "''")
|
204
|
+
|
205
|
+
safe_task_name = task_name.replace("'", "''")
|
206
|
+
safe_username = username.replace("'", "''")
|
207
|
+
|
208
|
+
action = f"$Action = New-ScheduledTaskAction -Execute '{safe_python_exe}' -Argument '{safe_task_command_args}'"
|
209
|
+
trigger = f"$Trigger = New-ScheduledTaskTrigger -AtLogOn -User '{safe_username}'"
|
210
|
+
principal = f"$Principal = New-ScheduledTaskPrincipal -UserId '{safe_username}' -LogonType Interactive"
|
211
|
+
settings = "$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit ([System.TimeSpan]::Zero) -RunOnlyIfNetworkAvailable:$false"
|
212
|
+
|
213
|
+
command = f"""
|
214
|
+
try {{
|
215
|
+
{action}
|
216
|
+
{trigger}
|
217
|
+
{principal}
|
218
|
+
{settings}
|
219
|
+
Register-ScheduledTask -TaskName '{safe_task_name}' -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force -ErrorAction Stop
|
220
|
+
Write-Host "Scheduled task '{safe_task_name}' registered/updated successfully."
|
221
|
+
}} catch {{
|
222
|
+
Write-Error "Failed to register/update scheduled task '{safe_task_name}': $_"
|
223
|
+
exit 1
|
224
|
+
}}
|
225
|
+
"""
|
226
|
+
success = self.run_powershell_command(command)
|
227
|
+
if success:
|
228
|
+
self.log_info(f"Successfully created/updated scheduled task '{task_name}' for user '{username}'.")
|
229
|
+
else:
|
230
|
+
self.log_error(f"Failed to create/update scheduled task '{task_name}' for user '{username}'.")
|
231
|
+
return success
|
232
|
+
|
233
|
+
def remove_logon_task(self, username):
|
234
|
+
"""Removes the logon scheduled task for a user."""
|
235
|
+
task_name = f"{self._task_name_prefix}{username}"
|
236
|
+
safe_task_name = task_name.replace("'", "''")
|
237
|
+
command = f"Unregister-ScheduledTask -TaskName '{safe_task_name}' -Confirm:$false -ErrorAction SilentlyContinue"
|
238
|
+
self.run_powershell_command(command, log_output=False)
|
239
|
+
self.log_info(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
|
240
|
+
return True
|
241
|
+
# --- End Instance Helper Methods ---
|
151
242
|
|
152
243
|
def __init__(self, args):
|
153
244
|
global _service_instance
|
@@ -157,15 +248,16 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
157
248
|
self.server_thread = None
|
158
249
|
self.command_queue = queue.Queue()
|
159
250
|
self.command_processor_thread = None
|
160
|
-
self.session = requests.Session()
|
251
|
+
self.session = requests.Session()
|
161
252
|
|
162
|
-
|
163
|
-
self.
|
253
|
+
# Initialize paths using instance methods
|
254
|
+
self.python_exe = self.get_python_executable()
|
255
|
+
self.pip_command_base = self.get_pip_executable()
|
164
256
|
self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
|
165
|
-
_service_instance = self
|
257
|
+
_service_instance = self
|
166
258
|
|
167
259
|
def SvcStop(self):
|
168
|
-
log_info(f"Service stop requested.")
|
260
|
+
self.log_info(f"Service stop requested.")
|
169
261
|
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
170
262
|
self.is_running = False
|
171
263
|
# Signal the command processor thread to stop
|
@@ -174,7 +266,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
174
266
|
win32event.SetEvent(self.hWaitStop)
|
175
267
|
# Stopping waitress gracefully from another thread is non-trivial.
|
176
268
|
# We rely on the SCM timeout / process termination for now.
|
177
|
-
log_info(f"{_SERVICE_NAME} SvcStop: Stop signaled. Server thread will be terminated by SCM.")
|
269
|
+
self.log_info(f"{_SERVICE_NAME} SvcStop: Stop signaled. Server thread will be terminated by SCM.")
|
178
270
|
|
179
271
|
|
180
272
|
def SvcDoRun(self):
|
@@ -182,51 +274,51 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
182
274
|
servicemanager.PYS_SERVICE_STARTED,
|
183
275
|
(self._svc_name_, ''))
|
184
276
|
try:
|
185
|
-
log_info(f"{_SERVICE_NAME} starting.")
|
277
|
+
self.log_info(f"{_SERVICE_NAME} starting.")
|
186
278
|
# Start the command processor thread
|
187
279
|
self.command_processor_thread = threading.Thread(
|
188
280
|
target=self.process_commands, name="CommandProcessor", daemon=True)
|
189
281
|
self.command_processor_thread.start()
|
190
|
-
log_info("Command processor thread started.")
|
282
|
+
self.log_info("Command processor thread started.")
|
191
283
|
|
192
284
|
# Start the Flask server (via Waitress) in a separate thread
|
193
285
|
self.server_thread = threading.Thread(
|
194
286
|
target=self.run_server, name="WebServerThread", daemon=True)
|
195
287
|
self.server_thread.start()
|
196
|
-
log_info(f"Web server thread started, listening on {_LISTEN_HOST}:{_LISTEN_PORT}.")
|
288
|
+
self.log_info(f"Web server thread started, listening on {_LISTEN_HOST}:{_LISTEN_PORT}.")
|
197
289
|
|
198
|
-
log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
290
|
+
self.log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
199
291
|
# Keep the main service thread alive waiting for stop signal
|
200
292
|
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
201
|
-
log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
293
|
+
self.log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
202
294
|
|
203
295
|
except Exception as e:
|
204
|
-
log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
296
|
+
self.log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
205
297
|
self.SvcStop() # Signal stop if possible
|
206
298
|
finally:
|
207
|
-
log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
299
|
+
self.log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
208
300
|
|
209
301
|
|
210
302
|
def run_server(self):
|
211
303
|
"""Runs the Flask app using Waitress."""
|
212
|
-
log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
304
|
+
self.log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
213
305
|
try:
|
214
306
|
serve(flask_app, host=_LISTEN_HOST, port=_LISTEN_PORT, threads=4)
|
215
|
-
log_info("Waitress server has stopped.") # Should only happen on shutdown
|
307
|
+
self.log_info("Waitress server has stopped.") # Should only happen on shutdown
|
216
308
|
except Exception as e:
|
217
|
-
log_error(f"Web server thread encountered an error: {e}", exc_info=True)
|
309
|
+
self.log_error(f"Web server thread encountered an error: {e}", exc_info=True)
|
218
310
|
# Consider signaling the main thread to stop if the web server fails critically
|
219
311
|
# For now, just log the error.
|
220
312
|
|
221
313
|
|
222
314
|
def process_commands(self):
|
223
315
|
"""Worker thread to process commands from the queue."""
|
224
|
-
log_info("Command processor thread starting.")
|
316
|
+
self.log_info("Command processor thread starting.")
|
225
317
|
while self.is_running:
|
226
318
|
try:
|
227
319
|
item = self.command_queue.get(block=True, timeout=1) # Add timeout to check is_running periodically
|
228
320
|
if item is None:
|
229
|
-
log_info("Command processor received stop signal.")
|
321
|
+
self.log_info("Command processor received stop signal.")
|
230
322
|
break # Exit loop
|
231
323
|
|
232
324
|
command_id, command = item
|
@@ -234,7 +326,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
234
326
|
target = command.get("target_user", "all_active")
|
235
327
|
status = "failed_unknown" # Default
|
236
328
|
|
237
|
-
log_info(f"Dequeued Command ID {command_id}: action='{action}', target='{target}'")
|
329
|
+
self.log_info(f"Dequeued Command ID {command_id}: action='{action}', target='{target}'")
|
238
330
|
|
239
331
|
try:
|
240
332
|
if action == "update":
|
@@ -244,10 +336,10 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
244
336
|
elif action == "start_ootb":
|
245
337
|
status = self.handle_start(target)
|
246
338
|
else:
|
247
|
-
log_error(f"Unknown action in queue: {action}")
|
339
|
+
self.log_error(f"Unknown action in queue: {action}")
|
248
340
|
status = "failed_unknown_action"
|
249
341
|
except Exception as handler_ex:
|
250
|
-
log_error(f"Exception processing Command ID {command_id} ({action}): {handler_ex}", exc_info=True)
|
342
|
+
self.log_error(f"Exception processing Command ID {command_id} ({action}): {handler_ex}", exc_info=True)
|
251
343
|
status = "failed_exception"
|
252
344
|
finally:
|
253
345
|
self.report_command_status(command_id, status)
|
@@ -257,17 +349,17 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
257
349
|
# Timeout occurred, just loop again and check self.is_running
|
258
350
|
continue
|
259
351
|
except Exception as e:
|
260
|
-
log_error(f"Error in command processing loop: {e}", exc_info=True)
|
352
|
+
self.log_error(f"Error in command processing loop: {e}", exc_info=True)
|
261
353
|
if self.is_running:
|
262
354
|
time.sleep(5)
|
263
355
|
|
264
|
-
log_info("Command processor thread finished.")
|
356
|
+
self.log_info("Command processor thread finished.")
|
265
357
|
|
266
358
|
|
267
359
|
def report_command_status(self, command_id, status, details=""):
|
268
360
|
"""Sends command status back to the server."""
|
269
361
|
if not _SERVER_STATUS_REPORT_URL:
|
270
|
-
log_error("No server status report URL configured. Skipping report.")
|
362
|
+
self.log_error("No server status report URL configured. Skipping report.")
|
271
363
|
return
|
272
364
|
|
273
365
|
payload = {
|
@@ -276,41 +368,40 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
276
368
|
"details": details,
|
277
369
|
"machine_id": os.getenv('COMPUTERNAME', 'unknown_guard')
|
278
370
|
}
|
279
|
-
log_info(f"Reporting status for command {command_id}: {status}")
|
371
|
+
self.log_info(f"Reporting status for command {command_id}: {status}")
|
280
372
|
try:
|
281
373
|
response = self.session.post(_SERVER_STATUS_REPORT_URL, json=payload, timeout=15)
|
282
374
|
response.raise_for_status()
|
283
|
-
log_info(f"Status report for command {command_id} accepted by server.")
|
375
|
+
self.log_info(f"Status report for command {command_id} accepted by server.")
|
284
376
|
except requests.exceptions.RequestException as e:
|
285
|
-
log_error(f"Failed to report status for command {command_id}: {e}")
|
377
|
+
self.log_error(f"Failed to report status for command {command_id}: {e}")
|
286
378
|
except Exception as e:
|
287
|
-
log_error(f"Unexpected error reporting status for command {command_id}: {e}", exc_info=True)
|
379
|
+
self.log_error(f"Unexpected error reporting status for command {command_id}: {e}", exc_info=True)
|
288
380
|
|
289
|
-
# --- Command Handlers ---
|
381
|
+
# --- Command Handlers --- Now call self. for helpers
|
290
382
|
|
291
383
|
def handle_update(self):
|
292
|
-
log_info("Executing OOTB update...")
|
384
|
+
self.log_info("Executing OOTB update...")
|
293
385
|
if not self.pip_command_base:
|
294
|
-
log_error("Cannot update: pip command not found.")
|
386
|
+
self.log_error("Cannot update: pip command not found.")
|
295
387
|
return "failed_pip_not_found"
|
296
388
|
|
297
389
|
update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
|
298
|
-
log_info(f"Running update command: {update_command}")
|
390
|
+
self.log_info(f"Running update command: {update_command}")
|
299
391
|
try:
|
300
392
|
result = subprocess.run(update_command, shell=True, capture_output=True, text=True, check=True, timeout=300, encoding='utf-8')
|
301
|
-
log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
393
|
+
self.log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
302
394
|
return "success"
|
303
395
|
except subprocess.CalledProcessError as e:
|
304
|
-
log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
396
|
+
self.log_error(f"Update failed (Exit Code {e.returncode}):\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
305
397
|
return f"failed_exit_{e.returncode}"
|
306
398
|
except subprocess.TimeoutExpired:
|
307
|
-
log_error(f"Update command timed out.")
|
399
|
+
self.log_error(f"Update command timed out.")
|
308
400
|
return "failed_timeout"
|
309
401
|
except Exception as e:
|
310
|
-
log_error(f"Unexpected error during update: {e}", exc_info=True)
|
402
|
+
self.log_error(f"Unexpected error during update: {e}", exc_info=True)
|
311
403
|
return "failed_exception"
|
312
404
|
|
313
|
-
|
314
405
|
def _get_ootb_processes(self, target_user="all_active"):
|
315
406
|
ootb_procs = []
|
316
407
|
target_pid_list = []
|
@@ -323,7 +414,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
323
414
|
else:
|
324
415
|
target_users.add(target_user.lower())
|
325
416
|
|
326
|
-
log_info(f"Searching for OOTB processes for users: {target_users}")
|
417
|
+
self.log_info(f"Searching for OOTB processes for users: {target_users}")
|
327
418
|
|
328
419
|
python_exe_path = self.python_exe.strip('"') # Get unquoted path for comparison
|
329
420
|
|
@@ -338,167 +429,325 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
338
429
|
cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
|
339
430
|
# Check if the process executable matches our python path AND module is in cmdline
|
340
431
|
if pinfo['exe'] and pinfo['exe'] == python_exe_path and _OOTB_MODULE in cmdline:
|
341
|
-
log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
|
432
|
+
self.log_info(f"Found matching OOTB process: PID={pinfo['pid']}, User={pinfo['username']}, Cmd={cmdline}")
|
342
433
|
ootb_procs.append(proc)
|
343
434
|
target_pid_list.append(pinfo['pid'])
|
344
435
|
|
345
436
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
346
437
|
continue
|
347
|
-
log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
|
438
|
+
self.log_info(f"Found {len(ootb_procs)} OOTB process(es) matching criteria: {target_pid_list}")
|
348
439
|
except Exception as e:
|
349
|
-
log_error(f"Error enumerating processes: {e}", exc_info=True)
|
440
|
+
self.log_error(f"Error enumerating processes: {e}", exc_info=True)
|
350
441
|
return ootb_procs
|
351
442
|
|
352
|
-
|
353
443
|
def handle_stop(self, target_user="all_active"):
|
354
|
-
log_info(f"Executing stop OOTB for target '{target_user}'...")
|
355
|
-
|
356
|
-
|
444
|
+
self.log_info(f"Executing stop OOTB for target '{target_user}'...")
|
445
|
+
stop_results = {} # Track results per user {username: (task_status, immediate_status)}
|
446
|
+
failed_users = set()
|
357
447
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
for proc in procs_to_stop:
|
448
|
+
try:
|
449
|
+
# --- Get target users and active sessions ---
|
450
|
+
active_sessions = {} # user_lower: session_id
|
451
|
+
# No need for all_system_users for stop, we only care about active or the specific target
|
363
452
|
try:
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
453
|
+
sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
|
454
|
+
for session in sessions:
|
455
|
+
if session['State'] == win32ts.WTSActive:
|
456
|
+
try:
|
457
|
+
user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
|
458
|
+
if user:
|
459
|
+
active_sessions[user.lower()] = session['SessionId']
|
460
|
+
except Exception as query_err:
|
461
|
+
self.log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
|
462
|
+
except Exception as user_enum_err:
|
463
|
+
self.log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
|
464
|
+
return "failed_user_enumeration"
|
465
|
+
|
466
|
+
self.log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
|
467
|
+
|
468
|
+
target_users_normalized = set()
|
469
|
+
if target_user == "all_active":
|
470
|
+
# Target only currently active users for stop all
|
471
|
+
target_users_normalized = set(active_sessions.keys())
|
472
|
+
self.log_info(f"Stop targeting all active users: {target_users_normalized}")
|
473
|
+
else:
|
474
|
+
# Target the specific user, regardless of active status (for task removal)
|
475
|
+
normalized_target = target_user.lower()
|
476
|
+
target_users_normalized.add(normalized_target)
|
477
|
+
self.log_info(f"Stop targeting specific user: {normalized_target}")
|
382
478
|
|
383
|
-
|
384
|
-
|
479
|
+
if not target_users_normalized:
|
480
|
+
self.log_info("No target users identified for stop.")
|
481
|
+
return "failed_no_target_users" # Or success if none were targeted?
|
385
482
|
|
483
|
+
# --- Process each target user ---
|
484
|
+
for user in target_users_normalized:
|
485
|
+
task_removed_status = "task_unknown"
|
486
|
+
immediate_stop_status = "stop_not_attempted"
|
487
|
+
stopped_count = 0
|
386
488
|
|
387
|
-
|
388
|
-
log_info(f"Executing start OOTB for target '{target_user}'...")
|
389
|
-
started_count = 0
|
390
|
-
target_users_started = set()
|
391
|
-
users_failed_to_start = set()
|
489
|
+
self.log_info(f"Processing stop for user '{user}'...")
|
392
490
|
|
393
|
-
|
394
|
-
|
395
|
-
|
491
|
+
# 1. Always try to remove the scheduled task
|
492
|
+
try:
|
493
|
+
# remove_logon_task always returns True for now, just logs attempt
|
494
|
+
self.remove_logon_task(user)
|
495
|
+
task_removed_status = "task_removed_attempted"
|
496
|
+
except Exception as task_err:
|
497
|
+
self.log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
|
498
|
+
task_removed_status = "task_exception"
|
499
|
+
failed_users.add(user)
|
500
|
+
# Continue to try and stop process if active
|
501
|
+
|
502
|
+
# 2. If user is active, try to terminate process
|
503
|
+
is_active = user in active_sessions
|
504
|
+
|
505
|
+
if is_active:
|
506
|
+
immediate_stop_status = "stop_attempted"
|
507
|
+
self.log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
|
508
|
+
# Pass the specific username to _get_ootb_processes
|
509
|
+
procs_to_stop = self._get_ootb_processes(user)
|
510
|
+
|
511
|
+
if not procs_to_stop:
|
512
|
+
self.log_info(f"No running OOTB processes found for active user '{user}'.")
|
513
|
+
immediate_stop_status = "stop_skipped_not_running"
|
514
|
+
else:
|
515
|
+
self.log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
|
516
|
+
for proc in procs_to_stop:
|
517
|
+
try:
|
518
|
+
pid = proc.pid # Get pid before potential termination
|
519
|
+
username = proc.info.get('username', 'unknown_user')
|
520
|
+
self.log_info(f"Terminating process PID={pid}, User={username}")
|
521
|
+
proc.terminate()
|
522
|
+
try:
|
523
|
+
proc.wait(timeout=3)
|
524
|
+
self.log_info(f"Process PID={pid} terminated successfully.")
|
525
|
+
stopped_count += 1
|
526
|
+
except psutil.TimeoutExpired:
|
527
|
+
self.log_error(f"Process PID={pid} did not terminate gracefully, killing.")
|
528
|
+
proc.kill()
|
529
|
+
stopped_count += 1
|
530
|
+
except psutil.NoSuchProcess:
|
531
|
+
self.log_info(f"Process PID={pid} already terminated.")
|
532
|
+
# Don't increment stopped_count here as we didn't stop it now
|
533
|
+
except psutil.AccessDenied:
|
534
|
+
self.log_error(f"Access denied trying to terminate process PID={pid}.")
|
535
|
+
failed_users.add(user) # Mark user as failed if stop fails
|
536
|
+
except Exception as e:
|
537
|
+
self.log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
|
538
|
+
failed_users.add(user) # Mark user as failed
|
539
|
+
|
540
|
+
# Determine status based on how many were found vs stopped
|
541
|
+
if user in failed_users:
|
542
|
+
immediate_stop_status = f"stop_errors_terminated_{stopped_count}_of_{len(procs_to_stop)}"
|
543
|
+
elif stopped_count == len(procs_to_stop):
|
544
|
+
immediate_stop_status = f"stop_success_terminated_{stopped_count}"
|
545
|
+
else: # Should ideally not happen if NoSuchProcess doesn't count
|
546
|
+
immediate_stop_status = f"stop_partial_terminated_{stopped_count}_of_{len(procs_to_stop)}"
|
547
|
+
|
548
|
+
else: # User not active
|
549
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
|
550
|
+
immediate_stop_status = "stop_skipped_inactive"
|
551
|
+
|
552
|
+
# Record final results for this user
|
553
|
+
stop_results[user] = (task_removed_status, immediate_stop_status)
|
554
|
+
|
555
|
+
|
556
|
+
# --- Consolidate status ---
|
557
|
+
total_processed = len(target_users_normalized)
|
558
|
+
final_status = "partial_success" if failed_users else "success"
|
559
|
+
if not stop_results: final_status = "no_targets_processed"
|
560
|
+
if len(failed_users) == total_processed and total_processed > 0 : final_status = "failed"
|
561
|
+
|
562
|
+
self.log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
|
563
|
+
try:
|
564
|
+
details = json.dumps(stop_results)
|
565
|
+
except Exception:
|
566
|
+
details = str(stop_results) # Fallback
|
567
|
+
return f"{final_status}::{details}" # Use :: as separator
|
568
|
+
|
569
|
+
except Exception as e:
|
570
|
+
self.log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
|
571
|
+
return "failed_exception"
|
396
572
|
|
397
|
-
for session in sessions:
|
398
|
-
if session['State'] == win32ts.WTSActive:
|
399
|
-
try:
|
400
|
-
user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
|
401
|
-
if user:
|
402
|
-
normalized_user = user.lower()
|
403
|
-
active_sessions[normalized_user] = session['SessionId']
|
404
|
-
except Exception as query_err:
|
405
|
-
log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
406
573
|
|
407
|
-
|
574
|
+
def handle_start(self, target_user="all_active"):
|
575
|
+
self.log_info(f"Executing start OOTB for target '{target_user}'...")
|
576
|
+
start_results = {} # Track results per user {username: (task_status, immediate_status)}
|
577
|
+
failed_users = set()
|
408
578
|
|
409
|
-
|
579
|
+
try:
|
580
|
+
# --- Get target users and active sessions ---
|
581
|
+
active_sessions = {} # user_lower: session_id
|
582
|
+
all_system_users = set() # user_lower
|
583
|
+
try:
|
584
|
+
# Use psutil for system user list, WTS for active sessions/IDs
|
585
|
+
for user_session in psutil.users():
|
586
|
+
username_lower = user_session.name.split('\\')[-1].lower()
|
587
|
+
all_system_users.add(username_lower)
|
588
|
+
|
589
|
+
sessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE)
|
590
|
+
for session in sessions:
|
591
|
+
if session['State'] == win32ts.WTSActive:
|
592
|
+
try:
|
593
|
+
user = win32ts.WTSQuerySessionInformation(win32ts.WTS_CURRENT_SERVER_HANDLE, session['SessionId'], win32ts.WTSUserName)
|
594
|
+
if user:
|
595
|
+
active_sessions[user.lower()] = session['SessionId']
|
596
|
+
except Exception as query_err:
|
597
|
+
self.log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
598
|
+
except Exception as user_enum_err:
|
599
|
+
self.log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
|
600
|
+
return "failed_user_enumeration"
|
601
|
+
|
602
|
+
self.log_info(f"Found active user sessions: {active_sessions}")
|
603
|
+
|
604
|
+
target_users_normalized = set()
|
410
605
|
if target_user == "all_active":
|
411
|
-
|
606
|
+
# If targeting all_active, only target those CURRENTLY active
|
607
|
+
target_users_normalized = set(active_sessions.keys())
|
608
|
+
self.log_info(f"Targeting all active users: {target_users_normalized}")
|
412
609
|
else:
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
610
|
+
normalized_target = target_user.lower()
|
611
|
+
# Check if the target user actually exists on the system, even if inactive
|
612
|
+
# This check might be complex/unreliable. Rely on task scheduler potentially failing?
|
613
|
+
# Let's assume admin provides a valid username for specific targeting.
|
614
|
+
# if normalized_target in all_system_users: # Removing this check, assume valid user input
|
615
|
+
target_users_normalized.add(normalized_target)
|
616
|
+
self.log_info(f"Targeting specific user: {normalized_target}")
|
617
|
+
# else:
|
618
|
+
# log_error(f"Target user '{target_user}' does not appear to exist on this system based on psutil.")
|
619
|
+
# return "failed_user_does_not_exist"
|
620
|
+
|
621
|
+
if not target_users_normalized:
|
622
|
+
self.log_info("No target users identified (or none active for 'all_active').")
|
623
|
+
# If target was specific user but they weren't found active, still try task?
|
624
|
+
# Let's proceed to task creation anyway for specific user case.
|
625
|
+
if target_user != "all_active": target_users_normalized.add(target_user.lower())
|
626
|
+
if not target_users_normalized:
|
627
|
+
return "failed_no_target_users"
|
628
|
+
|
629
|
+
# --- Check existing processes ---
|
630
|
+
# This check is only relevant for immediate start attempt
|
631
|
+
running_procs_by_user = {} # user_lower: count
|
632
|
+
try:
|
633
|
+
current_running = self._get_ootb_processes("all_active") # Check all
|
634
|
+
for proc in current_running:
|
635
|
+
try:
|
636
|
+
proc_username = proc.info.get('username')
|
637
|
+
if proc_username:
|
638
|
+
user_lower = proc_username.split('\\')[-1].lower()
|
639
|
+
running_procs_by_user[user_lower] = running_procs_by_user.get(user_lower, 0) + 1
|
640
|
+
except Exception: pass
|
641
|
+
except Exception as e:
|
642
|
+
self.log_error(f"Error checking existing processes: {e}")
|
643
|
+
self.log_info(f"Users currently running OOTB: {running_procs_by_user}")
|
419
644
|
|
420
|
-
|
421
|
-
|
422
|
-
|
645
|
+
# --- Process each target user ---
|
646
|
+
for user in target_users_normalized:
|
647
|
+
task_created_status = "task_unknown"
|
648
|
+
immediate_start_status = "start_not_attempted"
|
649
|
+
token = None # Ensure token is reset/defined
|
423
650
|
|
424
|
-
|
425
|
-
users_already_running = set()
|
426
|
-
for proc in running_procs:
|
427
|
-
try:
|
428
|
-
proc_username = proc.info.get('username')
|
429
|
-
if proc_username:
|
430
|
-
users_already_running.add(proc_username.split('\\')[-1].lower())
|
431
|
-
except Exception:
|
432
|
-
pass
|
651
|
+
self.log_info(f"Processing start for user '{user}'...")
|
433
652
|
|
434
|
-
|
653
|
+
# 1. Always try to create/update the scheduled task
|
654
|
+
try:
|
655
|
+
task_created = self.create_or_update_logon_task(user, self.ootb_command, self.python_exe)
|
656
|
+
task_created_status = "task_success" if task_created else "task_failed"
|
657
|
+
except Exception as task_err:
|
658
|
+
self.log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
|
659
|
+
task_created_status = "task_exception"
|
660
|
+
failed_users.add(user)
|
661
|
+
# Continue to potentially try immediate start IF user is active?
|
662
|
+
# Or maybe skip if task creation failed badly?
|
663
|
+
# Let's skip immediate start if task creation had exception.
|
664
|
+
start_results[user] = (task_created_status, immediate_start_status)
|
665
|
+
continue
|
666
|
+
|
667
|
+
# 2. If user is active AND not already running, try immediate start
|
668
|
+
is_active = user in active_sessions
|
669
|
+
is_running = running_procs_by_user.get(user, 0) > 0
|
670
|
+
|
671
|
+
if is_active:
|
672
|
+
if not is_running:
|
673
|
+
immediate_start_status = "start_attempted"
|
674
|
+
self.log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
|
675
|
+
try:
|
676
|
+
session_id = active_sessions[user]
|
677
|
+
token = win32ts.WTSQueryUserToken(session_id)
|
678
|
+
env = win32profile.CreateEnvironmentBlock(token, False)
|
679
|
+
startup = win32process.STARTUPINFO()
|
680
|
+
startup.dwFlags = win32process.STARTF_USESHOWWINDOW
|
681
|
+
startup.wShowWindow = win32con.SW_HIDE
|
682
|
+
creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
|
683
|
+
user_profile_dir = win32profile.GetUserProfileDirectory(token)
|
684
|
+
|
685
|
+
hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
|
686
|
+
token, self.python_exe, self.ootb_command,
|
687
|
+
None, None, False, creation_flags, env, user_profile_dir, startup
|
688
|
+
)
|
689
|
+
self.log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
|
690
|
+
win32api.CloseHandle(hProcess)
|
691
|
+
win32api.CloseHandle(hThread)
|
692
|
+
|
693
|
+
time.sleep(1)
|
694
|
+
if psutil.pid_exists(dwPid):
|
695
|
+
self.log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
|
696
|
+
immediate_start_status = "start_success"
|
697
|
+
else:
|
698
|
+
self.log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
|
699
|
+
immediate_start_status = "start_failed_exited"
|
700
|
+
failed_users.add(user)
|
701
|
+
|
702
|
+
except Exception as proc_err:
|
703
|
+
self.log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
|
704
|
+
immediate_start_status = "start_failed_exception"
|
705
|
+
failed_users.add(user)
|
706
|
+
finally:
|
707
|
+
if token:
|
708
|
+
try: win32api.CloseHandle(token)
|
709
|
+
except: pass
|
710
|
+
else: # User is active but already running
|
711
|
+
self.log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
|
712
|
+
immediate_start_status = "start_skipped_already_running"
|
713
|
+
else: # User is not active
|
714
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
|
715
|
+
immediate_start_status = "start_skipped_inactive"
|
716
|
+
|
717
|
+
# Record final results for this user
|
718
|
+
start_results[user] = (task_created_status, immediate_start_status)
|
719
|
+
|
720
|
+
|
721
|
+
# --- Consolidate status ---
|
722
|
+
total_processed = len(target_users_normalized)
|
723
|
+
final_status = "partial_success" if failed_users else "success"
|
724
|
+
if not start_results: final_status = "no_targets_processed"
|
725
|
+
# If all processed users failed in some way (either task or start)
|
726
|
+
if len(failed_users) == total_processed and total_processed > 0: final_status = "failed"
|
727
|
+
# Special case: target was specific user who wasn't found active
|
728
|
+
elif total_processed == 1 and target_user != "all_active" and target_user.lower() not in active_sessions:
|
729
|
+
user_key = target_user.lower()
|
730
|
+
if user_key in start_results and start_results[user_key][0] == "task_success":
|
731
|
+
final_status = "success_task_only_user_inactive"
|
732
|
+
else:
|
733
|
+
final_status = "failed_task_user_inactive"
|
435
734
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
log_info(f"Attempting to start OOTB for user '{user}' in session {session_id}...")
|
444
|
-
token = win32ts.WTSQueryUserToken(session_id)
|
445
|
-
env = win32profile.CreateEnvironmentBlock(token, False)
|
446
|
-
startup = win32process.STARTUPINFO()
|
447
|
-
# Simplify startup flags: Run hidden, don't explicitly set desktop
|
448
|
-
startup.dwFlags = win32process.STARTF_USESHOWWINDOW
|
449
|
-
startup.wShowWindow = win32con.SW_HIDE
|
450
|
-
# startup.lpDesktop = 'winsta0\\default' # Removed
|
451
|
-
|
452
|
-
creation_flags = win32process.CREATE_NEW_CONSOLE | win32process.CREATE_UNICODE_ENVIRONMENT
|
453
|
-
# Define cwd as user's profile directory if possible
|
454
|
-
user_profile_dir = win32profile.GetUserProfileDirectory(token)
|
455
|
-
|
456
|
-
hProcess, hThread, dwPid, dwTid = win32process.CreateProcessAsUser(
|
457
|
-
token, self.python_exe, self.ootb_command,
|
458
|
-
None, None, False, creation_flags, env,
|
459
|
-
user_profile_dir, # Set current directory
|
460
|
-
startup
|
461
|
-
)
|
462
|
-
log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}).")
|
463
|
-
|
464
|
-
# Add post-start check
|
465
|
-
time.sleep(1) # Small delay
|
466
|
-
if psutil.pid_exists(dwPid):
|
467
|
-
log_info(f"Process PID {dwPid} confirmed to exist shortly after creation.")
|
468
|
-
started_count += 1
|
469
|
-
target_users_started.add(user)
|
470
|
-
else:
|
471
|
-
log_error(f"Process PID {dwPid} reported by CreateProcessAsUser does NOT exist shortly after creation. It likely exited immediately.")
|
472
|
-
users_failed_to_start.add(user)
|
473
|
-
# Attempt to get exit code? Difficult without waiting.
|
474
|
-
|
475
|
-
win32api.CloseHandle(hProcess)
|
476
|
-
win32api.CloseHandle(hThread)
|
477
|
-
|
478
|
-
except Exception as proc_err:
|
479
|
-
log_error(f"Failed to start OOTB for user '{user}' in session {session_id}: {proc_err}", exc_info=True)
|
480
|
-
users_failed_to_start.add(user)
|
481
|
-
finally:
|
482
|
-
if token:
|
483
|
-
try: win32api.CloseHandle(token)
|
484
|
-
except: pass
|
485
|
-
|
486
|
-
log_info(f"Finished starting OOTB. Started {started_count} new instance(s). Failed for users: {users_failed_to_start or 'None'}")
|
487
|
-
if users_failed_to_start:
|
488
|
-
return f"partial_success_started_{started_count}_failed_for_{len(users_failed_to_start)}"
|
489
|
-
elif started_count > 0:
|
490
|
-
return f"success_started_{started_count}"
|
491
|
-
else:
|
492
|
-
return "no_action_needed_already_running"
|
735
|
+
self.log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
|
736
|
+
# Return detailed results as a JSON string for easier parsing/logging server-side
|
737
|
+
try:
|
738
|
+
details = json.dumps(start_results)
|
739
|
+
except Exception:
|
740
|
+
details = str(start_results) # Fallback
|
741
|
+
return f"{final_status}::{details}"
|
493
742
|
|
494
743
|
except Exception as e:
|
495
|
-
log_error(f"Error during start OOTB process: {e}", exc_info=True)
|
744
|
+
self.log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
|
496
745
|
return "failed_exception"
|
497
746
|
|
498
747
|
# --- Main Execution Block ---
|
499
748
|
if __name__ == '__main__':
|
500
749
|
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
|
501
|
-
log_info("Starting service in debug mode...")
|
750
|
+
self.log_info("Starting service in debug mode...")
|
502
751
|
print(f"Running Flask server via Waitress on {_LISTEN_HOST}:{_LISTEN_PORT} for debugging...")
|
503
752
|
print("Service logic (command processing) will NOT run in this mode.")
|
504
753
|
print("Use this primarily to test the '/command' endpoint receiving POSTs.")
|
@@ -10,6 +10,7 @@ import time
|
|
10
10
|
# Constants need to match guard_service.py
|
11
11
|
_SERVICE_NAME = "OOTBGuardService"
|
12
12
|
_SERVICE_DISPLAY_NAME = "OOTB Guard Service"
|
13
|
+
_TASK_NAME_PREFIX = "OOTB_UserLogon_" # Must match guard_service.py
|
13
14
|
|
14
15
|
def is_admin():
|
15
16
|
"""Check if the script is running with administrative privileges."""
|
@@ -104,6 +105,41 @@ def run_service_command(command_args, check_errors=True):
|
|
104
105
|
print(f"An unexpected error occurred running service command: {e}", file=sys.stderr)
|
105
106
|
return False
|
106
107
|
|
108
|
+
# --- Add cleanup helpers ---
|
109
|
+
def _run_powershell_cleanup_command(command):
|
110
|
+
"""Executes a PowerShell command specifically for cleanup, ignoring most errors."""
|
111
|
+
if platform.system() != "Windows": return True # Skip on non-windows
|
112
|
+
print(f"Executing PowerShell Cleanup: {command}")
|
113
|
+
try:
|
114
|
+
# Use check=False, don't capture output unless needed for debug
|
115
|
+
subprocess.run(
|
116
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", command],
|
117
|
+
check=False, # Don't throw error if command fails (e.g., no tasks found)
|
118
|
+
stdout=subprocess.DEVNULL, # Suppress stdout
|
119
|
+
stderr=subprocess.DEVNULL # Suppress stderr
|
120
|
+
)
|
121
|
+
return True # Assume success for cleanup flow
|
122
|
+
except Exception as e:
|
123
|
+
print(f"Warning: PowerShell cleanup command failed: {e}", file=sys.stderr)
|
124
|
+
return False # Indicate potential issue
|
125
|
+
|
126
|
+
def _cleanup_scheduled_tasks():
|
127
|
+
"""Removes all OOTB user logon scheduled tasks."""
|
128
|
+
print("Attempting to remove any existing OOTB user logon scheduled tasks...")
|
129
|
+
# Use -like operator and wildcard
|
130
|
+
# Use try-catch within PowerShell for robustness
|
131
|
+
command = f"""
|
132
|
+
$tasks = Get-ScheduledTask | Where-Object {{ $_.TaskName -like '{_TASK_NAME_PREFIX}*' }}
|
133
|
+
if ($tasks) {{
|
134
|
+
Write-Host "Found $($tasks.Count) OOTB logon tasks to remove."
|
135
|
+
$tasks | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
|
136
|
+
Write-Host "OOTB logon task removal attempted."
|
137
|
+
}} else {{
|
138
|
+
Write-Host "No OOTB logon tasks found to remove."
|
139
|
+
}}
|
140
|
+
"""
|
141
|
+
_run_powershell_cleanup_command(command)
|
142
|
+
# --- End cleanup helpers ---
|
107
143
|
|
108
144
|
def install_and_start():
|
109
145
|
"""Installs and starts the Guard Service."""
|
@@ -133,17 +169,24 @@ def install_and_start():
|
|
133
169
|
|
134
170
|
|
135
171
|
def stop_and_remove():
|
136
|
-
"""Stops and removes the Guard Service."""
|
172
|
+
"""Stops and removes the Guard Service and associated scheduled tasks."""
|
137
173
|
print(f"Attempting to stop service: '{_SERVICE_NAME}' (will ignore errors if not running)")
|
138
174
|
# Run stop first, ignore errors (check_errors=False)
|
139
175
|
run_service_command(['stop'], check_errors=False)
|
140
176
|
time.sleep(2) # Give service time to stop
|
141
177
|
|
142
178
|
print(f"\nAttempting to remove service: '{_SERVICE_NAME}'")
|
143
|
-
|
144
|
-
|
179
|
+
remove_success = run_service_command(['remove']) # Check if removal command itself failed
|
180
|
+
|
181
|
+
# Always attempt task cleanup, even if service removal had issues
|
182
|
+
_cleanup_scheduled_tasks()
|
183
|
+
|
184
|
+
if remove_success:
|
185
|
+
print(f"\nService '{_SERVICE_NAME}' stopped (if running) and removed successfully. Associated logon tasks cleanup attempted.")
|
145
186
|
else:
|
146
|
-
print(f"\nService '{_SERVICE_NAME}' removal failed.", file=sys.stderr)
|
187
|
+
print(f"\nService '{_SERVICE_NAME}' removal command failed.", file=sys.stderr)
|
188
|
+
# Make sure to mention cleanup was still attempted
|
189
|
+
print(f" Associated logon tasks cleanup attempted.", file=sys.stderr)
|
147
190
|
print(f" Ensure the service was stopped first, or check permissions.", file=sys.stderr)
|
148
191
|
|
149
192
|
if __name__ == '__main__':
|
{computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/RECORD
RENAMED
@@ -3,10 +3,10 @@ computer_use_ootb_internal/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4Tv
|
|
3
3
|
computer_use_ootb_internal/app_teachmode.py,sha256=PClMS7X6zRGzY7YPOV6Zxkfv5BajLVqmBiv-mOBVxkw,22164
|
4
4
|
computer_use_ootb_internal/app_teachmode_gradio.py,sha256=cmFpBrkdlZxOQADWveVdIaaNqaBD8IVs-xNLJogU7F8,7909
|
5
5
|
computer_use_ootb_internal/dependency_check.py,sha256=y8RMEP6RXQzTgU1MS_1piBLtz4J-Hfn9RjUZg59dyvo,1333
|
6
|
-
computer_use_ootb_internal/guard_service.py,sha256=
|
6
|
+
computer_use_ootb_internal/guard_service.py,sha256=rDAoNd2g14bq9bZ57A1MgmBSlrjrzcXUEw9m-dm2XBU,40119
|
7
7
|
computer_use_ootb_internal/requirements-lite.txt,sha256=5DAHomz4A_P2BmTIXNkNqkHbnIF0AyZ4_1XAlb1LaYs,290
|
8
8
|
computer_use_ootb_internal/run_teachmode_ootb_args.py,sha256=7Dj0iY4GG7P03tRKYJ2x9Yvt-PE-b7uyjCAed3SaF3Y,7086
|
9
|
-
computer_use_ootb_internal/service_manager.py,sha256=
|
9
|
+
computer_use_ootb_internal/service_manager.py,sha256=SD8jzfn0VVXBOr_nP6zmBWSC2TzrU_sp2e5JJkSlQFU,9734
|
10
10
|
computer_use_ootb_internal/computer_use_demo/animation/click_animation.py,sha256=tP1gsayFy-CKk10UlrE9RlexwlHWiHQUe5Ogg4vQvSg,3234
|
11
11
|
computer_use_ootb_internal/computer_use_demo/animation/icons8-select-cursor-transparent-96.gif,sha256=4LfwsfFQnREXrNRs32aJU2jO65JXianJoL_8q7-8elg,30966
|
12
12
|
computer_use_ootb_internal/computer_use_demo/animation/test_animation.py,sha256=2R1u98OLKYalSZ5nt5vvyZ71FL5R5vLv-n8zM8jVdV8,1183
|
@@ -34,7 +34,7 @@ computer_use_ootb_internal/computer_use_demo/tools/run.py,sha256=xhXdnBK1di9muaO
|
|
34
34
|
computer_use_ootb_internal/computer_use_demo/tools/screen_capture.py,sha256=L8qfvtUkPPQGt92N-2Zfw5ZTDBzLsDps39uMnX3_uSA,6857
|
35
35
|
computer_use_ootb_internal/preparation/__init__.py,sha256=AgtGHcBpiTkxJjF0xwcs3yyQ6SyUvhL3G0vD2XO-zJw,63
|
36
36
|
computer_use_ootb_internal/preparation/star_rail_prepare.py,sha256=s1VWszcTnJAKxqCHFlaOEwPkqVSrkiFx_yKpWSnSbHs,2649
|
37
|
-
computer_use_ootb_internal-0.0.
|
38
|
-
computer_use_ootb_internal-0.0.
|
39
|
-
computer_use_ootb_internal-0.0.
|
40
|
-
computer_use_ootb_internal-0.0.
|
37
|
+
computer_use_ootb_internal-0.0.121.dist-info/METADATA,sha256=nFXcxghQx78Ukkgbzl6-RNyU4yEhdzNKSb_HAlr86DU,1048
|
38
|
+
computer_use_ootb_internal-0.0.121.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
39
|
+
computer_use_ootb_internal-0.0.121.dist-info/entry_points.txt,sha256=bXfyAU_qq-G1EiEgAQEioXvgEdRCFxaTooqdDD9Y4OA,258
|
40
|
+
computer_use_ootb_internal-0.0.121.dist-info/RECORD,,
|
{computer_use_ootb_internal-0.0.119.dist-info → computer_use_ootb_internal-0.0.121.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|