feat: add GuruScan standalone multi-scanner security suite
Adds a complete PowerShell-based malware scanning toolkit: - Invoke-GuruScan.ps1: main orchestrator running RKill, AdwCleaner, Emsisoft, HitmanPro, and ESET in sequence with pre/post cleanup, whitelist support, ForceRemove blacklist, and -Headless switch - Invoke-PostRebootCleanup.ps1: post-reboot temp-user session that shows a fullscreen splash, verifies boot-time cleanup completed, removes scanner files, and restores the original user login name - Download-Scanners.ps1: downloads/refreshes scanner EXEs - Get-ScanSummary.ps1: parses results.json with optional Ollama AI analysis - Invoke-Remediation.ps1: re-runs scanners in clean mode Key features: exit-code-based reboot detection, whoami-based user capture (SYSTEM-safe via quser fallback), domain\user and local MACHINE\user restore on login screen after cleanup reboot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
115
projects/msp-tools/guru-scan/Download-Scanners.ps1
Normal file
115
projects/msp-tools/guru-scan/Download-Scanners.ps1
Normal file
@@ -0,0 +1,115 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Downloads or refreshes all scanner executables defined in scanners.json.
|
||||
.DESCRIPTION
|
||||
Reads scanner definitions from scanners.json and downloads each scanner EXE
|
||||
to the downloads\ subdirectory. Skips scanners with null download URLs.
|
||||
.PARAMETER Force
|
||||
Re-download all scanners even if they already exist locally.
|
||||
.EXAMPLE
|
||||
.\Download-Scanners.ps1
|
||||
.\Download-Scanners.ps1 -Force
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
$ConfigPath = Join-Path $PSScriptRoot 'scanners.json'
|
||||
$DownloadsDir = Join-Path $PSScriptRoot 'downloads'
|
||||
|
||||
if (-not (Test-Path $ConfigPath)) {
|
||||
Write-Host "[ERROR] scanners.json not found at: $ConfigPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
|
||||
if (-not (Test-Path $DownloadsDir)) {
|
||||
New-Item -ItemType Directory -Path $DownloadsDir -Force | Out-Null
|
||||
Write-Host "[INFO] Created downloads directory: $DownloadsDir" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$results = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||
|
||||
foreach ($scanner in $config.scanners) {
|
||||
$urlToUse = $null
|
||||
$fileToSave = $null
|
||||
|
||||
# Manual-download entries: print instructions and skip
|
||||
if ($scanner.PSObject.Properties['manual_download'] -and $scanner.manual_download -eq $true) {
|
||||
$note = if ($scanner.PSObject.Properties['manual_download_note']) { $scanner.manual_download_note } else { 'Place EXE manually in downloads\' }
|
||||
Write-Host " [MANUAL] $($scanner.name) — $note" -ForegroundColor Yellow
|
||||
$results.Add([pscustomobject]@{ Scanner = $scanner.name; Status = 'MANUAL'; File = ''; Size = 0 })
|
||||
continue
|
||||
}
|
||||
|
||||
if ($scanner.installer_exe -and $scanner.download_url) {
|
||||
$urlToUse = $scanner.download_url
|
||||
$fileToSave = Join-Path $DownloadsDir (Split-Path $scanner.installer_exe -Leaf)
|
||||
}
|
||||
elseif ($scanner.download_url) {
|
||||
$urlToUse = $scanner.download_url
|
||||
$leafName = Split-Path $scanner.exe -Leaf
|
||||
$fileToSave = Join-Path $DownloadsDir $leafName
|
||||
}
|
||||
else {
|
||||
Write-Host " [SKIP] $($scanner.name) — no download URL configured" -ForegroundColor Yellow
|
||||
$results.Add([pscustomobject]@{ Scanner = $scanner.name; Status = 'SKIPPED'; File = ''; Size = 0 })
|
||||
continue
|
||||
}
|
||||
|
||||
if ((Test-Path $fileToSave) -and -not $Force) {
|
||||
$existingSize = (Get-Item $fileToSave).Length
|
||||
Write-Host " [OK] $($scanner.name) already exists ($([math]::Round($existingSize / 1MB, 1)) MB)" -ForegroundColor Green
|
||||
$results.Add([pscustomobject]@{ Scanner = $scanner.name; Status = 'EXISTS'; File = $fileToSave; Size = $existingSize })
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host " [....] $($scanner.name) — downloading from $urlToUse" -ForegroundColor Cyan
|
||||
try {
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
|
||||
$wc.DownloadFile($urlToUse, $fileToSave)
|
||||
$wc.Dispose()
|
||||
|
||||
if (-not (Test-Path $fileToSave)) {
|
||||
throw "File not found after download attempt"
|
||||
}
|
||||
$downloadedSize = (Get-Item $fileToSave).Length
|
||||
if ($downloadedSize -eq 0) {
|
||||
throw "Downloaded file is 0 bytes — URL may be invalid or redirected"
|
||||
}
|
||||
|
||||
Write-Host " [OK] $($scanner.name) downloaded ($([math]::Round($downloadedSize / 1MB, 1)) MB) -> $fileToSave" -ForegroundColor Green
|
||||
$results.Add([pscustomobject]@{ Scanner = $scanner.name; Status = 'DOWNLOADED'; File = $fileToSave; Size = $downloadedSize })
|
||||
}
|
||||
catch {
|
||||
Write-Host " [ERROR] $($scanner.name) — $($_.Exception.Message)" -ForegroundColor Red
|
||||
if (Test-Path $fileToSave) {
|
||||
Remove-Item $fileToSave -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$results.Add([pscustomobject]@{ Scanner = $scanner.name; Status = 'FAILED'; File = $fileToSave; Size = 0 })
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Download Summary ===" -ForegroundColor Cyan
|
||||
$results | Format-Table -AutoSize
|
||||
|
||||
$manual = $results | Where-Object { $_.Status -eq 'MANUAL' }
|
||||
if ($manual) {
|
||||
Write-Host "[INFO] $($manual.Count) scanner(s) require manual download — see notes above." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$failed = $results | Where-Object { $_.Status -eq 'FAILED' }
|
||||
if ($failed) {
|
||||
Write-Host "[WARNING] $($failed.Count) scanner(s) failed to download. Scan coverage will be incomplete." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user