Files
claudetools/projects/msp-tools/guru-scan/session-logs/2026-05-27-session.md
Howard Enos d5a352e43d sync: auto-sync from HOWARD-HOME at 2026-05-27 06:58:33
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-05-27 06:58:33
2026-05-27 06:58:39 -07:00

10 KiB

User

  • User: Howard Enos (howard)
  • Machine: Howard-Home
  • Role: tech

Session Summary

The session focused on verifying and extending the GuruScan pipeline for the GuruRMM platform. The GURUSCAN_RESULT_JSON pipeline was confirmed end-to-end: the launcher script reads results.json after the scan, compresses it into a single-line JSON, and emits it as GURUSCAN_RESULT_JSON:<json> to stdout. The GuruRMM agent captures this in command.stdout. This had been implemented in a prior session and was verified working with scan 2c531967.

AdwCleaner integration was thoroughly tested and found to be incompatible with both GuruRMM dispatch contexts. Dispatching in user_session context failed with ERROR_CANCELLED — the WTS token from WTSQueryUserToken is a non-elevated standard user token, and AdwCleaner's manifest requests administrator; CreateProcess returns the error immediately without showing a UAC dialog. Dispatching in system context caused the process to hang indefinitely before the scan started — AdwCleaner initializes its GUI framework before accepting CLI args, and in Session 0 (SYSTEM, no window station) that initialization blocks forever. Both paths were tested directly against the VM. AdwCleaner was removed from scanners.json pending a future schtasks InteractiveToken + HighestAvailable dispatch mechanism.

A critical 3-hour pipe hang bug was discovered via a background task notification. A full GuruScan command dispatched at 05:51 UTC (scan RMM-TEST-MACHIN-20260526-225146) completed its scan work in 6.5 minutes — results.json was written at 05:58 — but the GuruRMM command stayed running for 3 hours until the server-side reaper killed it. Root cause: NoNewWindow = [bool]$Headless in Invoke-ScanPass caused all scanner processes to share the parent PowerShell's console when running headless. Sharing the console means child processes inherit the parent's stdout/stderr pipe handles. AdwCleaner started (or partially started) in Session 0, inherited the pipe handle, then hung. After PowerShell exited, AdwCleaner still held the write end of the stdout pipe open, so the GuruRMM agent's read side never saw EOF and waited indefinitely.

The fix was to replace NoNewWindow = [bool]$Headless with WindowStyle = 'Hidden' when $Headless is set. This gives each scanner its own window/console, preventing pipe handle inheritance entirely. Any scanner that hangs will be killed by Wait-ProcessWithTimeout at its configured timeout; the overall GuruScan process exits normally regardless. Verification scan 84059ee7 (dispatched post-fix) completed in 7.5 minutes with EICAR test file detected and removed by Emsisoft, GURUSCAN_RESULT_JSON captured in stdout, no pipe hang. Documentation was updated to reflect the 3-scanner chain and the corrected headless behavior description.


Key Decisions

  • Removed AdwCleaner from scanners.json: AdwCleaner needs elevated token AND interactive desktop simultaneously. system context provides elevation but no desktop (Session 0). user_session provides desktop but a non-elevated WTS token. Neither context satisfies both requirements. Will be re-added when a schtasks InteractiveToken + HighestAvailable dispatch path is implemented in GuruScan.psm1.

  • WindowStyle = 'Hidden' instead of NoNewWindow: NoNewWindow shares the parent console and causes child processes to inherit the PowerShell pipe handles. WindowStyle = 'Hidden' creates an independent process with its own window/console — no handle inheritance. The only visible behavioral difference is that scanners no longer write their output to the RMM agent's pipe (which was never used anyway — GuruScan collects output via log files).

  • AdwCleaner re-add path is schtasks with InteractiveToken: From SYSTEM context, create a task XML with <LogonType>InteractiveToken</LogonType> and <RunLevel>HighestAvailable</RunLevel>. This creates the process as the currently-logged-in user at their highest privilege level, in their desktop session. If the user is a local admin this gets an elevated token with a visible window — no UAC prompt because Task Scheduler handles the elevation internally.

  • Kept AdwCleaner exit-code mappings in GuruScan.psm1: The Get-ExitCodeThreats and Get-ExitCodeReboot functions retain AdwCleaner cases. No harm in keeping them, and they'll be needed when AdwCleaner is re-added.


Problems Encountered

  • AdwCleaner user_session dispatch: ERROR_CANCELLED: PowerShell's & operator uses CreateProcess, which cannot handle elevation requests from a non-elevated token. Returns ERROR_CANCELLED (0x800704C7) immediately, no UAC dialog. Resolution: documented as architecture limitation; using Start-Process -Verb RunAs would allow the UAC prompt to appear on the user's desktop, but requires user interaction. Removed AdwCleaner for now.

  • AdwCleaner system context: process hung indefinitely: Even with WindowStyle = 'Hidden' (tested directly), AdwCleaner enters its GUI initialization loop before processing CLI args. In Session 0 there is no window station; the initialization blocks forever. Confirmed by checking VM process list and scan logs — no [S01].txt created during today's system-context attempts, and the only existing log ([S00].txt from 2026-05-26) was from a prior non-SYSTEM run. Resolution: same as above.

  • 3-hour pipe hang on GuruRMM command 0d05681f: Background monitor reported the command ran for 180 minutes before server-side reaper. Scan itself completed in 6.5 min (verified via results.json timestamps). Root cause: NoNewWindow = [bool]$Headless caused pipe handle inheritance; AdwCleaner (or a residual process from it) held the write end of the stdout pipe after PowerShell exited. Fix: WindowStyle = 'Hidden'. Verified with next scan (84059ee7), completed in 7.5 min with no hang.

  • Vault path resolution for JWT token: First attempt used projects/gururmm/api.sops.yaml which doesn't exist. Correct path is infrastructure/gururmm-server.sops.yaml, field credentials.gururmm-api.admin-password. Used login endpoint /api/auth/login with claude-api@azcomputerguru.com to obtain JWT.


