computer-use-ootb-internal 0.0.120__tar.gz → 0.0.121__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.121}/PKG-INFO +1 -1
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/pyproject.toml +1 -1
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/guard_service.py +228 -228
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/.gitignore +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/deploy.sh +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/README.md +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/app_teachmode.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/app_teachmode_gradio.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/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.121}/src/computer_use_ootb_internal/dependency_check.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/src/computer_use_ootb_internal/preparation/__init__.py +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/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.121}/src/computer_use_ootb_internal/requirements-lite.txt +0 -0
- {computer_use_ootb_internal-0.0.120 → computer_use_ootb_internal-0.0.121}/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.121}/src/computer_use_ootb_internal/service_manager.py +0 -0
@@ -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
|
-
python_exe = f'"{python_exe}"'
|
94
|
-
return python_exe
|
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(): ...
|
223
93
|
|
94
|
+
# Define loggers at module level for use before instance exists?
|
95
|
+
# Or handle carefully within instance methods.
|
224
96
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
logger(f"Attempted removal of scheduled task '{task_name}' for user '{username}'.")
|
233
|
-
return True
|
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): ...
|
234
104
|
|
235
105
|
# --- End PowerShell Task Scheduler Helpers ---
|
236
106
|
|
@@ -238,6 +108,137 @@ 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_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 ---
|
241
242
|
|
242
243
|
def __init__(self, args):
|
243
244
|
global _service_instance
|
@@ -247,15 +248,16 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
247
248
|
self.server_thread = None
|
248
249
|
self.command_queue = queue.Queue()
|
249
250
|
self.command_processor_thread = None
|
250
|
-
self.session = requests.Session()
|
251
|
+
self.session = requests.Session()
|
251
252
|
|
252
|
-
|
253
|
-
self.
|
253
|
+
# Initialize paths using instance methods
|
254
|
+
self.python_exe = self.get_python_executable()
|
255
|
+
self.pip_command_base = self.get_pip_executable()
|
254
256
|
self.ootb_command = f"{self.python_exe} -m {_OOTB_MODULE}"
|
255
|
-
_service_instance = self
|
257
|
+
_service_instance = self
|
256
258
|
|
257
259
|
def SvcStop(self):
|
258
|
-
log_info(f"Service stop requested.")
|
260
|
+
self.log_info(f"Service stop requested.")
|
259
261
|
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
260
262
|
self.is_running = False
|
261
263
|
# Signal the command processor thread to stop
|
@@ -264,7 +266,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
264
266
|
win32event.SetEvent(self.hWaitStop)
|
265
267
|
# Stopping waitress gracefully from another thread is non-trivial.
|
266
268
|
# 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.")
|
269
|
+
self.log_info(f"{_SERVICE_NAME} SvcStop: Stop signaled. Server thread will be terminated by SCM.")
|
268
270
|
|
269
271
|
|
270
272
|
def SvcDoRun(self):
|
@@ -272,51 +274,51 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
272
274
|
servicemanager.PYS_SERVICE_STARTED,
|
273
275
|
(self._svc_name_, ''))
|
274
276
|
try:
|
275
|
-
log_info(f"{_SERVICE_NAME} starting.")
|
277
|
+
self.log_info(f"{_SERVICE_NAME} starting.")
|
276
278
|
# Start the command processor thread
|
277
279
|
self.command_processor_thread = threading.Thread(
|
278
280
|
target=self.process_commands, name="CommandProcessor", daemon=True)
|
279
281
|
self.command_processor_thread.start()
|
280
|
-
log_info("Command processor thread started.")
|
282
|
+
self.log_info("Command processor thread started.")
|
281
283
|
|
282
284
|
# Start the Flask server (via Waitress) in a separate thread
|
283
285
|
self.server_thread = threading.Thread(
|
284
286
|
target=self.run_server, name="WebServerThread", daemon=True)
|
285
287
|
self.server_thread.start()
|
286
|
-
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}.")
|
287
289
|
|
288
|
-
log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
290
|
+
self.log_info(f"{_SERVICE_NAME} running. Waiting for stop signal.")
|
289
291
|
# Keep the main service thread alive waiting for stop signal
|
290
292
|
win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
|
291
|
-
log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
293
|
+
self.log_info(f"{_SERVICE_NAME} received stop signal in main thread.")
|
292
294
|
|
293
295
|
except Exception as e:
|
294
|
-
log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
296
|
+
self.log_error(f"Fatal error in SvcDoRun: {e}", exc_info=True)
|
295
297
|
self.SvcStop() # Signal stop if possible
|
296
298
|
finally:
|
297
|
-
log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
299
|
+
self.log_info(f"{_SERVICE_NAME} SvcDoRun finished.")
|
298
300
|
|
299
301
|
|
300
302
|
def run_server(self):
|
301
303
|
"""Runs the Flask app using Waitress."""
|
302
|
-
log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
304
|
+
self.log_info(f"Waitress server starting on {_LISTEN_HOST}:{_LISTEN_PORT}")
|
303
305
|
try:
|
304
306
|
serve(flask_app, host=_LISTEN_HOST, port=_LISTEN_PORT, threads=4)
|
305
|
-
log_info("Waitress server has stopped.") # Should only happen on shutdown
|
307
|
+
self.log_info("Waitress server has stopped.") # Should only happen on shutdown
|
306
308
|
except Exception as e:
|
307
|
-
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)
|
308
310
|
# Consider signaling the main thread to stop if the web server fails critically
|
309
311
|
# For now, just log the error.
|
310
312
|
|
311
313
|
|
312
314
|
def process_commands(self):
|
313
315
|
"""Worker thread to process commands from the queue."""
|
314
|
-
log_info("Command processor thread starting.")
|
316
|
+
self.log_info("Command processor thread starting.")
|
315
317
|
while self.is_running:
|
316
318
|
try:
|
317
319
|
item = self.command_queue.get(block=True, timeout=1) # Add timeout to check is_running periodically
|
318
320
|
if item is None:
|
319
|
-
log_info("Command processor received stop signal.")
|
321
|
+
self.log_info("Command processor received stop signal.")
|
320
322
|
break # Exit loop
|
321
323
|
|
322
324
|
command_id, command = item
|
@@ -324,7 +326,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
324
326
|
target = command.get("target_user", "all_active")
|
325
327
|
status = "failed_unknown" # Default
|
326
328
|
|
327
|
-
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}'")
|
328
330
|
|
329
331
|
try:
|
330
332
|
if action == "update":
|
@@ -334,10 +336,10 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
334
336
|
elif action == "start_ootb":
|
335
337
|
status = self.handle_start(target)
|
336
338
|
else:
|
337
|
-
log_error(f"Unknown action in queue: {action}")
|
339
|
+
self.log_error(f"Unknown action in queue: {action}")
|
338
340
|
status = "failed_unknown_action"
|
339
341
|
except Exception as handler_ex:
|
340
|
-
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)
|
341
343
|
status = "failed_exception"
|
342
344
|
finally:
|
343
345
|
self.report_command_status(command_id, status)
|
@@ -347,17 +349,17 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
347
349
|
# Timeout occurred, just loop again and check self.is_running
|
348
350
|
continue
|
349
351
|
except Exception as e:
|
350
|
-
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)
|
351
353
|
if self.is_running:
|
352
354
|
time.sleep(5)
|
353
355
|
|
354
|
-
log_info("Command processor thread finished.")
|
356
|
+
self.log_info("Command processor thread finished.")
|
355
357
|
|
356
358
|
|
357
359
|
def report_command_status(self, command_id, status, details=""):
|
358
360
|
"""Sends command status back to the server."""
|
359
361
|
if not _SERVER_STATUS_REPORT_URL:
|
360
|
-
log_error("No server status report URL configured. Skipping report.")
|
362
|
+
self.log_error("No server status report URL configured. Skipping report.")
|
361
363
|
return
|
362
364
|
|
363
365
|
payload = {
|
@@ -366,41 +368,40 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
366
368
|
"details": details,
|
367
369
|
"machine_id": os.getenv('COMPUTERNAME', 'unknown_guard')
|
368
370
|
}
|
369
|
-
log_info(f"Reporting status for command {command_id}: {status}")
|
371
|
+
self.log_info(f"Reporting status for command {command_id}: {status}")
|
370
372
|
try:
|
371
373
|
response = self.session.post(_SERVER_STATUS_REPORT_URL, json=payload, timeout=15)
|
372
374
|
response.raise_for_status()
|
373
|
-
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.")
|
374
376
|
except requests.exceptions.RequestException as e:
|
375
|
-
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}")
|
376
378
|
except Exception as e:
|
377
|
-
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)
|
378
380
|
|
379
|
-
# --- Command Handlers ---
|
381
|
+
# --- Command Handlers --- Now call self. for helpers
|
380
382
|
|
381
383
|
def handle_update(self):
|
382
|
-
log_info("Executing OOTB update...")
|
384
|
+
self.log_info("Executing OOTB update...")
|
383
385
|
if not self.pip_command_base:
|
384
|
-
log_error("Cannot update: pip command not found.")
|
386
|
+
self.log_error("Cannot update: pip command not found.")
|
385
387
|
return "failed_pip_not_found"
|
386
388
|
|
387
389
|
update_command = f"{self.pip_command_base} install --upgrade --no-cache-dir {_PACKAGE_NAME}"
|
388
|
-
log_info(f"Running update command: {update_command}")
|
390
|
+
self.log_info(f"Running update command: {update_command}")
|
389
391
|
try:
|
390
392
|
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}")
|
393
|
+
self.log_info(f"Update successful: \nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
|
392
394
|
return "success"
|
393
395
|
except subprocess.CalledProcessError as e:
|
394
|
-
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}")
|
395
397
|
return f"failed_exit_{e.returncode}"
|
396
398
|
except subprocess.TimeoutExpired:
|
397
|
-
log_error(f"Update command timed out.")
|
399
|
+
self.log_error(f"Update command timed out.")
|
398
400
|
return "failed_timeout"
|
399
401
|
except Exception as e:
|
400
|
-
log_error(f"Unexpected error during update: {e}", exc_info=True)
|
402
|
+
self.log_error(f"Unexpected error during update: {e}", exc_info=True)
|
401
403
|
return "failed_exception"
|
402
404
|
|
403
|
-
|
404
405
|
def _get_ootb_processes(self, target_user="all_active"):
|
405
406
|
ootb_procs = []
|
406
407
|
target_pid_list = []
|
@@ -413,7 +414,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
413
414
|
else:
|
414
415
|
target_users.add(target_user.lower())
|
415
416
|
|
416
|
-
log_info(f"Searching for OOTB processes for users: {target_users}")
|
417
|
+
self.log_info(f"Searching for OOTB processes for users: {target_users}")
|
417
418
|
|
418
419
|
python_exe_path = self.python_exe.strip('"') # Get unquoted path for comparison
|
419
420
|
|
@@ -428,20 +429,19 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
428
429
|
cmdline = ' '.join(pinfo['cmdline']) if pinfo['cmdline'] else ''
|
429
430
|
# Check if the process executable matches our python path AND module is in cmdline
|
430
431
|
if pinfo['exe'] and pinfo['exe'] == python_exe_path and _OOTB_MODULE in cmdline:
|
431
|
-
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}")
|
432
433
|
ootb_procs.append(proc)
|
433
434
|
target_pid_list.append(pinfo['pid'])
|
434
435
|
|
435
436
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
436
437
|
continue
|
437
|
-
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}")
|
438
439
|
except Exception as e:
|
439
|
-
log_error(f"Error enumerating processes: {e}", exc_info=True)
|
440
|
+
self.log_error(f"Error enumerating processes: {e}", exc_info=True)
|
440
441
|
return ootb_procs
|
441
442
|
|
442
|
-
|
443
443
|
def handle_stop(self, target_user="all_active"):
|
444
|
-
log_info(f"Executing stop OOTB for target '{target_user}'...")
|
444
|
+
self.log_info(f"Executing stop OOTB for target '{target_user}'...")
|
445
445
|
stop_results = {} # Track results per user {username: (task_status, immediate_status)}
|
446
446
|
failed_users = set()
|
447
447
|
|
@@ -458,26 +458,26 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
458
458
|
if user:
|
459
459
|
active_sessions[user.lower()] = session['SessionId']
|
460
460
|
except Exception as query_err:
|
461
|
-
log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
|
461
|
+
self.log_error(f"Could not query session {session['SessionId']} during stop: {query_err}")
|
462
462
|
except Exception as user_enum_err:
|
463
|
-
log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
|
463
|
+
self.log_error(f"Error enumerating users/sessions during stop: {user_enum_err}", exc_info=True)
|
464
464
|
return "failed_user_enumeration"
|
465
465
|
|
466
|
-
log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
|
466
|
+
self.log_info(f"Stop target: '{target_user}'. Active sessions: {active_sessions}")
|
467
467
|
|
468
468
|
target_users_normalized = set()
|
469
469
|
if target_user == "all_active":
|
470
470
|
# Target only currently active users for stop all
|
471
471
|
target_users_normalized = set(active_sessions.keys())
|
472
|
-
log_info(f"Stop targeting all active users: {target_users_normalized}")
|
472
|
+
self.log_info(f"Stop targeting all active users: {target_users_normalized}")
|
473
473
|
else:
|
474
474
|
# Target the specific user, regardless of active status (for task removal)
|
475
475
|
normalized_target = target_user.lower()
|
476
476
|
target_users_normalized.add(normalized_target)
|
477
|
-
log_info(f"Stop targeting specific user: {normalized_target}")
|
477
|
+
self.log_info(f"Stop targeting specific user: {normalized_target}")
|
478
478
|
|
479
479
|
if not target_users_normalized:
|
480
|
-
log_info("No target users identified for stop.")
|
480
|
+
self.log_info("No target users identified for stop.")
|
481
481
|
return "failed_no_target_users" # Or success if none were targeted?
|
482
482
|
|
483
483
|
# --- Process each target user ---
|
@@ -486,15 +486,15 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
486
486
|
immediate_stop_status = "stop_not_attempted"
|
487
487
|
stopped_count = 0
|
488
488
|
|
489
|
-
log_info(f"Processing stop for user '{user}'...")
|
489
|
+
self.log_info(f"Processing stop for user '{user}'...")
|
490
490
|
|
491
491
|
# 1. Always try to remove the scheduled task
|
492
492
|
try:
|
493
493
|
# remove_logon_task always returns True for now, just logs attempt
|
494
|
-
remove_logon_task(user)
|
494
|
+
self.remove_logon_task(user)
|
495
495
|
task_removed_status = "task_removed_attempted"
|
496
496
|
except Exception as task_err:
|
497
|
-
log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
|
497
|
+
self.log_error(f"Exception removing scheduled task for {user}: {task_err}", exc_info=True)
|
498
498
|
task_removed_status = "task_exception"
|
499
499
|
failed_users.add(user)
|
500
500
|
# Continue to try and stop process if active
|
@@ -504,37 +504,37 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
504
504
|
|
505
505
|
if is_active:
|
506
506
|
immediate_stop_status = "stop_attempted"
|
507
|
-
log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
|
507
|
+
self.log_info(f"User '{user}' is active. Attempting to terminate OOTB process(es)...")
|
508
508
|
# Pass the specific username to _get_ootb_processes
|
509
509
|
procs_to_stop = self._get_ootb_processes(user)
|
510
510
|
|
511
511
|
if not procs_to_stop:
|
512
|
-
log_info(f"No running OOTB processes found for active user '{user}'.")
|
512
|
+
self.log_info(f"No running OOTB processes found for active user '{user}'.")
|
513
513
|
immediate_stop_status = "stop_skipped_not_running"
|
514
514
|
else:
|
515
|
-
log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
|
515
|
+
self.log_info(f"Found {len(procs_to_stop)} process(es) for user '{user}' to stop.")
|
516
516
|
for proc in procs_to_stop:
|
517
517
|
try:
|
518
518
|
pid = proc.pid # Get pid before potential termination
|
519
519
|
username = proc.info.get('username', 'unknown_user')
|
520
|
-
log_info(f"Terminating process PID={pid}, User={username}")
|
520
|
+
self.log_info(f"Terminating process PID={pid}, User={username}")
|
521
521
|
proc.terminate()
|
522
522
|
try:
|
523
523
|
proc.wait(timeout=3)
|
524
|
-
log_info(f"Process PID={pid} terminated successfully.")
|
524
|
+
self.log_info(f"Process PID={pid} terminated successfully.")
|
525
525
|
stopped_count += 1
|
526
526
|
except psutil.TimeoutExpired:
|
527
|
-
log_error(f"Process PID={pid} did not terminate gracefully, killing.")
|
527
|
+
self.log_error(f"Process PID={pid} did not terminate gracefully, killing.")
|
528
528
|
proc.kill()
|
529
529
|
stopped_count += 1
|
530
530
|
except psutil.NoSuchProcess:
|
531
|
-
log_info(f"Process PID={pid} already terminated.")
|
531
|
+
self.log_info(f"Process PID={pid} already terminated.")
|
532
532
|
# Don't increment stopped_count here as we didn't stop it now
|
533
533
|
except psutil.AccessDenied:
|
534
|
-
log_error(f"Access denied trying to terminate process PID={pid}.")
|
534
|
+
self.log_error(f"Access denied trying to terminate process PID={pid}.")
|
535
535
|
failed_users.add(user) # Mark user as failed if stop fails
|
536
536
|
except Exception as e:
|
537
|
-
log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
|
537
|
+
self.log_error(f"Error stopping process PID={pid}: {e}", exc_info=True)
|
538
538
|
failed_users.add(user) # Mark user as failed
|
539
539
|
|
540
540
|
# Determine status based on how many were found vs stopped
|
@@ -546,7 +546,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
546
546
|
immediate_stop_status = f"stop_partial_terminated_{stopped_count}_of_{len(procs_to_stop)}"
|
547
547
|
|
548
548
|
else: # User not active
|
549
|
-
log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
|
549
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate process stop (task removal attempted).")
|
550
550
|
immediate_stop_status = "stop_skipped_inactive"
|
551
551
|
|
552
552
|
# Record final results for this user
|
@@ -559,7 +559,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
559
559
|
if not stop_results: final_status = "no_targets_processed"
|
560
560
|
if len(failed_users) == total_processed and total_processed > 0 : final_status = "failed"
|
561
561
|
|
562
|
-
log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
|
562
|
+
self.log_info(f"Finished stopping OOTB. Overall Status: {final_status}. Results: {stop_results}")
|
563
563
|
try:
|
564
564
|
details = json.dumps(stop_results)
|
565
565
|
except Exception:
|
@@ -567,12 +567,12 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
567
567
|
return f"{final_status}::{details}" # Use :: as separator
|
568
568
|
|
569
569
|
except Exception as e:
|
570
|
-
log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
|
570
|
+
self.log_error(f"Error during combined stop OOTB process: {e}", exc_info=True)
|
571
571
|
return "failed_exception"
|
572
572
|
|
573
573
|
|
574
574
|
def handle_start(self, target_user="all_active"):
|
575
|
-
log_info(f"Executing start OOTB for target '{target_user}'...")
|
575
|
+
self.log_info(f"Executing start OOTB for target '{target_user}'...")
|
576
576
|
start_results = {} # Track results per user {username: (task_status, immediate_status)}
|
577
577
|
failed_users = set()
|
578
578
|
|
@@ -594,18 +594,18 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
594
594
|
if user:
|
595
595
|
active_sessions[user.lower()] = session['SessionId']
|
596
596
|
except Exception as query_err:
|
597
|
-
log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
597
|
+
self.log_error(f"Could not query session {session['SessionId']}: {query_err}")
|
598
598
|
except Exception as user_enum_err:
|
599
|
-
log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
|
599
|
+
self.log_error(f"Error enumerating users/sessions: {user_enum_err}", exc_info=True)
|
600
600
|
return "failed_user_enumeration"
|
601
601
|
|
602
|
-
log_info(f"Found active user sessions: {active_sessions}")
|
602
|
+
self.log_info(f"Found active user sessions: {active_sessions}")
|
603
603
|
|
604
604
|
target_users_normalized = set()
|
605
605
|
if target_user == "all_active":
|
606
606
|
# If targeting all_active, only target those CURRENTLY active
|
607
607
|
target_users_normalized = set(active_sessions.keys())
|
608
|
-
log_info(f"Targeting all active users: {target_users_normalized}")
|
608
|
+
self.log_info(f"Targeting all active users: {target_users_normalized}")
|
609
609
|
else:
|
610
610
|
normalized_target = target_user.lower()
|
611
611
|
# Check if the target user actually exists on the system, even if inactive
|
@@ -613,13 +613,13 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
613
613
|
# Let's assume admin provides a valid username for specific targeting.
|
614
614
|
# if normalized_target in all_system_users: # Removing this check, assume valid user input
|
615
615
|
target_users_normalized.add(normalized_target)
|
616
|
-
log_info(f"Targeting specific user: {normalized_target}")
|
616
|
+
self.log_info(f"Targeting specific user: {normalized_target}")
|
617
617
|
# else:
|
618
618
|
# log_error(f"Target user '{target_user}' does not appear to exist on this system based on psutil.")
|
619
619
|
# return "failed_user_does_not_exist"
|
620
620
|
|
621
621
|
if not target_users_normalized:
|
622
|
-
log_info("No target users identified (or none active for 'all_active').")
|
622
|
+
self.log_info("No target users identified (or none active for 'all_active').")
|
623
623
|
# If target was specific user but they weren't found active, still try task?
|
624
624
|
# Let's proceed to task creation anyway for specific user case.
|
625
625
|
if target_user != "all_active": target_users_normalized.add(target_user.lower())
|
@@ -639,8 +639,8 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
639
639
|
running_procs_by_user[user_lower] = running_procs_by_user.get(user_lower, 0) + 1
|
640
640
|
except Exception: pass
|
641
641
|
except Exception as e:
|
642
|
-
log_error(f"Error checking existing processes: {e}")
|
643
|
-
log_info(f"Users currently running OOTB: {running_procs_by_user}")
|
642
|
+
self.log_error(f"Error checking existing processes: {e}")
|
643
|
+
self.log_info(f"Users currently running OOTB: {running_procs_by_user}")
|
644
644
|
|
645
645
|
# --- Process each target user ---
|
646
646
|
for user in target_users_normalized:
|
@@ -648,14 +648,14 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
648
648
|
immediate_start_status = "start_not_attempted"
|
649
649
|
token = None # Ensure token is reset/defined
|
650
650
|
|
651
|
-
log_info(f"Processing start for user '{user}'...")
|
651
|
+
self.log_info(f"Processing start for user '{user}'...")
|
652
652
|
|
653
653
|
# 1. Always try to create/update the scheduled task
|
654
654
|
try:
|
655
|
-
task_created = create_or_update_logon_task(user, self.ootb_command, self.python_exe)
|
655
|
+
task_created = self.create_or_update_logon_task(user, self.ootb_command, self.python_exe)
|
656
656
|
task_created_status = "task_success" if task_created else "task_failed"
|
657
657
|
except Exception as task_err:
|
658
|
-
log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
|
658
|
+
self.log_error(f"Exception creating/updating scheduled task for {user}: {task_err}", exc_info=True)
|
659
659
|
task_created_status = "task_exception"
|
660
660
|
failed_users.add(user)
|
661
661
|
# Continue to potentially try immediate start IF user is active?
|
@@ -671,7 +671,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
671
671
|
if is_active:
|
672
672
|
if not is_running:
|
673
673
|
immediate_start_status = "start_attempted"
|
674
|
-
log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
|
674
|
+
self.log_info(f"User '{user}' is active and not running OOTB. Attempting immediate start...")
|
675
675
|
try:
|
676
676
|
session_id = active_sessions[user]
|
677
677
|
token = win32ts.WTSQueryUserToken(session_id)
|
@@ -686,21 +686,21 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
686
686
|
token, self.python_exe, self.ootb_command,
|
687
687
|
None, None, False, creation_flags, env, user_profile_dir, startup
|
688
688
|
)
|
689
|
-
log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
|
689
|
+
self.log_info(f"CreateProcessAsUser call succeeded for user '{user}' (PID: {dwPid}). Checking existence...")
|
690
690
|
win32api.CloseHandle(hProcess)
|
691
691
|
win32api.CloseHandle(hThread)
|
692
692
|
|
693
693
|
time.sleep(1)
|
694
694
|
if psutil.pid_exists(dwPid):
|
695
|
-
log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
|
695
|
+
self.log_info(f"Immediate start succeeded for user '{user}' (PID {dwPid}).")
|
696
696
|
immediate_start_status = "start_success"
|
697
697
|
else:
|
698
|
-
log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
|
698
|
+
self.log_error(f"Immediate start failed for user '{user}': Process {dwPid} exited immediately.")
|
699
699
|
immediate_start_status = "start_failed_exited"
|
700
700
|
failed_users.add(user)
|
701
701
|
|
702
702
|
except Exception as proc_err:
|
703
|
-
log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
|
703
|
+
self.log_error(f"Exception during immediate start for user '{user}': {proc_err}", exc_info=True)
|
704
704
|
immediate_start_status = "start_failed_exception"
|
705
705
|
failed_users.add(user)
|
706
706
|
finally:
|
@@ -708,10 +708,10 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
708
708
|
try: win32api.CloseHandle(token)
|
709
709
|
except: pass
|
710
710
|
else: # User is active but already running
|
711
|
-
log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
|
711
|
+
self.log_info(f"User '{user}' is active but OOTB is already running. Skipping immediate start.")
|
712
712
|
immediate_start_status = "start_skipped_already_running"
|
713
713
|
else: # User is not active
|
714
|
-
log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
|
714
|
+
self.log_info(f"User '{user}' is not active. Skipping immediate start (task created/updated).")
|
715
715
|
immediate_start_status = "start_skipped_inactive"
|
716
716
|
|
717
717
|
# Record final results for this user
|
@@ -732,7 +732,7 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
732
732
|
else:
|
733
733
|
final_status = "failed_task_user_inactive"
|
734
734
|
|
735
|
-
log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
|
735
|
+
self.log_info(f"Finished starting OOTB. Overall Status: {final_status}. Results: {start_results}")
|
736
736
|
# Return detailed results as a JSON string for easier parsing/logging server-side
|
737
737
|
try:
|
738
738
|
details = json.dumps(start_results)
|
@@ -741,13 +741,13 @@ class GuardService(win32serviceutil.ServiceFramework):
|
|
741
741
|
return f"{final_status}::{details}"
|
742
742
|
|
743
743
|
except Exception as e:
|
744
|
-
log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
|
744
|
+
self.log_error(f"Error during combined start OOTB process: {e}", exc_info=True)
|
745
745
|
return "failed_exception"
|
746
746
|
|
747
747
|
# --- Main Execution Block ---
|
748
748
|
if __name__ == '__main__':
|
749
749
|
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
|
750
|
-
log_info("Starting service in debug mode...")
|
750
|
+
self.log_info("Starting service in debug mode...")
|
751
751
|
print(f"Running Flask server via Waitress on {_LISTEN_HOST}:{_LISTEN_PORT} for debugging...")
|
752
752
|
print("Service logic (command processing) will NOT run in this mode.")
|
753
753
|
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
|
File without changes
|