diff --git a/projects/msp-tools/guru-scan/GuruScan.psm1 b/projects/msp-tools/guru-scan/GuruScan.psm1 index cfffcd2..aab231c 100644 --- a/projects/msp-tools/guru-scan/GuruScan.psm1 +++ b/projects/msp-tools/guru-scan/GuruScan.psm1 @@ -377,7 +377,8 @@ function Invoke-ScanPass { .PARAMETER TimeoutMinOverride If > 0, overrides the per-scanner timeout_min for all scanners. .PARAMETER Headless - When set, suppress scanner UI windows (NoNewWindow). + When set, launches scanner processes with WindowStyle=Hidden so no UI + windows appear. Use when dispatching from an RMM agent with no desktop. .OUTPUTS [System.Collections.Generic.List[pscustomobject]] of result objects. #> @@ -771,7 +772,7 @@ function Invoke-GuruScan { ) # Whitelist -- written to C:\GuruScan\whitelist.txt before any scanner runs. - # Emsisoft and HitmanPro honour this; RKill and AdwCleaner do not. + # Emsisoft (/wl=) and HitmanPro (/excludelist=) honour this; RKill does not. $whitelist = @('C:\GuruScan') # ForceRemove blacklist -- items removed after all scanners complete. @@ -849,7 +850,7 @@ function Invoke-GuruScan { # Add Windows Defender exclusions for scanner paths so Defender does not # quarantine scanner EXEs or log files mid-run. - $defenderExclusions = @($script:Base, $script:LogRoot, 'C:\EmsisoftCmd', 'C:\AdwCleaner') + $defenderExclusions = @($script:Base, $script:LogRoot, 'C:\EmsisoftCmd') try { Add-MpPreference -ExclusionPath $defenderExclusions -ErrorAction SilentlyContinue Write-Host "[INFO] Windows Defender exclusions added for scanner paths" -ForegroundColor Cyan @@ -1014,7 +1015,7 @@ function Invoke-Remediation { successfully are re-run. .EXAMPLE Invoke-Remediation -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000" - Invoke-Remediation -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000" -Scanners AdwCleaner,MSERT + Invoke-Remediation -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000" -Scanners Emsisoft,MSERT #> [CmdletBinding()] param( diff --git a/projects/msp-tools/guru-scan/README.md b/projects/msp-tools/guru-scan/README.md index bd5cdc7..de846a8 100644 --- a/projects/msp-tools/guru-scan/README.md +++ b/projects/msp-tools/guru-scan/README.md @@ -17,7 +17,7 @@ for downstream processing by the RMM agent. 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. +typically `C:\GuruScan\` or the RMM deployment path. --- @@ -28,12 +28,16 @@ Scanners run in this order. Each stage hands off to the next regardless of findi | # | 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`. | +| 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). | -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. +**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. --- @@ -42,29 +46,29 @@ for routine remediation runs. Add it back to `scanners.json` if needed. | 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. +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, no temp user account: +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, 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. +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 (e.g. if the task was missed): +To run cleanup immediately without waiting: ```powershell .\Invoke-PostRebootCleanup.ps1 ``` @@ -73,13 +77,47 @@ To run cleanup immediately without waiting (e.g. if the task was missed): ## 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). +- `-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:` 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 } + ] +} +``` --- @@ -88,7 +126,6 @@ To run cleanup immediately without waiting (e.g. if the task was missed): | 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. | @@ -154,14 +191,14 @@ Invoke-PostRebootCleanup ``` 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) + 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) ```