- README: remove AdwCleaner from scanner chain and exit code/licensing tables; add AdwCleaner note explaining why it is temporarily excluded; fix Headless description (WindowStyle=Hidden, not NoNewWindow); add GuruRMM integration section with example JSON output structure - GuruScan.psm1: fix Headless param docstring; update whitelist comment (Emsisoft + HitmanPro only); remove C:\AdwCleaner from Defender exclusion list; fix Invoke-Remediation example (-Scanners Emsisoft,MSERT) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
205 lines
7.5 KiB
Markdown
205 lines
7.5 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.
|
|
|
|
---
|
|
|
|
## 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 | **Emsisoft Command Line Scanner** | antimalware | Two-step: NSIS installer extracts to `C:\EmsisoftCmd\`, then `/update` fetches latest definitions, then deep-scans `C:\`. |
|
|
| 3 | **HitmanPro** | antimalware | Cloud-assisted second-opinion scanner. Trial registry is reset before each run via `Invoke-HitmanProTrialReset`. Runs with `/quiet` (no GUI). |
|
|
|
|
**AdwCleaner** is not currently in the chain. It requires both elevated privileges
|
|
and an interactive desktop session simultaneously — SYSTEM context is elevated but
|
|
Session 0 (no desktop), user_session has a desktop but a non-elevated WTS token.
|
|
It will be re-added once a scheduled-task `InteractiveToken` dispatch is implemented.
|
|
|
|
MSERT (Microsoft Safety Scanner) is also excluded from the default chain — too slow
|
|
for routine runs. Add it 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) | — | — | — |
|
|
| 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: 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:
|
|
|
|
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, as SYSTEM).
|
|
6. The cleanup script removes all scanner installation paths (`C:\EmsisoftCmd`, `C:\ProgramData\HitmanPro*`, `C:\GuruScan\downloads\`), writes `logs-ready.json` for GuruRMM to pick up, and unregisters itself.
|
|
|
|
To run cleanup immediately without waiting:
|
|
```powershell
|
|
.\Invoke-PostRebootCleanup.ps1
|
|
```
|
|
|
|
---
|
|
|
|
## Headless / SYSTEM Behavior
|
|
|
|
- `-Headless` launches all scanner processes with `WindowStyle=Hidden`, suppressing
|
|
UI windows and preventing child processes from inheriting the PowerShell pipe
|
|
handles. Use this when dispatching from an RMM agent with 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). The result
|
|
record shows `SKIPPED (requires user session)` rather than a failure.
|
|
- The whitelist (`C:\GuruScan\whitelist.txt`) is honoured by Emsisoft (`/wl=`) and
|
|
HitmanPro (`/excludelist=`). RKill does not support a whitelist.
|
|
|
|
---
|
|
|
|
## GuruRMM Integration
|
|
|
|
When dispatched via GuruRMM in `system` context with `-Headless`, the launcher
|
|
emits a `GURUSCAN_RESULT_JSON:<compressed-json>` line to stdout at the end of the
|
|
run. The GuruRMM agent captures this in `command.stdout` for structured result
|
|
reporting on the dashboard.
|
|
|
|
```powershell
|
|
# GuruRMM dispatch command (system context, elevated):
|
|
C:\GuruScan\Invoke-GuruScan.ps1 -Headless
|
|
```
|
|
|
|
The JSON structure matches `results.json` written to disk:
|
|
|
|
```json
|
|
{
|
|
"scan_id": "HOSTNAME-20260527-010203",
|
|
"machine": "HOSTNAME",
|
|
"started_at": "...",
|
|
"completed_at": "...",
|
|
"total_threats": 0,
|
|
"reboot_required": false,
|
|
"scan_mode": "clean",
|
|
"scanners": [
|
|
{ "name": "RKill", "status": "completed", "exit_code": 1, "threats_found": 0, "duration_min": 0.09 },
|
|
{ "name": "Emsisoft", "status": "completed", "exit_code": 0, "threats_found": 0, "duration_min": 4.8 },
|
|
{ "name": "HitmanPro","status": "completed", "exit_code": 0, "threats_found": 0, "duration_min": 2.2 }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Licensing
|
|
|
|
| Scanner | License for MSP use |
|
|
|---------|---------------------|
|
|
| RKill | Free (BleepingComputer) |
|
|
| 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
|
|
Invoke-ScannerCleanup.ps1 # Post-reboot cleanup; copied to C:\GuruScan\ when reboot is needed
|
|
Download-Scanners.ps1 # Downloads scanner EXEs from scanners.json URLs
|
|
downloads\ # Scanner EXEs (gitignored)
|
|
```
|