sync: auto-sync from HOWARD-HOME at 2026-05-26 21:58:00
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-05-26 21:58:00
This commit is contained in:
@@ -33,262 +33,11 @@ param(
|
||||
[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 '</?think[^>]*>', '' -replace '(?s)<think>.*?</think>', '').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
|
||||
$moduleManifest = Join-Path $PSScriptRoot 'GuruScan.psd1'
|
||||
if (-not (Test-Path $moduleManifest)) {
|
||||
Write-Host "[ERROR] GuruScan module not found: $moduleManifest" -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
|
||||
Import-Module $moduleManifest -Force
|
||||
Get-ScanSummary @PSBoundParameters
|
||||
|
||||
Reference in New Issue
Block a user