Configuration Changes

  • projects/msp-tools/guru-scan/GuruScan.psm1 — 5 changes:
    • NoNewWindow = [bool]$HeadlessWindowStyle = 'Hidden' when $Headless (pipe hang fix)
    • Headless param docstring corrected ("WindowStyle=Hidden", not "NoNewWindow")
    • Whitelist comment: removed AdwCleaner ("Emsisoft and HitmanPro only")
    • $defenderExclusions: removed 'C:\AdwCleaner'
    • Invoke-Remediation example: AdwCleaner,MSERTEmsisoft,MSERT
  • projects/msp-tools/guru-scan/scanners.json — AdwCleaner entry removed entirely
  • projects/msp-tools/guru-scan/README.md — full rewrite for 3-scanner chain; added GuruRMM integration section with GURUSCAN_RESULT_JSON structure; fixed Headless description; removed AdwCleaner from all tables; added note on AdwCleaner re-add path

Credentials & Secrets

No new credentials created or discovered. GuruRMM API access used existing vault entry:

  • Vault: infrastructure/gururmm-server.sops.yamlcredentials.gururmm-api.admin-email / admin-password
  • Login endpoint: POST http://172.16.3.30:3001/api/auth/login

Infrastructure & Servers

  • GuruRMM server: 172.16.3.30:3001
  • RMM-TEST-MACHINE: agent ID 7d3456f5-d811-4eac-8629-d88e9d1c594a, VM at 172.20.12.192, online
  • HTTP deploy server: 172.20.0.1:8888 (Python, serving C:/claudetools/projects/msp-tools/guru-scan/)

Commands & Outputs

# GuruRMM dispatch (system context, post-fix)
C:\GuruScan\Invoke-GuruScan.ps1 -Headless
# → completed in 7.5 min, GURUSCAN_RESULT_JSON captured, no pipe hang
# → EICAR test file C:\Users\User\Downloads\eicar.com_.txt detected by Emsisoft, removed

Verify fix deployed on VM:

Get-Content 'C:\GuruScan\GuruScan.psm1' | Select-String 'WindowStyle|NoNewWindow'
# Output: WindowStyle = 'Hidden'  (no NoNewWindow line in scanner launch block)

Scan result from verification run (command 84059ee7):

{
  "scan_id": "RMM-TEST-MACHIN-20260527-015912",
  "total_threats": 1,
  "scanners": [
    { "name": "RKill",    "status": "completed", "exit_code": 1, "duration_min": 0.09 },
    { "name": "AdwCleaner","status": "SKIPPED (requires user session)" },
    { "name": "Emsisoft", "status": "completed", "exit_code": 1, "duration_min": 4.86 },
    { "name": "HitmanPro","status": "completed", "exit_code": 0, "duration_min": 2.25 }
  ]
}

(AdwCleaner was still in scanners.json at time of this scan; removed immediately after.)


Pending / Incomplete Tasks

  • AdwCleaner re-add via schtasks InteractiveToken: Implement in GuruScan.psm1. When session0_compatible: false and running as SYSTEM, instead of skipping, create a task XML with <LogonType>InteractiveToken</LogonType> + <RunLevel>HighestAvailable</RunLevel>, register + trigger, poll for log file as completion signal, unregister. No GuruRMM agent changes required.
  • GuruRMM dashboard UI: Parse GURUSCAN_RESULT_JSON from command.stdout and display per-scanner results on the agent detail page. Separate GuruRMM feature.
  • GURUSCAN_RESULT_JSON when SYSTEM detection causes old version mismatch: The 2026-05-26 22:51 scan showed AdwCleaner as FAILED (not SKIPPED), suggesting the VM had a module version without the session0_compatible check at that time. Monitor for recurrence; current deployed version is correct.

Reference Information

  • Commits this session:
    • a980ef1 — fix: use WindowStyle=Hidden instead of NoNewWindow in headless scanner dispatch
    • a655b52 — chore: remove AdwCleaner from scanner chain
    • 47517e9 — docs: update GuruScan README and module comments for current state
  • GuruRMM API base: http://172.16.3.30:3001/api
  • GuruRMM login endpoint: POST /api/auth/login (body: email, password)
  • Test machine agent ID: 7d3456f5-d811-4eac-8629-d88e9d1c594a
  • Scan log dir: C:\ScanLogs\ on VM
  • EICAR test file on VM: C:\Users\User\Downloads\eicar.com_.txt
  • AdwCleaner CLI reference: https://help.malwarebytes.com/hc/en-us/articles/31589247152027-Command-line-options-for-AdwCleaner
  • Wait-ProcessWithTimeout in GuruScan.psm1:69 — timeout-kills processes exceeding timeout_min
  • Test-RunningAsSystem in GuruScan.psm1:684 — uses WindowsIdentity.IsSystem