Files
claudetools/projects/msp-tools/guru-scan/README.md
Howard Enos 40e090c95a feat(guru-scan): fix exit code capture, add GURUSCAN_RESULT_JSON reporting, pre-scan hardening
Exit code fix: add $proc.Handle caching after Start-Process -PassThru to prevent
the handle from being released before ExitCode is readable (known PS5.1 bug).

GuruRMM reporting: launcher now finds results.json after each scan and emits
GURUSCAN_RESULT_JSON:<compressed> to stdout. Agent CommandResult captures it;
server stores it in commands.stdout for retrieval via GET /api/commands/:id.

Pre-scan hardening:
- Pre-flight EXE check: warns about missing scanner binaries before run starts
- Windows Defender exclusions added for scanner/log paths before scan, removed after

AdwCleaner: add /path {LOG_ROOT} arg so logs write directly to scan log root;
update log_src to {LOG_ROOT}\Logs to match.

HitmanPro: add /quiet to scan and clean args to suppress GUI in headless runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:13:16 -07:00

168 lines
6.7 KiB
Markdown

# GuruScan
Multi-engine malware scan orchestrator for GuruRMM. Runs a sequenced chain of
portable security scanners, captures all logs, and writes structured JSON results
for downstream processing by the RMM agent.
---
## Deploy Layout
| Path | Purpose |
|------|---------|
| `C:\GuruScan\` | Base directory — module files, whitelist, state files |
| `C:\GuruScan\downloads\` | Scanner EXEs (populated by `Download-Scanners.ps1`) |
| `C:\GuruScan\reports\` | Per-scan zip archives |
| `C:\ScanLogs\` | Per-scan log folders (`<HOSTNAME>-<YYYYMMDD-HHmmss>\`) |
The module files (`GuruScan.psm1`, `GuruScan.psd1`, `scanners.json`) live in the
same directory as the launcher scripts (`Invoke-GuruScan.ps1`, etc.). This is
typically `C:\GuruScan\` or the RMM deployment path — wherever the scripts are placed.
---
## Scanner Chain
Scanners run in this order. Each stage hands off to the next regardless of findings.
| # | Scanner | Category | Notes |
|---|---------|----------|-------|
| 1 | **RKill** | process-killer | Kills malware-related processes before scanners run. Exit 1 = processes were killed (not a threat indicator). |
| 2 | **AdwCleaner** | adware | Removes adware, PUPs, and browser hijackers. **Requires an interactive user session** (GUI app; no headless/SYSTEM mode). Skipped automatically when running as SYSTEM with no desktop. To include AdwCleaner, dispatch via GuruRMM with `context: user_session`. |
| 3 | **Emsisoft Command Line Scanner** | antimalware | Two-step: NSIS installer extracts to `C:\EmsisoftCmd\`, then `/update` fetches latest definitions, then scans. |
| 4 | **HitmanPro** | antimalware | Cloud-assisted second-opinion scanner. Trial registry is reset before each run via `Invoke-HitmanProTrialReset`. |
MSERT (Microsoft Safety Scanner) is excluded from the default chain — it is too slow
for routine remediation runs. Add it back to `scanners.json` if needed.
---
## Exit Code Semantics
| Scanner | Exit 0 | Exit 1 | Exit 2 | Exit 3 | Other |
|---------|--------|--------|--------|--------|-------|
| RKill | Clean run | Processes killed (not a threat) | — | — | — |
| AdwCleaner | Clean | Cleaned, no reboot needed | Cleaned, reboot required | Found but not cleaned (scan-only) | — |
| Emsisoft | Clean | Threats found/cleaned | Cleaned, reboot required | — | — |
| HitmanPro | Clean | Cleaned | Cleaned, reboot required | — | — |
| MSERT | Clean | Threats found/cleaned | — | — | Non-zero = threats |
| TDSSKiller | Clean | Threats found | — | — | — |
| Stinger | Clean | — | — | — | 13 = threats |
Reboot-required exit codes: AdwCleaner 2, HitmanPro 2, Emsisoft 2.
---
## Post-Scan Cleanup Lifecycle
When any scanner exits with a reboot-required code (exit 2), the following sequence runs automatically — no forced reboot, no temp user account:
1. `Register-ScannerCleanupTask` writes `cleanup-state.json` (scan ID + log path) to `C:\GuruScan\`.
2. `Invoke-ScannerCleanup.ps1` is written to `C:\GuruScan\`.
3. A SYSTEM scheduled task (`GuruRMM-ScannerCleanup`) is registered with an **at-logon + 30-minute delay** trigger.
4. The scan completes and prints a message to reboot at your convenience.
5. After the next natural reboot and user login, the task fires 30 minutes later (silently, in the background as SYSTEM).
6. The cleanup script removes all scanner installation paths (`C:\EmsisoftCmd`, `C:\AdwCleaner`, `C:\ProgramData\HitmanPro*`, `C:\GuruScan\downloads\`), writes `logs-ready.json` for GuruRMM to pick up, and unregisters itself.
To run cleanup immediately without waiting (e.g. if the task was missed):
```powershell
.\Invoke-PostRebootCleanup.ps1
```
---
## Headless / SYSTEM Behavior
- `-Headless` passes `NoNewWindow` to all scanner launches, suppressing UI windows.
Use this when dispatching from an RMM agent that has no interactive desktop.
- Scanners with `session0_compatible: false` in `scanners.json` are automatically skipped
when the module detects it is running as SYSTEM (Session 0). Currently: **AdwCleaner**.
The result record shows `SKIPPED (requires user session)` rather than a failure.
- To run AdwCleaner via GuruRMM, dispatch with `context: user_session` so it runs in
the active user's desktop session (requires a logged-in user on the target machine).
---
## Licensing
| Scanner | License for MSP use |
|---------|---------------------|
| RKill | Free (BleepingComputer) |
| AdwCleaner | Free for personal and commercial use |
| Emsisoft Command Line Scanner | Free for personal and MSP remediation use |
| HitmanPro | Commercial license required. Each scan uses trial mode; `Invoke-HitmanProTrialReset` resets the trial window. Verify current licensing terms at https://www.hitmanpro.com before deploying at scale. |
Always verify current licensing terms with each vendor before large-scale deployment.
---
## Stand-alone Usage
```powershell
# Run all scanners in clean mode (default)
.\Invoke-GuruScan.ps1
# Detect only, then auto-remediate if threats found
.\Invoke-GuruScan.ps1 -ScanOnly -AutoRemediate
# Suppress scanner windows (RMM dispatch)
.\Invoke-GuruScan.ps1 -Headless
# View the latest scan results
.\Get-ScanSummary.ps1
# View with AI analysis (requires Ollama)
.\Get-ScanSummary.ps1 -AI
# Re-run clean pass against a prior scan
.\Invoke-Remediation.ps1 -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000"
# Download/refresh scanner EXEs
.\Download-Scanners.ps1
```
---
## Module Usage
The launcher scripts are thin wrappers. Import the module directly for
scripted/pipeline use:
```powershell
Import-Module .\GuruScan.psd1
# Disk sink (default) — writes results.json + CSV + zip
Invoke-GuruScan
# RMM sink — returns result object to the pipeline, no disk writes
$result = Invoke-GuruScan -OutputSink RMM -Headless
if ($result.total_threats -gt 0) { ... }
# Remediation from a prior scan
Invoke-Remediation -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000"
# Summary report
Get-ScanSummary -AI
# Manual scanner cleanup (normally runs via scheduled task)
Invoke-PostRebootCleanup
```
---
## Module Structure
```
guru-scan\
GuruScan.psm1 # Core module — all helpers + exported cmdlets
GuruScan.psd1 # Module manifest
scanners.json # Scanner definitions (single source of truth)
Invoke-GuruScan.ps1 # Thin launcher -> Invoke-GuruScan
Invoke-Remediation.ps1 # Thin launcher -> Invoke-Remediation
Get-ScanSummary.ps1 # Thin launcher -> Get-ScanSummary
Invoke-PostRebootCleanup.ps1 # Thin launcher -> Invoke-PostRebootCleanup (manual cleanup trigger)
Invoke-ScannerCleanup.ps1 # Post-reboot cleanup script; copied to C:\GuruScan\ when reboot is needed
Download-Scanners.ps1 # Downloads scanner EXEs from scanners.json URLs
downloads\ # Scanner EXEs (gitignored)
```