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>
74 lines
2.8 KiB
PowerShell
74 lines
2.8 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
<#
|
|
.SYNOPSIS
|
|
GuruScan - multi-engine malware scanning orchestrator (single-file, RMM-ready).
|
|
.DESCRIPTION
|
|
Runs a suite of portable malware scanners in sequence, captures logs,
|
|
and writes a structured results.json plus a zip archive of all logs.
|
|
Scanner definitions are read from scanners.json in the same directory.
|
|
|
|
By default runs all scanners in clean (remediation) mode.
|
|
Use -ScanOnly to detect without cleaning.
|
|
|
|
NOTE: MSERT is no longer included in the default scanner list because it
|
|
takes too long for routine runs. To run MSERT, invoke it directly or add
|
|
it back to scanners.json.
|
|
.PARAMETER ScanOnly
|
|
Use scan args (detect only) instead of clean args for every scanner.
|
|
.PARAMETER AutoRemediate
|
|
After a scan-only pass, if threats are found, automatically re-run all
|
|
scanners in clean mode.
|
|
.PARAMETER Scanners
|
|
Run only the named scanners (comma-separated or multiple values).
|
|
Names must match the Name field in scanners.json exactly.
|
|
.PARAMETER TimeoutMin
|
|
Override the per-scanner timeout (in minutes) for all scanners.
|
|
.PARAMETER SkipScanners
|
|
Skip one or more named scanners by name. Names must match the Name field
|
|
in scanners.json exactly. Useful for excluding a single scanner without
|
|
respecifying the entire list.
|
|
.PARAMETER Headless
|
|
Suppress scanner windows (used when dispatching via RMM).
|
|
.EXAMPLE
|
|
.\Invoke-GuruScan.ps1
|
|
.\Invoke-GuruScan.ps1 -ScanOnly -AutoRemediate
|
|
.\Invoke-GuruScan.ps1 -SkipScanners Emsisoft
|
|
.\Invoke-GuruScan.ps1 -Headless
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$ScanOnly,
|
|
[switch]$AutoRemediate,
|
|
[string[]]$Scanners,
|
|
[int]$TimeoutMin = 0,
|
|
[string[]]$SkipScanners = @(),
|
|
[switch]$Headless
|
|
)
|
|
|
|
$moduleManifest = Join-Path $PSScriptRoot 'GuruScan.psd1'
|
|
if (-not (Test-Path $moduleManifest)) {
|
|
Write-Host "[ERROR] GuruScan module not found: $moduleManifest" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Import-Module $moduleManifest -Force
|
|
$scanStart = Get-Date
|
|
Invoke-GuruScan @PSBoundParameters -OutputSink Disk
|
|
|
|
# Emit structured JSON to stdout for GuruRMM CommandResult capture.
|
|
# Read from results.json written during this run (newer than $scanStart).
|
|
$resultsFile = Get-ChildItem -Path 'C:\ScanLogs' -Recurse -Filter 'results.json' -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.LastWriteTime -gt $scanStart } |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 1
|
|
|
|
if ($resultsFile) {
|
|
$json = Get-Content -Path $resultsFile.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
|
if ($json) {
|
|
# Compress to single line so the agent's stdout parser sees it as one line
|
|
$compressed = ($json | ConvertFrom-Json | ConvertTo-Json -Depth 10 -Compress)
|
|
Write-Output ''
|
|
Write-Output "GURUSCAN_RESULT_JSON:$compressed"
|
|
}
|
|
}
|