Continued Work on Remote Script Execution

This commit is contained in:
2025-09-04 04:52:01 -06:00
parent c6047c41d9
commit 772a9c31d0
8 changed files with 485 additions and 1196 deletions

View File

@@ -1,7 +1,6 @@
import os
import sys
import subprocess
import shlex
import ctypes
from datetime import datetime
@@ -11,12 +10,6 @@ def _now():
def project_paths():
r"""Derive important paths relative to the venv python (sys.executable).
Layout assumed at runtime:
<ProjectRoot>/Agent/Scripts/python.exe == sys.executable
<ProjectRoot>/Agent/Borealis/*.py == deployed agent files
"""
venv_scripts = os.path.dirname(os.path.abspath(sys.executable))
venv_root = os.path.abspath(os.path.join(venv_scripts, os.pardir))
project_root = os.path.abspath(os.path.join(venv_root, os.pardir))
@@ -31,8 +24,6 @@ def project_paths():
"borealis_dir": borealis_dir,
"logs_dir": logs_dir,
"temp_dir": temp_dir,
"service_script": os.path.join(borealis_dir, "windows_script_service.py"),
# Use tray launcher for the scheduled task
"agent_script": os.path.join(borealis_dir, "tray_launcher.py"),
}
@@ -53,43 +44,21 @@ def log_write(paths, name, text):
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin() != 0 # type: ignore[attr-defined]
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
return False
def run(cmd, cwd=None, capture=False):
if isinstance(cmd, str):
shell = True
args = cmd
else:
shell = False
args = cmd
return subprocess.run(
args,
cwd=cwd,
shell=shell,
text=True,
capture_output=capture,
check=False,
)
def run(cmd, capture=False):
return subprocess.run(cmd, text=True, capture_output=capture, check=False)
def run_elevated_powershell(paths, ps_content, log_name):
"""Run a short PowerShell script elevated and wait for completion.
Writes combined output to Logs/log_name.
"""
ensure_dirs(paths)
log_path = os.path.join(paths["logs_dir"], log_name)
stub_path = os.path.join(paths["temp_dir"], f"elevate_{os.getpid()}_{log_name.replace('.', '_')}.ps1")
with open(stub_path, "w", encoding="utf-8") as f:
f.write(ps_content)
# Build powershell command
ps = "powershell.exe"
args = f"-NoProfile -ExecutionPolicy Bypass -File \"{stub_path}\""
# ShellExecute to run as admin
SEE_MASK_NOCLOSEPROCESS = 0x00000040
class SHELLEXECUTEINFO(ctypes.Structure):
_fields_ = [
@@ -109,31 +78,21 @@ def run_elevated_powershell(paths, ps_content, log_name):
("hIcon", ctypes.c_void_p),
("hProcess", ctypes.c_void_p),
]
sei = SHELLEXECUTEINFO()
sei.cbSize = ctypes.sizeof(SHELLEXECUTEINFO)
sei.fMask = SEE_MASK_NOCLOSEPROCESS
sei.hwnd = None
sei.lpVerb = "runas"
sei.lpFile = ps
sei.lpParameters = args
sei.lpFile = "powershell.exe"
sei.lpParameters = f"-NoProfile -ExecutionPolicy Bypass -File \"{stub_path}\""
sei.lpDirectory = paths["project_root"]
sei.nShow = 1
if not ctypes.windll.shell32.ShellExecuteExW(ctypes.byref(sei)):
log_write(paths, log_name, "[ERROR] UAC elevation failed (ShellExecuteExW)")
try:
os.remove(stub_path)
except Exception:
pass
return 1
# Wait for elevated process
ctypes.windll.kernel32.WaitForSingleObject(sei.hProcess, 0xFFFFFFFF)
# Capture output from stub if it appended
try:
with open(log_path, "a", encoding="utf-8") as f:
f.write("")
except Exception:
pass
hproc = sei.hProcess
if hproc:
ctypes.windll.kernel32.WaitForSingleObject(hproc, 0xFFFFFFFF)
try:
os.remove(stub_path)
except Exception:
@@ -141,262 +100,64 @@ def run_elevated_powershell(paths, ps_content, log_name):
return 0
def current_service_path(service_name):
"""Return the configured BinaryPathName for a service (or None)."""
try:
r = run(["sc.exe", "qc", service_name], capture=True)
if r.returncode != 0:
return None
for line in (r.stdout or "").splitlines():
if "BINARY_PATH_NAME" in line:
# Example: "BINARY_PATH_NAME : C:\\...\\python.exe C:\\...\\windows_script_service.py"
parts = line.split(":", 1)
if len(parts) == 2:
return parts[1].strip()
except Exception:
pass
return None
def ensure_script_service(paths):
service_name = "BorealisAgent"
log_name = "Borealis_ScriptService_Install.log"
ensure_dirs(paths)
log_write(paths, log_name, "[INFO] Ensuring script execution service...")
# Decide if install/update needed
need_install = False
bin_path = current_service_path(service_name)
expected_root = paths["venv_root"].lower()
if not bin_path:
need_install = True
else:
if expected_root not in bin_path.lower():
need_install = True
if not is_admin():
# Relaunch elevated to perform service installation/update
venv = paths["venv_python"].replace("\"", "\"\"")
srv = paths["service_script"].replace("\"", "\"\"")
log = os.path.join(paths["logs_dir"], log_name).replace("\"", "\"\"")
venv_dir = os.path.dirname(paths["venv_python"]).replace("\"", "\"\"")
postinstall = os.path.join(venv_dir, "pywin32_postinstall.py").replace("\"", "\"\"")
py_home = paths["venv_root"].replace("\"", "\"\"")
content = f"""
$ErrorActionPreference = 'Continue'
$venv = "{venv}"
$srv = "{srv}"
$log = "{log}"
$post = "{postinstall}"
$pyhome = "{py_home}"
try {{
try {{ New-Item -ItemType Directory -Force -Path (Split-Path $log -Parent) | Out-Null }} catch {{}}
# Remove legacy service names if present
try {{ sc.exe stop BorealisScriptAgent 2>$null | Out-Null }} catch {{}}
try {{ sc.exe delete BorealisScriptAgent 2>$null | Out-Null }} catch {{}}
try {{ sc.exe stop BorealisScriptService 2>$null | Out-Null }} catch {{}}
try {{ sc.exe delete BorealisScriptService 2>$null | Out-Null }} catch {{}}
if (Test-Path $post) {{ & $venv $post -install *>> "$log" }} else {{ & $venv -m pywin32_postinstall -install *>> "$log" }}
try {{ & $venv $srv remove *>> "$log" }} catch {{}}
& $venv $srv --startup auto install *>> "$log"
# Ensure registry points to correct module and PY path
reg add "HKLM\SYSTEM\CurrentControlSet\Services\{service_name}\PythonClass" /ve /t REG_SZ /d "windows_script_service.BorealisAgentService" /f | Out-Null
reg add "HKLM\SYSTEM\CurrentControlSet\Services\{service_name}\PythonPath" /ve /t REG_SZ /d "{paths['borealis_dir']}" /f | Out-Null
reg add "HKLM\SYSTEM\CurrentControlSet\Services\{service_name}\PythonHome" /ve /t REG_SZ /d "$pyhome" /f | Out-Null
sc.exe config {service_name} obj= LocalSystem start= auto | Out-File -FilePath "$log" -Append -Encoding UTF8
sc.exe start {service_name} | Out-File -FilePath "$log" -Append -Encoding UTF8
"[INFO] Completed service ensure." | Out-File -FilePath "$log" -Append -Encoding UTF8
}} catch {{
"[ERROR] $_" | Out-File -FilePath "$log" -Append -Encoding UTF8
exit 1
}}
"""
rc = run_elevated_powershell(paths, content, log_name)
return rc == 0
# Admin path: perform directly
try:
# Ensure pywin32 service hooks present in this venv
post_py = os.path.join(os.path.dirname(paths["venv_python"]), "pywin32_postinstall.py")
if os.path.isfile(post_py):
run([paths["venv_python"], post_py, "-install"]) # ignore rc
else:
run([paths["venv_python"], "-m", "pywin32_postinstall", "-install"]) # ignore rc
except Exception:
pass
try:
# Remove legacy service if it exists
run(["sc.exe", "stop", "BorealisScriptAgent"]) # ignore rc
run(["sc.exe", "delete", "BorealisScriptAgent"]) # ignore rc
run(["sc.exe", "stop", "BorealisScriptService"]) # ignore rc
run(["sc.exe", "delete", "BorealisScriptService"]) # ignore rc
if need_install:
run([paths["venv_python"], paths["service_script"], "remove"]) # ignore rc
r1 = run([paths["venv_python"], paths["service_script"], "--startup", "auto", "install"], capture=True)
log_write(paths, log_name, f"[INFO] install rc={r1.returncode} out={r1.stdout}\nerr={r1.stderr}")
# fix registry for module import and runtime resolution
# PythonHome: base interpreter home (from pyvenv.cfg 'home') so pythonservice can load pythonXY.dll
# PythonPath: add Borealis dir and venv site-packages including pywin32 dirs
try:
cfg = os.path.join(paths["venv_root"], "pyvenv.cfg")
base_home = None
if os.path.isfile(cfg):
with open(cfg, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
if line.strip().lower().startswith("home ="):
base_home = line.split("=",1)[1].strip()
break
if not base_home:
# fallback to parent of venv Scripts
base_home = os.path.dirname(os.path.dirname(paths["venv_python"]))
except Exception:
base_home = os.path.dirname(os.path.dirname(paths["venv_python"]))
site = os.path.join(paths["venv_root"], "Lib", "site-packages")
pypath = ";".join([
paths["borealis_dir"],
site,
os.path.join(site, "win32"),
os.path.join(site, "win32", "lib"),
os.path.join(site, "pywin32_system32"),
])
run(["reg", "add", fr"HKLM\\SYSTEM\\CurrentControlSet\\Services\\{service_name}\\PythonClass", "/ve", "/t", "REG_SZ", "/d", "windows_script_service.BorealisAgentService", "/f"]) # noqa
run(["reg", "add", fr"HKLM\\SYSTEM\\CurrentControlSet\\Services\\{service_name}\\PythonPath", "/ve", "/t", "REG_SZ", "/d", pypath, "/f"]) # noqa
run(["reg", "add", fr"HKLM\\SYSTEM\\CurrentControlSet\\Services\\{service_name}\\PythonHome", "/ve", "/t", "REG_SZ", "/d", base_home, "/f"]) # noqa
run(["sc.exe", "config", service_name, "obj=", "LocalSystem"]) # ensure LocalSystem
run(["sc.exe", "start", service_name])
# quick validate
qc = run(["sc.exe", "query", service_name], capture=True)
ok = (qc.returncode == 0)
log_write(paths, log_name, f"[INFO] ensure complete (ok={ok})")
return ok
except Exception as e:
log_write(paths, log_name, f"[ERROR] ensure (admin) failed: {e}")
return False
def ensure_user_logon_task(paths):
"""Ensure the per-user scheduled task that launches the agent in the user's session.
Name: "Borealis Agent"
Trigger: On logon (current user)
Action: <venv_python> -W ignore::SyntaxWarning <agent_script>
"""Ensure per-user scheduled task that launches the helper at logon.
Task name: "Borealis Agent"
"""
ensure_dirs(paths)
log_name = "Borealis_CollectorTask_Install.log"
task_name = "Borealis Agent"
# Use pythonw.exe to avoid opening a console window in the user's session
pyw = paths.get("venv_pythonw") or paths["venv_python"]
cmd = f"\"{pyw}\" -W ignore::SyntaxWarning \"{paths['agent_script']}\""
# If task exists, try remove (non-elevated first)
cmd = f'"{pyw}" -W ignore::SyntaxWarning "{paths["agent_script"]}"'
# Try create non-elevated
q = run(["schtasks.exe", "/Query", "/TN", task_name])
if q.returncode == 0:
d = run(["schtasks.exe", "/Delete", "/TN", task_name, "/F"], capture=True)
d = run(["schtasks.exe", "/Delete", "/TN", task_name, "/F"])
if d.returncode != 0:
# Attempt elevated deletion (task might have been created under admin previously)
ps = f"""
$ErrorActionPreference = 'SilentlyContinue'
try {{ schtasks.exe /Delete /TN "{task_name}" /F | Out-Null }} catch {{}}
"""
run_elevated_powershell(paths, ps, log_name)
c = run(["schtasks.exe", "/Create", "/SC", "ONLOGON", "/TN", task_name, "/TR", cmd, "/F", "/RL", "LIMITED"], capture=True)
log_write(paths, log_name, f"[INFO] create rc={c.returncode} out={c.stdout} err={c.stderr}")
if c.returncode != 0:
# Fallback: elevate and register task for the current user SID
user = os.environ.get("USERNAME", "")
domain = os.environ.get("USERDOMAIN", os.environ.get("COMPUTERNAME", ""))
py = (paths.get("venv_pythonw") or paths["venv_python"]).replace("\"", "\"\"")
ag = paths["agent_script"].replace("\"", "\"\"")
content = f"""
$ErrorActionPreference = 'Continue'
pass
c = run(["schtasks.exe", "/Create", "/SC", "ONLOGON", "/TN", task_name, "/TR", cmd, "/F", "/RL", "LIMITED"])
if c.returncode == 0:
run(["schtasks.exe", "/Run", "/TN", task_name])
return True
# Elevated fallback using ScheduledTasks cmdlets for better reliability
ps = f"""
$ErrorActionPreference='Continue'
$task = "{task_name}"
$py = "{py}"
$arg = "-W ignore::SyntaxWarning {ag}"
$cmd = '"' + $py + '" ' + $arg
$user = "{domain}\{user}"
try {{
# Resolve user SID
$sid = (New-Object System.Security.Principal.NTAccount($user)).Translate([System.Security.Principal.SecurityIdentifier]).Value
}} catch {{ $sid = $null }}
try {{
# Delete any existing task (any scope)
try {{ schtasks.exe /Delete /TN $task /F | Out-Null }} catch {{}}
$action = New-ScheduledTaskAction -Execute $py -Argument $arg
$trigger = New-ScheduledTaskTrigger -AtLogOn
$settings = New-ScheduledTaskSettingsSet -Hidden
if ($sid) {{
$principal = New-ScheduledTaskPrincipal -UserId $sid -LogonType Interactive -RunLevel Limited
Register-ScheduledTask -TaskName $task -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
}} else {{
# Fallback: bind by username (use Interactive to avoid password)
$principal = New-ScheduledTaskPrincipal -UserId "{domain}\{user}" -LogonType Interactive -RunLevel Limited
Register-ScheduledTask -TaskName $task -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
}}
}} catch {{
"[ERROR] Task register failed: $_" | Out-File -FilePath "{os.path.join(paths['logs_dir'], log_name)}" -Append -Encoding UTF8
exit 1
}}
$py = "{pyw}"
$arg = "-W ignore::SyntaxWarning {paths['agent_script']}"
try {{ Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}}
$action = New-ScheduledTaskAction -Execute $py -Argument $arg
$trigger= New-ScheduledTaskTrigger -AtLogOn
$settings = New-ScheduledTaskSettingsSet -Hidden
Register-ScheduledTask -TaskName $task -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null
Start-ScheduledTask -TaskName $task | Out-Null
"""
rc = run_elevated_powershell(paths, content, log_name)
if rc != 0:
return False
else:
# Created via schtasks; set Hidden=true via elevated PowerShell (schtasks lacks a /HIDDEN switch)
ps_hide = f"""
$ErrorActionPreference = 'SilentlyContinue'
try {{
$settings = New-ScheduledTaskSettingsSet -Hidden
Set-ScheduledTask -TaskName "{task_name}" -Settings $settings | Out-Null
}} catch {{}}
"""
run_elevated_powershell(paths, ps_hide, log_name)
# Start immediately (if a session is active)
run(["schtasks.exe", "/Run", "/TN", task_name])
return True
rc = run_elevated_powershell(paths, ps, "Borealis_CollectorTask_Install.log")
return rc == 0
def ensure_all():
paths = project_paths()
ensure_dirs(paths)
ok_svc = ensure_script_service(paths)
# Service now launches per-session helper; scheduled task is not required.
return 0 if ok_svc else 1
ok = ensure_user_logon_task(paths)
return 0 if ok else 1
def main(argv):
# Simple CLI
if len(argv) <= 1:
print("Usage: agent_deployment.py [ensure-all|service-install|service-remove|task-ensure|task-remove]")
print("Usage: agent_deployment.py [ensure-all|task-ensure|task-remove]")
return 2
cmd = argv[1].lower()
paths = project_paths()
ensure_dirs(paths)
if cmd == "ensure-all":
return ensure_all()
if cmd == "service-install":
return 0 if ensure_script_service(paths) else 1
if cmd == "service-remove":
name = "BorealisAgent"
if not is_admin():
ps = f"try {{ sc.exe stop {name} }} catch {{}}; try {{ sc.exe delete {name} }} catch {{}}"
return run_elevated_powershell(paths, ps, "Borealis_ScriptService_Remove.log")
run(["sc.exe", "stop", name])
r = run(["sc.exe", "delete", name])
return r.returncode
if cmd == "task-ensure":
return 0 if ensure_user_logon_task(paths) else 1
if cmd == "task-remove":
tn = "Borealis Agent"
r = run(["schtasks.exe", "/Delete", "/TN", tn, "/F"])
return r.returncode
return run(["schtasks.exe", "/Delete", "/TN", "Borealis Agent", "/F"]).returncode
print(f"Unknown command: {cmd}")
return 2
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -0,0 +1,169 @@
import os
import sys
import time
import subprocess
import threading
import datetime
# Optional pywin32 imports for per-session launching
try:
import win32ts
import win32con
import win32process
import win32security
import win32profile
import win32api
import pywintypes
except Exception:
win32ts = None
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
AGENT_DIR = os.path.join(ROOT, 'Agent')
BOREALIS_DIR = os.path.join(AGENT_DIR, 'Borealis')
LOG_DIR = os.path.join(ROOT, 'Logs', 'Agent')
os.makedirs(LOG_DIR, exist_ok=True)
LOG_FILE = os.path.join(LOG_DIR, 'Supervisor.log')
def log(msg: str):
try:
ts = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"[{ts}] {msg}\n")
except Exception:
pass
def venv_python():
try:
exe_dir = os.path.join(AGENT_DIR, 'Scripts')
py = os.path.join(exe_dir, 'python.exe')
if os.path.isfile(py):
return py
except Exception:
pass
return sys.executable
def venv_pythonw():
try:
exe_dir = os.path.join(AGENT_DIR, 'Scripts')
pyw = os.path.join(exe_dir, 'pythonw.exe')
if os.path.isfile(pyw):
return pyw
except Exception:
pass
return venv_python()
def ensure_script_agent():
"""Ensure LocalSystem script_agent.py is running; restart if not."""
try:
# best-effort: avoid duplicate spawns
import psutil # type: ignore
for p in psutil.process_iter(['name', 'cmdline']):
try:
cl = (p.info.get('cmdline') or [])
if any('script_agent.py' in (part or '') for part in cl):
return
except Exception:
pass
except Exception:
pass
py = venv_python()
script = os.path.join(ROOT, 'Data', 'Agent', 'script_agent.py')
try:
subprocess.Popen([py, '-W', 'ignore::SyntaxWarning', script], creationflags=(0x08000000 if os.name == 'nt' else 0))
log('Launched script_agent.py')
except Exception as e:
log(f'Failed to launch script_agent.py: {e}')
def _enable_privileges():
try:
hProc = win32api.GetCurrentProcess()
hTok = win32security.OpenProcessToken(hProc, win32con.TOKEN_ADJUST_PRIVILEGES | win32con.TOKEN_QUERY)
for name in [
win32security.SE_ASSIGNPRIMARYTOKEN_NAME,
win32security.SE_INCREASE_QUOTA_NAME,
win32security.SE_TCB_NAME,
win32security.SE_BACKUP_NAME,
win32security.SE_RESTORE_NAME,
]:
try:
luid = win32security.LookupPrivilegeValue(None, name)
win32security.AdjustTokenPrivileges(hTok, False, [(luid, win32con.SE_PRIVILEGE_ENABLED)])
except Exception:
pass
except Exception:
pass
def active_sessions():
ids = []
try:
if win32ts is None:
return ids
for s in win32ts.WTSEnumerateSessions(None, 1, 0):
sid, _, state = s
if state == win32ts.WTSActive:
ids.append(sid)
except Exception:
pass
return ids
def launch_helper_in_session(session_id):
try:
if win32ts is None:
return False
_enable_privileges()
hUser = win32ts.WTSQueryUserToken(session_id)
primary = win32security.DuplicateTokenEx(
hUser,
win32con.MAXIMUM_ALLOWED,
win32security.SECURITY_ATTRIBUTES(),
win32security.SecurityImpersonation,
win32con.TOKEN_PRIMARY,
)
env = win32profile.CreateEnvironmentBlock(primary, True)
si = win32process.STARTUPINFO()
si.lpDesktop = 'winsta0\\default'
cmd = f'"{venv_pythonw()}" -W ignore::SyntaxWarning "{os.path.join(BOREALIS_DIR, "borealis-agent.py")}"'
flags = getattr(win32con, 'CREATE_UNICODE_ENVIRONMENT', 0x00000400)
win32process.CreateProcessAsUser(primary, None, cmd, None, None, False, flags, env, BOREALIS_DIR, si)
log(f'Started user helper in session {session_id}')
return True
except Exception as e:
log(f'Failed to start helper in session {session_id}: {e}')
return False
def manage_user_helpers_loop():
known = set()
while True:
try:
cur = set(active_sessions())
for sid in cur:
if sid not in known:
launch_helper_in_session(sid)
known = cur
except Exception:
pass
time.sleep(3)
def main():
log('Supervisor starting')
t = threading.Thread(target=manage_user_helpers_loop, daemon=True)
t.start()
while True:
ensure_script_agent()
time.sleep(5)
if __name__ == '__main__':
main()

