Files
claudetools/projects/msp-tools/guru-scan/Download-Scanners.ps1
Howard Enos 3a0c83dd42 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>
2026-05-26 12:40:56 -07:00

116 lines
4.5 KiB
PowerShell

<#
.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