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:
Register-ScannerCleanupTaskwritescleanup-state.json(scan ID + log path) toC:\GuruScan\.Invoke-ScannerCleanup.ps1is written toC:\GuruScan\.- A SYSTEM scheduled task (
GuruRMM-ScannerCleanup) is registered with an at-logon + 30-minute delay trigger. - The scan completes and prints a message to reboot at your convenience.
- After the next natural reboot and user login, the task fires 30 minutes later (silently, as SYSTEM).
- The cleanup script removes all scanner installation paths (
C:\EmsisoftCmd,C:\ProgramData\HitmanPro*,C:\GuruScan\downloads\), writeslogs-ready.jsonfor GuruRMM to pick up, and unregisters itself.
To run cleanup immediately without waiting:
.\Invoke-PostRebootCleanup.ps1
Headless / SYSTEM Behavior
-Headlesslaunches all scanner processes withWindowStyle=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: falseinscanners.jsonare automatically skipped when the module detects it is running as SYSTEM (Session 0). The result record showsSKIPPED (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.
# GuruRMM dispatch command (system context, elevated):
C:\GuruScan\Invoke-GuruScan.ps1 -Headless
The JSON structure matches results.json written to disk:
{
"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
# 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:
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)