<# .SYNOPSIS Parses a GuruScan results.json into a human-readable report. .DESCRIPTION Locates a results.json (defaults to the most recent scan in C:\ScanLogs\), prints a formatted summary with per-scanner results, threat counts, and a remediation recommendation if threats were found. Use -AI to send log content to a local Ollama model for threat analysis and AI-generated remediation recommendations. .PARAMETER ResultsFile Full path to a specific results.json. If omitted, the latest file in C:\ScanLogs\ is used automatically. .PARAMETER ShowAll Show every scanner including those that completed clean (no threats). .PARAMETER AI Send scan logs to local Ollama (http://localhost:11434) for AI-powered threat analysis and prioritized remediation recommendations. .PARAMETER OllamaUrl Ollama base URL. Defaults to http://localhost:11434. .PARAMETER OllamaModel Ollama model to use. Defaults to qwen3.6:latest. .EXAMPLE .\Get-ScanSummary.ps1 .\Get-ScanSummary.ps1 -AI .\Get-ScanSummary.ps1 -ResultsFile "C:\ScanLogs\DESKTOP-20260523-143000\results.json" -AI #> [CmdletBinding()] param( [string]$ResultsFile = '', [switch]$ShowAll, [switch]$AI, [string]$OllamaUrl = 'http://localhost:11434', [string]$OllamaModel = 'qwen3.6:latest' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # --------------------------------------------------------------------------- # Ollama helper # --------------------------------------------------------------------------- function Invoke-Ollama { param([string]$Prompt, [string]$Model = $OllamaModel, [string]$BaseUrl = $OllamaUrl) $body = @{ model = $Model; prompt = $Prompt; stream = $false } | ConvertTo-Json -Depth 3 try { $r = Invoke-RestMethod -Uri "$BaseUrl/api/generate" -Method POST ` -Body $body -ContentType 'application/json' -TimeoutSec 180 return ($r.response -replace ']*>', '' -replace '(?s).*?', '').Trim() } catch { return $null } } # --------------------------------------------------------------------------- # Locate results.json # --------------------------------------------------------------------------- if (-not $ResultsFile) { $scanLogsRoot = 'C:\ScanLogs' if (-not (Test-Path $scanLogsRoot)) { Write-Host "[ERROR] No results file specified and C:\ScanLogs does not exist." -ForegroundColor Red exit 1 } $latestJson = Get-ChildItem -Path $scanLogsRoot -Recurse -Filter 'results.json' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if (-not $latestJson) { Write-Host "[ERROR] No results.json found under C:\ScanLogs\" -ForegroundColor Red exit 1 } $ResultsFile = $latestJson.FullName Write-Host "[INFO] Using latest results: $ResultsFile" -ForegroundColor Cyan } if (-not (Test-Path $ResultsFile)) { Write-Host "[ERROR] Results file not found: $ResultsFile" -ForegroundColor Red exit 1 } # --------------------------------------------------------------------------- # Parse JSON # --------------------------------------------------------------------------- $data = Get-Content $ResultsFile -Raw | ConvertFrom-Json # --------------------------------------------------------------------------- # Header # --------------------------------------------------------------------------- $startDt = [datetime]::Parse($data.started_at).ToLocalTime() $endDt = [datetime]::Parse($data.completed_at).ToLocalTime() $totalDurMin = [math]::Round(($endDt - $startDt).TotalMinutes, 1) Write-Host "" Write-Host "================================================================" -ForegroundColor Cyan Write-Host " GuruScan Report" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan Write-Host " Machine : $($data.machine)" Write-Host " Scan ID : $($data.scan_id)" Write-Host " Mode : $($data.scan_mode)" Write-Host " Started : $($startDt.ToString('yyyy-MM-dd HH:mm:ss'))" Write-Host " Completed : $($endDt.ToString('yyyy-MM-dd HH:mm:ss'))" Write-Host " Duration : $totalDurMin min total" Write-Host "================================================================" -ForegroundColor Cyan Write-Host "" # --------------------------------------------------------------------------- # Per-scanner table # --------------------------------------------------------------------------- $rows = foreach ($s in $data.scanners) { $threatStr = if ($s.threats_found -gt 0) { "YES ($($s.threats_found))" } else { 'Clean' } $statusColor = switch ($s.status) { 'completed' { 'Green' } { $_ -like 'TIMED*' } { 'Yellow' } default { 'Red' } } [pscustomobject]@{ Scanner = $s.name Status = $s.status ExitCode = $s.exit_code Duration = "$($s.duration_min) min" Threats = $threatStr '_Color' = $statusColor } } Write-Host " Scanner Results:" -ForegroundColor Cyan Write-Host "" $header = ' {0,-18} {1,-28} {2,-10} {3,-12} {4}' -f 'Scanner', 'Status', 'ExitCode', 'Duration', 'Threats' Write-Host $header -ForegroundColor Gray Write-Host (' ' + ('-' * 78)) -ForegroundColor DarkGray foreach ($row in $rows) { $line = ' {0,-18} {1,-28} {2,-10} {3,-12} {4}' -f $row.Scanner, $row.Status, $row.ExitCode, $row.Duration, $row.Threats Write-Host $line -ForegroundColor $row.'_Color' } Write-Host "" # --------------------------------------------------------------------------- # Threat summary # --------------------------------------------------------------------------- $threatScanners = $data.scanners | Where-Object { $_.threats_found -gt 0 } $nonClean = $data.scanners | Where-Object { $_.status -ne 'completed' } if ($data.total_threats -gt 0) { Write-Host "================================================================" -ForegroundColor Yellow Write-Host " [WARNING] THREATS DETECTED" -ForegroundColor Yellow Write-Host "================================================================" -ForegroundColor Yellow Write-Host "" Write-Host " Scanners that detected threats:" -ForegroundColor Yellow foreach ($ts in $threatScanners) { Write-Host " - $($ts.name) (exit code $($ts.exit_code), log: $($ts.log_path))" -ForegroundColor Yellow } Write-Host "" } else { Write-Host " [OK] No threats detected across all scanners." -ForegroundColor Green Write-Host "" } if ($nonClean) { Write-Host " [WARNING] Scanners with non-completed status:" -ForegroundColor Yellow foreach ($nc in $nonClean) { Write-Host " - $($nc.name): $($nc.status)" -ForegroundColor Yellow } Write-Host "" } # --------------------------------------------------------------------------- # Recommendation # --------------------------------------------------------------------------- Write-Host "================================================================" -ForegroundColor Cyan Write-Host " Recommendation" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan if ($data.reboot_required) { Write-Host " [WARNING] A REBOOT IS REQUIRED to complete remediation." -ForegroundColor Yellow Write-Host " Reboot the machine, then re-run GuruScan to verify." -ForegroundColor Yellow } elseif ($data.total_threats -gt 0 -and $data.scan_mode -eq 'scan') { Write-Host " [INFO] Threats were detected in scan-only mode." -ForegroundColor Cyan Write-Host " Run: .\Invoke-GuruScan.ps1 (without -ScanOnly)" -ForegroundColor Cyan Write-Host " Or: .\Invoke-Remediation.ps1 -LogRoot `"$(Split-Path $ResultsFile)`"" -ForegroundColor Cyan } elseif ($data.total_threats -gt 0) { Write-Host " [INFO] Clean mode was run. Review logs to confirm threats removed." -ForegroundColor Cyan Write-Host " Log folder: $(Split-Path $ResultsFile)" -ForegroundColor Cyan } else { Write-Host " [OK] System appears clean. No further action required." -ForegroundColor Green Write-Host " Consider running ESET Online Scanner for a second opinion." -ForegroundColor Gray } Write-Host "" Write-Host " Full logs: $(Split-Path $ResultsFile)" -ForegroundColor Gray Write-Host "" # --------------------------------------------------------------------------- # Ollama AI analysis (optional — requires -AI switch) # --------------------------------------------------------------------------- if ($AI) { Write-Host "================================================================" -ForegroundColor Cyan Write-Host " AI Analysis (Ollama)" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan Write-Host " Model : $OllamaModel" Write-Host " Server : $OllamaUrl" Write-Host "" # Collect log content from all scanner log folders (cap at 8KB each to avoid huge prompts) $logFolder = Split-Path $ResultsFile $logContent = [System.Text.StringBuilder]::new() Get-ChildItem -Path $logFolder -Recurse -Include '*.log','*.txt','*.csv' -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 0 -and $_.Length -lt 200KB } | ForEach-Object { $snippet = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue if ($snippet) { $snippet = if ($snippet.Length -gt 8192) { $snippet.Substring(0, 8192) + "`n[TRUNCATED]" } else { $snippet } [void]$logContent.AppendLine("=== $($_.Name) ===") [void]$logContent.AppendLine($snippet) [void]$logContent.AppendLine() } } $logText = if ($logContent.Length -gt 0) { $logContent.ToString() } else { 'No log files found.' } $scanJson = $data | ConvertTo-Json -Depth 5 # Step 1: Extract specific threat names / findings Write-Host " [....] Extracting threat details..." -ForegroundColor Cyan $threatPrompt = @" You are a malware analyst. Below is a JSON scan summary and raw log excerpts from a multi-engine malware scan. SCAN SUMMARY JSON: $scanJson RAW LOG EXCERPTS: $logText Task: Extract a concise list of specific threat names, file paths, registry keys, or other findings from the logs. Format your response as a plain numbered list. If no specific threats are named in the logs, say "No named threats found in logs." Do not add commentary — only the list. "@ $threatDetails = Invoke-Ollama -Prompt $threatPrompt if ($threatDetails) { Write-Host "" Write-Host " Identified Threats / Findings:" -ForegroundColor Yellow $threatDetails -split "`n" | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } } else { Write-Host " [WARNING] Ollama unavailable — skipping threat extraction." -ForegroundColor Yellow } # Step 2: Prioritized remediation recommendations Write-Host "" Write-Host " [....] Generating remediation recommendations..." -ForegroundColor Cyan $remediationPrompt = @" You are an MSP technician. A multi-engine malware scan produced these results: $scanJson Threat details extracted from logs: $threatDetails Task: Write a short, prioritized remediation checklist (max 8 steps) for the technician. Include: immediate actions, follow-up verification steps, and whether a reboot is needed. Plain numbered list, no markdown headers, no padding text. Be specific. "@ $recommendations = Invoke-Ollama -Prompt $remediationPrompt if ($recommendations) { Write-Host "" Write-Host " Remediation Checklist:" -ForegroundColor Cyan $recommendations -split "`n" | ForEach-Object { Write-Host " $_" -ForegroundColor Cyan } } else { Write-Host " [WARNING] Ollama unavailable — skipping recommendations." -ForegroundColor Yellow } Write-Host "" Write-Host "================================================================" -ForegroundColor DarkGray Write-Host " NOTE: AI output is advisory. Review logs before acting." -ForegroundColor DarkGray Write-Host "================================================================" -ForegroundColor DarkGray Write-Host "" } exit 0