View File

@@ -998,18 +998,22 @@ async def on_quick_job_run(payload):
content = payload.get('script_content') or ''
# Only handle non-SYSTEM runs here; SYSTEM runs are handled by the LocalSystem service agent
if run_mode == 'system':
# Optionally, could emit a status indicating delegation; for now, just ignore to avoid overlap
# Ignore: handled by SYSTEM supervisor/agent
return
if script_type != 'powershell':
await sio.emit('quick_job_result', { 'job_id': job_id, 'status': 'Failed', 'stdout': '', 'stderr': f"Unsupported type: {script_type}" })
return
path = _write_temp_script(content, '.ps1')
rc = 0; out = ''; err = ''
if run_mode == 'admin':
# Admin credentialed runs are disabled in current design
rc, out, err = -1, '', 'Admin credentialed runs are disabled; use SYSTEM (service) or Current User.'
rc, out, err = -1, '', 'Admin credentialed runs are disabled; use SYSTEM or Current User.'
else:
rc, out, err = await _run_powershell_local(path)
# Prefer ephemeral scheduled task in current user context
rc, out, err = await _run_powershell_via_user_task(content)
if rc == -999:
# Fallback to direct execution
path = _write_temp_script(content, '.ps1')
rc, out, err = await _run_powershell_local(path)
status = 'Success' if rc == 0 else 'Failed'
await sio.emit('quick_job_result', {
'job_id': job_id,
@@ -1053,6 +1057,60 @@ async def idle_task():
print(f"[FATAL] Idle task crashed: {e}")
traceback.print_exc()
async def _run_powershell_via_user_task(content: str):
ps = None
if IS_WINDOWS:
ps = os.path.expandvars(r"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe")
if not os.path.isfile(ps):
ps = "powershell.exe"
else:
return -999, '', 'Windows only'
try:
temp_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'Temp')
temp_dir = os.path.abspath(temp_dir)
os.makedirs(temp_dir, exist_ok=True)
fd, path = tempfile.mkstemp(prefix='usr_task_', suffix='.ps1', dir=temp_dir, text=True)
with os.fdopen(fd, 'w', encoding='utf-8', newline='\n') as f:
f.write(content or '')
out_path = os.path.join(temp_dir, f'out_{uuid.uuid4().hex}.txt')
name = f"Borealis Agent - Task - {uuid.uuid4().hex} @ CurrentUser"
task_ps = f"""
$ErrorActionPreference='Continue'
$task = "{name}"
$ps = "{ps}"
$scr = "{path}"
$out = "{out_path}"
try {{ Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}}
$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -File "' + $scr + '" *> "' + $out + '"')
$settings = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$principal= New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Limited
Register-ScheduledTask -TaskName $task -Action $action -Settings $settings -Principal $principal -Force | Out-Null
Start-ScheduledTask -TaskName $task | Out-Null
Start-Sleep -Seconds 2
Get-ScheduledTask -TaskName $task | Out-Null
"""
proc = await asyncio.create_subprocess_exec(ps, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', task_ps, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
await proc.communicate()
if proc.returncode != 0:
return -999, '', 'failed to create user task'
# Wait for output
deadline = time.time() + 60
out_data = ''
while time.time() < deadline:
try:
if os.path.isfile(out_path) and os.path.getsize(out_path) > 0:
with open(out_path, 'r', encoding='utf-8', errors='replace') as f:
out_data = f.read()
break
except Exception:
pass
await asyncio.sleep(1)
cleanup = f"try {{ Unregister-ScheduledTask -TaskName '{name}' -Confirm:$false }} catch {{}}"
await asyncio.create_subprocess_exec(ps, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', cleanup)
return 0, out_data or '', ''
except Exception as e:
return -999, '', str(e)
# ---------------- Dummy Qt Widget to Prevent Exit ----------------
class PersistentWindow(QtWidgets.QWidget):
def __init__(self):

View File

@@ -10,6 +10,8 @@ import tempfile
import socketio
import platform
import time
import uuid
import tempfile
def get_project_root():
@@ -89,7 +91,11 @@ async def main():
'stderr': f"Unsupported type: {script_type}"
})
return
rc, out, err = run_powershell_script_content(content)
# Preferred: run via ephemeral scheduled task under SYSTEM for isolation
rc, out, err = run_powershell_via_system_task(content)
if rc == -999:
# Fallback to direct execution if task creation not available
rc, out, err = run_powershell_script_content(content)
status = 'Success' if rc == 0 else 'Failed'
await sio.emit('quick_job_result', {
'job_id': job_id,
@@ -144,5 +150,61 @@ async def main():
await asyncio.sleep(5)
def run_powershell_via_system_task(content: str):
"""Create an ephemeral scheduled task under SYSTEM to run the script.
Returns (rc, stdout, stderr). If the environment lacks PowerShell ScheduledTasks module, returns (-999, '', 'unavailable').
"""
ps_exe = os.path.expandvars(r"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe")
if not os.path.isfile(ps_exe):
ps_exe = 'powershell.exe'
try:
os.makedirs(os.path.join(get_project_root(), 'Temp'), exist_ok=True)
# Write the target script
script_fd, script_path = tempfile.mkstemp(prefix='sys_task_', suffix='.ps1', dir=os.path.join(get_project_root(), 'Temp'), text=True)
with os.fdopen(script_fd, 'w', encoding='utf-8', newline='\n') as f:
f.write(content or '')
# Output capture path
out_path = os.path.join(get_project_root(), 'Temp', f'out_{uuid.uuid4().hex}.txt')
task_name = f"Borealis Agent - Task - {uuid.uuid4().hex} @ SYSTEM"
# Build PS to create/run task with DeleteExpiredTaskAfter
task_ps = f"""
$ErrorActionPreference='Continue'
$task = "{task_name}"
$ps = "{ps_exe}"
$scr = "{script_path}"
$out = "{out_path}"
try {{ Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}}
$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -File "' + $scr + '" *> "' + $out + '"')
$settings = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$principal= New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName $task -Action $action -Settings $settings -Principal $principal -Force | Out-Null
Start-ScheduledTask -TaskName $task | Out-Null
Start-Sleep -Seconds 2
Get-ScheduledTask -TaskName $task | Out-Null
"""
# Run task creation
proc = subprocess.run([ps_exe, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', task_ps], capture_output=True, text=True)
if proc.returncode != 0:
return -999, '', (proc.stderr or proc.stdout or 'scheduled task creation failed')
# Wait up to 60s for output to be written
deadline = time.time() + 60
out_data = ''
while time.time() < deadline:
try:
if os.path.isfile(out_path) and os.path.getsize(out_path) > 0:
with open(out_path, 'r', encoding='utf-8', errors='replace') as f:
out_data = f.read()
break
except Exception:
pass
time.sleep(1)
# Cleanup task (best-effort)
cleanup_ps = f"try {{ Unregister-ScheduledTask -TaskName '{task_name}' -Confirm:$false }} catch {{}}"
subprocess.run([ps_exe, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', cleanup_ps], capture_output=True, text=True)
return 0, out_data or '', ''
except Exception as e:
return -999, '', str(e)
if __name__ == '__main__':
asyncio.run(main())

View File

@@ -37,14 +37,12 @@ class TrayApp(QtWidgets.QSystemTrayIcon):
self.action_show_console = self.menu.addAction('Switch to Foreground Mode')
self.action_hide_console = self.menu.addAction('Switch to Background Mode')
self.action_restart = self.menu.addAction('Restart Agent')
self.action_restart_service = self.menu.addAction('Restart Borealis Agent Service')
self.menu.addSeparator()
self.action_quit = self.menu.addAction('Quit Agent and Tray')
self.action_show_console.triggered.connect(self.switch_to_console)
self.action_hide_console.triggered.connect(self.switch_to_background)
self.action_restart.triggered.connect(self.restart_agent)
self.action_restart_service.triggered.connect(self.restart_script_service)
self.action_quit.triggered.connect(self.quit_all)
self.setContextMenu(self.menu)
@@ -101,35 +99,7 @@ class TrayApp(QtWidgets.QSystemTrayIcon):
# Restart using current mode
self._start_agent(console=self.console_mode)
def restart_script_service(self):
# Try direct stop/start; if fails (likely due to permissions), attempt elevation via PowerShell
service_name = 'BorealisAgent'
try:
# Stop service
subprocess.run(["sc.exe", "stop", service_name], check=False, capture_output=True)
# Start service
subprocess.run(["sc.exe", "start", service_name], check=False, capture_output=True)
return
except Exception:
pass
# Fallback: elevate via PowerShell
try:
script = (
f"$ErrorActionPreference='Continue'; "
f"try {{ Stop-Service -Name '{service_name}' -Force -ErrorAction SilentlyContinue }} catch {{}}; "
f"Start-Sleep -Seconds 1; "
f"try {{ Start-Service -Name '{service_name}' }} catch {{}};"
)
# Start-Process PowerShell -Verb RunAs to elevate
ps_cmd = [
'powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass',
'-Command',
"Start-Process PowerShell -Verb RunAs -ArgumentList '-NoProfile -ExecutionPolicy Bypass -Command \"" + script.replace("\"", "\\\"") + "\"'"
]
subprocess.Popen(ps_cmd)
except Exception:
pass
# Service controls removed in task-centric architecture
def quit_all(self):
self._stop_agent()

View File

@@ -1,274 +0,0 @@
import win32serviceutil
import win32service
import win32event
import servicemanager
import subprocess
import os
import sys
import datetime
import threading
# Session/process helpers for per-user helper launch
try:
import win32ts
import win32con
import win32process
import win32security
import win32profile
import win32api
except Exception:
win32ts = None
class BorealisAgentService(win32serviceutil.ServiceFramework):
_svc_name_ = "BorealisAgent"
_svc_display_name_ = "Borealis Agent"
_svc_description_ = "Background agent for data collection and remote script execution."
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
self.proc = None
self.user_helpers = {}
self.helpers_thread = None
try:
self._log("Service initialized")
except Exception:
pass
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
try:
self._log("Stop requested")
except Exception:
pass
try:
if self.proc and self.proc.poll() is None:
try:
self.proc.terminate()
except Exception:
pass
except Exception:
pass
# Stop user helpers
try:
for sid, h in list(self.user_helpers.items()):
try:
hp = h.get('hProcess')
if hp:
win32api.TerminateProcess(hp, 0)
except Exception:
pass
except Exception:
pass
win32event.SetEvent(self.hWaitStop)
def SvcDoRun(self):
try:
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
except Exception:
pass
try:
self._log("SvcDoRun entered")
except Exception:
pass
# Mark the service as running once initialized
try:
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
except Exception:
pass
self.main()
def main(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
agent_py = os.path.join(script_dir, 'script_agent.py')
def _venv_python():
try:
exe_dir = os.path.dirname(sys.executable)
py = os.path.join(exe_dir, 'python.exe')
if os.path.isfile(py):
return py
except Exception:
pass
return sys.executable
def _start_script_agent():
python = _venv_python()
try:
self._log(f"Launching script_agent via {python}")
return subprocess.Popen(
[python, '-W', 'ignore::SyntaxWarning', agent_py],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=0x08000000 if os.name == 'nt' else 0
)
except Exception as e:
try:
self._log(f"Failed to start script_agent: {e}")
except Exception:
pass
return None
# Launch the system script agent (and keep it alive)
self.proc = _start_script_agent()
# Start per-user helper manager in a background thread
if win32ts is not None:
try:
self.helpers_thread = threading.Thread(target=self._manage_user_helpers_loop, daemon=True)
self.helpers_thread.start()
except Exception:
try:
self._log("Failed to start user helper manager thread")
except Exception:
pass
# Monitor stop event and child process; restart child if it exits
while True:
rc = win32event.WaitForSingleObject(self.hWaitStop, 2000)
if rc == win32event.WAIT_OBJECT_0:
break
try:
if not self.proc or (self.proc and self.proc.poll() is not None):
# child exited; attempt restart
self._log("script_agent exited; attempting restart")
self.proc = _start_script_agent()
except Exception:
pass
def _log(self, msg: str):
try:
root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
logs = os.path.join(root, 'Logs')
os.makedirs(logs, exist_ok=True)
p = os.path.join(logs, 'AgentService_Runtime.log')
ts = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
with open(p, 'a', encoding='utf-8') as f:
f.write(f"{ts} {msg}\n")
except Exception:
pass
# ---------------------- Per-Session User Helper Management ----------------------
def _enable_privileges(self):
try:
hProc = win32api.GetCurrentProcess()
hTok = win32security.OpenProcessToken(
hProc,
win32con.TOKEN_ADJUST_PRIVILEGES | win32con.TOKEN_QUERY,
)
for name in [
win32security.SE_ASSIGNPRIMARYTOKEN_NAME,
win32security.SE_INCREASE_QUOTA_NAME,
win32security.SE_TCB_NAME,
win32security.SE_BACKUP_NAME,
win32security.SE_RESTORE_NAME,
]:
try:
luid = win32security.LookupPrivilegeValue(None, name)
win32security.AdjustTokenPrivileges(
hTok, False, [(luid, win32con.SE_PRIVILEGE_ENABLED)]
)
except Exception:
pass
except Exception:
pass
def _active_session_ids(self):
ids = []
try:
sessions = win32ts.WTSEnumerateSessions(None, 1, 0)
for s in sessions:
# tuple: (SessionId, WinStationName, State)
sess_id, _, state = s
if state == win32ts.WTSActive:
ids.append(sess_id)
except Exception:
pass
return ids
def _launch_helper_in_session(self, session_id):
try:
self._enable_privileges()
# User token for session
hUser = win32ts.WTSQueryUserToken(session_id)
# Duplicate to primary
primary = win32security.DuplicateTokenEx(
hUser,
win32con.MAXIMUM_ALLOWED,
win32security.SECURITY_ATTRIBUTES(),
win32security.SecurityImpersonation,
win32con.TOKEN_PRIMARY,
)
env = win32profile.CreateEnvironmentBlock(primary, True)
startup = win32process.STARTUPINFO()
startup.lpDesktop = "winsta0\\default"
# Compute pythonw + helper script
venv_dir = os.path.dirname(sys.executable) # e.g., Agent
pyw = os.path.join(venv_dir, 'pythonw.exe')
agent_dir = os.path.dirname(os.path.abspath(__file__))
helper = os.path.join(agent_dir, 'borealis-agent.py')
cmd = f'"{pyw}" -W ignore::SyntaxWarning "{helper}"'
flags = getattr(win32con, 'CREATE_NEW_PROCESS_GROUP', 0)
flags |= getattr(win32con, 'CREATE_UNICODE_ENVIRONMENT', 0x00000400)
proc_tuple = win32process.CreateProcessAsUser(
primary,
None,
cmd,
None,
None,
False,
flags,
env,
agent_dir,
startup,
)
# proc_tuple: (hProcess, hThread, dwProcessId, dwThreadId)
self.user_helpers[session_id] = {
'hProcess': proc_tuple[0],
'hThread': proc_tuple[1],
'pid': proc_tuple[2],
'started': datetime.datetime.now(),
}
self._log(f"Started user helper in session {session_id}")
return True
except Exception as e:
try:
self._log(f"Failed to start helper in session {session_id}: {e}")
except Exception:
pass
return False
def _manage_user_helpers_loop(self):
# Periodically ensure one user helper per active session
while True:
rc = win32event.WaitForSingleObject(self.hWaitStop, 2000)
if rc == win32event.WAIT_OBJECT_0:
break
try:
active = set(self._active_session_ids())
# Start missing
for sid in active:
if sid not in self.user_helpers:
self._launch_helper_in_session(sid)
# Cleanup ended
for sid in list(self.user_helpers.keys()):
if sid not in active:
info = self.user_helpers.pop(sid, None)
try:
hp = info and info.get('hProcess')
if hp:
win32api.TerminateProcess(hp, 0)
except Exception:
pass
self._log(f"Cleaned helper for session {sid}")
except Exception:
pass
if __name__ == '__main__':
win32serviceutil.HandleCommandLine(BorealisAgentService)