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:
2026-05-26 21:58:07 -07:00
parent e828dacdbd
commit f844054847
8 changed files with 1764 additions and 1954 deletions

View File

@@ -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

View File

@@ -0,0 +1,27 @@
@{
RootModule = 'GuruScan.psm1'
ModuleVersion = '1.0.0'
GUID = 'a3f2c1d4-8e5b-4a7f-9c2e-1b3d5f7a9e0c'
Author = 'Arizona Computer Guru'
CompanyName = 'Arizona Computer Guru LLC'
Description = 'Multi-engine malware scan orchestrator for GuruRMM'
PowerShellVersion = '5.1'
FunctionsToExport = @(
'Invoke-GuruScan',
'Invoke-Remediation',
'Get-ScanSummary',
'Invoke-PostRebootCleanup'
)
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
PrivateData = @{
PSData = @{
Tags = @('malware', 'scanner', 'remediation', 'msp', 'security')
ProjectUri = 'https://git.azcomputerguru.com/azcomputerguru/claudetools'
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,215 +1,13 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Post-reboot cleanup agent for GuruScan. Runs automatically as GuruRMM-Temp
after a reboot triggered by scanner exit code 2 (pending removal required).
Shows a full-screen splash, verifies cleanup completed, removes scanner files,
restores the original user's login name, then logs off.
Manually triggers GuruScan post-scan cleanup (removes scanner files).
Normally this runs automatically via the GuruRMM-ScannerCleanup scheduled task.
#>
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$Base = 'C:\GuruScan'
$StateFile = "$Base\cleanup-state.json"
$TempUser = 'GuruRMM-Temp'
$WlKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
$SpecialKey= 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList'
# ---------------------------------------------------------------------------
# Kill Explorer so we have a clean screen before showing splash
# ---------------------------------------------------------------------------
Get-Process -Name explorer -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
# ---------------------------------------------------------------------------
# Full-screen splash in a STA runspace (non-blocking)
# ---------------------------------------------------------------------------
$sync = [hashtable]::Synchronized(@{ Close = $false })
$uiRS = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$uiRS.ApartmentState = 'STA'
$uiRS.ThreadOptions = 'ReuseThread'
$uiRS.Open()
$uiPS = [System.Management.Automation.PowerShell]::Create()
$uiPS.Runspace = $uiRS
[void]$uiPS.AddScript({
param($s)
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
$win = New-Object System.Windows.Window
$win.WindowStyle = 'None'
$win.WindowState = 'Maximized'
$win.Background = [System.Windows.Media.Brushes]::Black
$win.Topmost = $true
$win.Title = 'GuruRMM'
$win.Cursor = [System.Windows.Input.Cursors]::None
$panel = New-Object System.Windows.Controls.StackPanel
$panel.VerticalAlignment = 'Center'
$panel.HorizontalAlignment = 'Center'
$title = New-Object System.Windows.Controls.TextBlock
$title.Text = 'GuruRMM'
$title.Foreground = [System.Windows.Media.Brushes]::White
$title.FontSize = 42
$title.FontFamily = New-Object System.Windows.Media.FontFamily('Segoe UI')
$title.FontWeight = [System.Windows.FontWeights]::Light
$title.TextAlignment = 'Center'
$title.Margin = New-Object System.Windows.Thickness(0,0,0,24)
$body = New-Object System.Windows.Controls.TextBlock
$body.Text = "Security cleanup is being completed on this machine.`n`nPlease do not power off this computer.`n`nThis process will finish shortly."
$body.Foreground = [System.Windows.Media.SolidColorBrush][System.Windows.Media.Color]::FromRgb(180,180,180)
$body.FontSize = 22
$body.FontFamily = New-Object System.Windows.Media.FontFamily('Segoe UI')
$body.TextAlignment = 'Center'
$body.LineHeight = 38
[void]$panel.Children.Add($title)
[void]$panel.Children.Add($body)
$win.Content = $panel
$timer = New-Object System.Windows.Threading.DispatcherTimer
$timer.Interval = [TimeSpan]::FromMilliseconds(500)
$timer.Add_Tick({ if ($s.Close) { $win.Close(); $timer.Stop() } })
$timer.Start()
[void]$win.ShowDialog()
}).AddArgument($sync)
$uiHandle = $uiPS.BeginInvoke()
# ---------------------------------------------------------------------------
# Read state file
# ---------------------------------------------------------------------------
$state = @{ original_user = ''; scan_id = ''; log_root = '' }
if (Test-Path $StateFile) {
try { $state = Get-Content $StateFile -Raw | ConvertFrom-Json } catch {}
$moduleManifest = Join-Path $PSScriptRoot 'GuruScan.psd1'
if (-not (Test-Path $moduleManifest)) {
Write-Host "[ERROR] GuruScan module not found: $moduleManifest" -ForegroundColor Red
exit 1
}
$logRoot = $state.log_root
$scanId = $state.scan_id
$origUser = $state.original_user
# ---------------------------------------------------------------------------
# Wait for boot-time cleanup to settle
# ---------------------------------------------------------------------------
Start-Sleep -Seconds 60
# ---------------------------------------------------------------------------
# Verify: check PendingFileRenameOperations (empty = boot cleanup completed)
# ---------------------------------------------------------------------------
$pendingKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
$pendingOps = (Get-ItemProperty -Path $pendingKey -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations
$pendingDone = (-not $pendingOps -or $pendingOps.Count -eq 0)
# Check scanner post-reboot logs
$adwLog = Get-ChildItem 'C:\AdwCleaner\Logs' -Filter '*.txt' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
$hitLog = if ($logRoot -and (Test-Path "$logRoot\HitmanPro_Scan_Log.txt")) {
Get-Item "$logRoot\HitmanPro_Scan_Log.txt"
} else { $null }
$verification = [ordered]@{
checked_at = (Get-Date).ToUniversalTime().ToString('o')
pending_ops_cleared = $pendingDone
adwcleaner_log = if ($adwLog) { $adwLog.FullName } else { 'not found' }
hitmanpro_log = if ($hitLog) { $hitLog.FullName } else { 'not found' }
result = if ($pendingDone) { 'clean' } else { 'pending_items_remain' }
}
# Write verification result alongside original results.json
if ($logRoot -and (Test-Path $logRoot)) {
$verification | ConvertTo-Json | Set-Content "$logRoot\post_reboot_verification.json" -Encoding UTF8
}
# ---------------------------------------------------------------------------
# Remove scanner installation files from this machine
# ---------------------------------------------------------------------------
$scannerPaths = @(
'C:\EmsisoftCmd',
'C:\AdwCleaner',
'C:\ProgramData\HitmanPro',
'C:\ProgramData\HitmanPro.Alert'
)
foreach ($p in $scannerPaths) {
Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue
}
# ---------------------------------------------------------------------------
# Flag logs for GuruRMM to pull (agent reads this file)
# ---------------------------------------------------------------------------
@{
scan_id = $scanId
log_root = $logRoot
zip_path = "$Base\reports\$scanId.zip"
verified = $verification.result
flagged_at = (Get-Date).ToUniversalTime().ToString('o')
} | ConvertTo-Json | Set-Content "$Base\logs-ready.json" -Encoding UTF8
# ---------------------------------------------------------------------------
# Restore original user's name in the login screen.
# Win32_ComputerSystem.UserName returns "DOMAIN\user" or "MACHINE\user".
# Split so the login screen pre-fills both fields correctly.
# ---------------------------------------------------------------------------
if ($origUser) {
try {
if ($origUser -match '^(.+)\\(.+)$') {
$restoreDomain = $Matches[1]
$restoreUser = $Matches[2]
} else {
$restoreDomain = $env:COMPUTERNAME
$restoreUser = $origUser
}
Set-ItemProperty -Path $WlKey -Name 'DefaultUserName' -Value $restoreUser
Set-ItemProperty -Path $WlKey -Name 'DefaultDomainName' -Value $restoreDomain
} catch {}
}
# ---------------------------------------------------------------------------
# Clear autologon settings
# ---------------------------------------------------------------------------
try {
Set-ItemProperty -Path $WlKey -Name 'AutoAdminLogon' -Value '0'
Remove-ItemProperty -Path $WlKey -Name 'DefaultPassword' -ErrorAction SilentlyContinue
} catch {}
# ---------------------------------------------------------------------------
# Remove our scheduled logon task
# ---------------------------------------------------------------------------
Unregister-ScheduledTask -TaskName 'GuruRMM-PostRebootCleanup' -Confirm:$false -ErrorAction SilentlyContinue
# ---------------------------------------------------------------------------
# Schedule SYSTEM task to delete GuruRMM-Temp 2 minutes from now
# (can't delete the account we're currently logged into)
# ---------------------------------------------------------------------------
try {
$deleteScript = @"
Remove-LocalUser -Name '$TempUser' -ErrorAction SilentlyContinue
Remove-ItemProperty -Path '$SpecialKey' -Name '$TempUser' -ErrorAction SilentlyContinue
Remove-Item -Path 'C:\Users\$TempUser' -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path '$StateFile' -Force -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName 'GuruRMM-TempUserDelete' -Confirm:`$false -ErrorAction SilentlyContinue
"@
$deleteScript | Set-Content "$Base\delete-temp-user.ps1" -Encoding UTF8
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$Base\delete-temp-user.ps1`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(2)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName 'GuruRMM-TempUserDelete' -Action $action `
-Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
} catch {}
# ---------------------------------------------------------------------------
# Close splash and log off
# ---------------------------------------------------------------------------
$sync.Close = $true
Start-Sleep -Seconds 3
$uiPS.Stop()
$uiRS.Close()
& logoff
Import-Module $moduleManifest -Force
Invoke-PostRebootCleanup

View File

@@ -1,4 +1,4 @@
#Requires -RunAsAdministrator
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Re-runs GuruScan scanners in clean mode against a previous scan's log folder.
@@ -25,436 +25,11 @@ param(
[string[]]$Scanners
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue'
# ---------------------------------------------------------------------------
# Helpers (duplicated here so this script is self-contained)
# ---------------------------------------------------------------------------
function Expand-TokenizedArgs {
param([string[]]$Args, [string]$LogRoot, [string]$ExeDir)
$out = foreach ($a in $Args) {
$a = $a -replace '\{LOG_ROOT\}', $LogRoot
$a = $a -replace '\{EXE_DIR\}', $ExeDir
$a
}
return $out
}
function Expand-EnvPath {
param([string]$Path)
return [System.Environment]::ExpandEnvironmentVariables($Path)
}
function Remove-PathSilent {
param([string]$Path)
$expanded = Expand-EnvPath $Path
if (Test-Path $expanded) {
Remove-Item -Path $expanded -Recurse -Force -ErrorAction SilentlyContinue
}
}
function Wait-ProcessWithTimeout {
param(
[System.Diagnostics.Process]$Process,
[int]$TimeoutSeconds
)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while (-not $Process.HasExited) {
if ((Get-Date) -gt $deadline) {
try { $Process.Kill() } catch { }
return $false
}
Start-Sleep -Seconds 5
}
return $true
}
function Wait-ServicesToStop {
param([string[]]$ServiceNames, [int]$TimeoutSeconds = 120)
if (-not $ServiceNames -or $ServiceNames.Count -eq 0) { return }
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
foreach ($svc in $ServiceNames) {
while ((Get-Date) -lt $deadline) {
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
if (-not $s -or $s.Status -ne 'Running') { break }
Start-Sleep -Seconds 3
}
}
}
function Invoke-HitmanProTrialReset {
Remove-Item -Path "HKLM:\SOFTWARE\HitmanPro" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path "HKLM:\SOFTWARE\HitmanPro.Alert" -Recurse -Force -ErrorAction SilentlyContinue
New-Item -Path "HKLM:\SOFTWARE\HitmanPro" -Force | Out-Null
New-Item -Path "HKLM:\SOFTWARE\HitmanPro.Alert" -Force | Out-Null
}
function Get-ExitCodeThreats {
param([string]$ScannerName, [int]$ExitCode)
switch ($ScannerName) {
'AdwCleaner' { if ($ExitCode -eq 1) { return 1 } }
'MSERT' { if ($ExitCode -ne 0) { return 1 } }
'TDSSKiller' { if ($ExitCode -eq 1) { return 1 } }
'HitmanPro' { if ($ExitCode -eq 1) { return 1 } }
'Emsisoft' { if ($ExitCode -ge 1) { return 1 } }
'Stinger' { if ($ExitCode -eq 13) { return 1 } }
'ESET' { if ($ExitCode -ne 0) { return 1 } }
}
return 0
}
# ---------------------------------------------------------------------------
# Validate inputs
# ---------------------------------------------------------------------------
$ScriptRoot = $PSScriptRoot
$ConfigPath = Join-Path $ScriptRoot 'scanners.json'
$PriorResults = Join-Path $LogRoot 'results.json'
if (-not (Test-Path $LogRoot)) {
Write-Host "[ERROR] LogRoot not found: $LogRoot" -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
}
if (-not (Test-Path $PriorResults)) {
Write-Host "[ERROR] results.json not found in: $LogRoot" -ForegroundColor Red
exit 1
}
if (-not (Test-Path $ConfigPath)) {
Write-Host "[ERROR] scanners.json not found: $ConfigPath" -ForegroundColor Red
exit 1
}
# ---------------------------------------------------------------------------
# Load config and prior results
# ---------------------------------------------------------------------------
$config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
$priorData = Get-Content $PriorResults -Raw | ConvertFrom-Json
$priorScannerNames = $priorData.scanners |
Where-Object { $_.status -eq 'completed' } |
ForEach-Object { $_.name }
$scannerList = $config.scanners | Where-Object { $priorScannerNames -contains $_.name }
if ($Scanners -and $Scanners.Count -gt 0) {
$scannerList = $scannerList | Where-Object { $Scanners -contains $_.name }
}
if (-not $scannerList) {
Write-Host "[WARNING] No eligible scanners to remediate." -ForegroundColor Yellow
exit 0
}
Write-Host ""
Write-Host "=== GuruScan Remediation ===" -ForegroundColor Cyan
Write-Host " Log root : $LogRoot"
Write-Host " Scanners : $($scannerList.name -join ', ')"
Write-Host ""
# ---------------------------------------------------------------------------
# Run scanners in clean mode
# ---------------------------------------------------------------------------
$results = [System.Collections.Generic.List[pscustomobject]]::new()
$startedAt = Get-Date
$totalThreats = 0
foreach ($s in $scannerList) {
Write-Host "------------------------------------------------------------" -ForegroundColor DarkGray
Write-Host " Scanner : $($s.name) [CLEAN MODE]" -ForegroundColor Cyan
# ------------------------------------------------------------------
# Resolve EXE paths
# ------------------------------------------------------------------
$mainExePath = $null
$installerExePath = $null
if ($s.installer_exe) {
$installerExePath = if ([System.IO.Path]::IsPathRooted($s.installer_exe)) {
$s.installer_exe
} else {
Join-Path $ScriptRoot $s.installer_exe
}
}
$rawExe = $s.exe
if ([System.IO.Path]::IsPathRooted($rawExe)) {
$mainExePath = $rawExe
} else {
$mainExePath = Join-Path $ScriptRoot $rawExe
}
$exeToCheck = if ($installerExePath) { $installerExePath } else { $mainExePath }
if (-not (Test-Path $exeToCheck)) {
Write-Host " [WARNING] EXE not found — skipping: $exeToCheck" -ForegroundColor Yellow
$results.Add([pscustomobject]@{
Scanner = $s.name
Status = 'SKIPPED (missing)'
ExitCode = $null
ThreatsFound = 0
Duration = '0 min'
LogPath = ''
})
continue
}
# ------------------------------------------------------------------
# Resolve args (always clean_args)
# ------------------------------------------------------------------
$exeDir = Split-Path $mainExePath -Parent
$expandedArgs = Expand-TokenizedArgs -Args @($s.clean_args) -LogRoot $LogRoot -ExeDir $exeDir
# ------------------------------------------------------------------
# HitmanPro trial reset
# ------------------------------------------------------------------
if ($s.hitmanpro_trial_reset -eq $true) {
Write-Host " [INFO] Resetting HitmanPro trial registry..." -ForegroundColor Cyan
Invoke-HitmanProTrialReset
}
# ------------------------------------------------------------------
# Pre-clean
# ------------------------------------------------------------------
foreach ($p in $s.pre_clean_paths) {
Remove-PathSilent $p
}
# ------------------------------------------------------------------
# Timeout
# ------------------------------------------------------------------
$timeoutSec = $s.timeout_min * 60
# ------------------------------------------------------------------
# Two-step install (Emsisoft)
# ------------------------------------------------------------------
if ($installerExePath) {
Write-Host " [INFO] Running installer: $installerExePath" -ForegroundColor Cyan
try {
$instProc = Start-Process -FilePath $installerExePath -ArgumentList '/S' -PassThru -NoNewWindow
$instCompleted = Wait-ProcessWithTimeout -Process $instProc -TimeoutSeconds 300
if (-not $instCompleted) {
Write-Host " [WARNING] Installer timed out — skipping" -ForegroundColor Yellow
$results.Add([pscustomobject]@{
Scanner = $s.name
Status = 'FAILED (installer timeout)'
ExitCode = $null
ThreatsFound = 0
Duration = '0 min'
LogPath = ''
})
continue
}
}
catch {
Write-Host " [ERROR] Installer failed: $_" -ForegroundColor Red
$results.Add([pscustomobject]@{
Scanner = $s.name
Status = "FAILED (installer error)"
ExitCode = $null
ThreatsFound = 0
Duration = '0 min'
LogPath = ''
})
continue
}
if (-not (Test-Path $mainExePath)) {
Write-Host " [ERROR] Main EXE missing after install: $mainExePath" -ForegroundColor Red
$results.Add([pscustomobject]@{
Scanner = $s.name
Status = 'FAILED (post-install EXE missing)'
ExitCode = $null
ThreatsFound = 0
Duration = '0 min'
LogPath = ''
})
continue
}
Write-Host " [INFO] Updating Emsisoft definitions..." -ForegroundColor Cyan
try {
$updateProc = Start-Process -FilePath $mainExePath -ArgumentList '/update' -PassThru -NoNewWindow
Wait-ProcessWithTimeout -Process $updateProc -TimeoutSeconds 300 | Out-Null
}
catch {
Write-Host " [WARNING] Update step failed: $_ — continuing with existing definitions" -ForegroundColor Yellow
}
}
# ------------------------------------------------------------------
# Randomize EXE (TDSSKiller)
# ------------------------------------------------------------------
$randomizedExePath = $null
$launchExe = $mainExePath
if ($s.randomize_exe -eq $true) {
$tempName = [System.IO.Path]::GetRandomFileName() -replace '\..*$', ''
$randomizedExePath = Join-Path $env:TEMP "$tempName.exe"
Copy-Item -Path $mainExePath -Destination $randomizedExePath -Force
$launchExe = $randomizedExePath
Write-Host " [INFO] TDSSKiller randomized to: $randomizedExePath" -ForegroundColor Cyan
}
# ------------------------------------------------------------------
# Launch
# ------------------------------------------------------------------
Write-Host " [....] Launching $($s.name) in clean mode..." -ForegroundColor Cyan
$scanStart = Get-Date
$proc = $null
$status = 'completed'
$exitCode = $null
try {
$startParams = @{
FilePath = $launchExe
PassThru = $true
NoNewWindow = $true
}
if ($expandedArgs -and $expandedArgs.Count -gt 0) {
$startParams['ArgumentList'] = $expandedArgs
}
$proc = Start-Process @startParams
$completed = Wait-ProcessWithTimeout -Process $proc -TimeoutSeconds $timeoutSec
if (-not $completed) {
$status = 'TIMED OUT'
Write-Host " [WARNING] $($s.name) timed out after $($s.timeout_min) min" -ForegroundColor Yellow
} else {
$exitCode = $proc.ExitCode
}
}
catch {
$status = 'FAILED'
Write-Host " [ERROR] $($s.name) failed: $_" -ForegroundColor Red
}
# ------------------------------------------------------------------
# Wait for services
# ------------------------------------------------------------------
if ($s.service_names -and $s.service_names.Count -gt 0) {
Wait-ServicesToStop -ServiceNames $s.service_names -TimeoutSeconds 180
}
# ------------------------------------------------------------------
# Duration
# ------------------------------------------------------------------
$durMin = [math]::Round(((Get-Date) - $scanStart).TotalMinutes, 2)
# ------------------------------------------------------------------
# Collect logs
# ------------------------------------------------------------------
$logDestDir = Join-Path $LogRoot "$($s.name)_Remediation_Logs"
if ($s.log_src) {
$expandedLogSrc = Expand-TokenizedArgs -Args @($s.log_src) -LogRoot $LogRoot -ExeDir $exeDir
$expandedLogSrc = Expand-EnvPath $expandedLogSrc[0]
if (Test-Path $expandedLogSrc) {
New-Item -ItemType Directory -Path $logDestDir -Force | Out-Null
Copy-Item -Path $expandedLogSrc -Destination $logDestDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# ------------------------------------------------------------------
# Post-clean
# ------------------------------------------------------------------
foreach ($p in $s.post_clean_paths) {
Remove-PathSilent $p
}
# ------------------------------------------------------------------
# Randomized EXE cleanup
# ------------------------------------------------------------------
if ($randomizedExePath -and (Test-Path $randomizedExePath)) {
Remove-Item $randomizedExePath -Force -ErrorAction SilentlyContinue
}
# ------------------------------------------------------------------
# Threats
# ------------------------------------------------------------------
$threatsFound = 0
if ($null -ne $exitCode) {
$threatsFound = Get-ExitCodeThreats -ScannerName $s.name -ExitCode $exitCode
}
$totalThreats += $threatsFound
$color = if ($status -eq 'completed') { 'Green' } elseif ($status -eq 'TIMED OUT') { 'Yellow' } else { 'Red' }
Write-Host " [$status] ExitCode=$exitCode Threats=$threatsFound Duration=${durMin}min" -ForegroundColor $color
$results.Add([pscustomobject]@{
Scanner = $s.name
Status = $status
ExitCode = $exitCode
ThreatsFound = $threatsFound
Duration = "$durMin min"
LogPath = $logDestDir
})
}
# ---------------------------------------------------------------------------
# Write remediation-results.json
# ---------------------------------------------------------------------------
$completedAt = Get-Date
$scannerResults = foreach ($r in $results) {
$durValue = 0
if ($r.Duration -match '^([\d.]+)') {
$durValue = [double]$Matches[1]
}
[ordered]@{
name = $r.Scanner
status = $r.Status
exit_code = $r.ExitCode
threats_found = $r.ThreatsFound
duration_min = $durValue
log_path = $r.LogPath
}
}
$remObj = [ordered]@{
scan_id = $priorData.scan_id
machine = $env:COMPUTERNAME
started_at = $startedAt.ToUniversalTime().ToString('o')
completed_at = $completedAt.ToUniversalTime().ToString('o')
total_threats = $totalThreats
reboot_required = $false
scan_mode = 'clean'
scanners = @($scannerResults)
}
$remResultsPath = Join-Path $LogRoot 'remediation-results.json'
$remObj | ConvertTo-Json -Depth 10 | Set-Content -Path $remResultsPath -Encoding UTF8
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " REMEDIATION COMPLETE" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host ""
$results | Format-Table -AutoSize
Write-Host ""
if ($totalThreats -gt 0) {
Write-Host " [WARNING] $totalThreats threat indicator(s) still detected after clean pass." -ForegroundColor Yellow
Write-Host " Review logs carefully — some threats may require a reboot before" -ForegroundColor Yellow
Write-Host " they can be fully removed. Reboot and re-scan." -ForegroundColor Yellow
} else {
Write-Host " [OK] Clean pass complete — no threats detected in output." -ForegroundColor Green
}
Write-Host ""
Write-Host " Remediation results: $remResultsPath" -ForegroundColor Gray
Write-Host ""
exit 0
Import-Module $moduleManifest -Force
Invoke-Remediation @PSBoundParameters

View File

@@ -0,0 +1,169 @@
# GuruScan
Multi-engine malware scan orchestrator for GuruRMM. Runs a sequenced chain of
portable security scanners, captures all logs, and writes structured JSON results
for downstream processing by the RMM agent.
---
## Deploy Layout
| Path | Purpose |
|------|---------|
| `C:\GuruScan\` | Base directory — module files, whitelist, state files |
| `C:\GuruScan\downloads\` | Scanner EXEs (populated by `Download-Scanners.ps1`) |
| `C:\GuruScan\reports\` | Per-scan zip archives |
| `C:\ScanLogs\` | Per-scan log folders (`<HOSTNAME>-<YYYYMMDD-HHmmss>\`) |
The module files (`GuruScan.psm1`, `GuruScan.psd1`, `scanners.json`) live in the
same directory as the launcher scripts (`Invoke-GuruScan.ps1`, etc.). This is
typically `C:\GuruScan\` or the RMM deployment path — wherever the scripts are placed.
---
## Scanner Chain
Scanners run in this order. Each stage hands off to the next regardless of findings.
| # | Scanner | Category | Notes |
|---|---------|----------|-------|
| 1 | **RKill** | process-killer | Kills malware-related processes before scanners run. Exit 1 = processes were killed (not a threat indicator). |
| 2 | **AdwCleaner** | adware | Removes adware, PUPs, and browser hijackers. |
| 3 | **Emsisoft Command Line Scanner** | antimalware | Two-step: NSIS installer extracts to `C:\EmsisoftCmd\`, then `/update` fetches latest definitions, then scans. |
| 4 | **HitmanPro** | antimalware | Cloud-assisted second-opinion scanner. Trial registry is reset before each run via `Invoke-HitmanProTrialReset`. |
| 5 | **ESET Online Scanner** | antimalware | Skipped automatically when running as SYSTEM (requires interactive desktop). |
MSERT (Microsoft Safety Scanner) is excluded from the default chain — it is too slow
for routine remediation runs. Add it back to `scanners.json` if needed.
---
## Exit Code Semantics
| Scanner | Exit 0 | Exit 1 | Exit 2 | Exit 3 | Other |
|---------|--------|--------|--------|--------|-------|
| RKill | Clean run | Processes killed (not a threat) | — | — | — |
| AdwCleaner | Clean | Cleaned, no reboot needed | Cleaned, reboot required | Found but not cleaned (scan-only) | — |
| Emsisoft | Clean | Threats found/cleaned | Cleaned, reboot required | — | — |
| HitmanPro | Clean | Cleaned | Cleaned, reboot required | — | — |
| ESET | Clean | Threats found | Incomplete removal, reboot may help | — | — |
| MSERT | Clean | Threats found/cleaned | — | — | Non-zero = threats |
| TDSSKiller | Clean | Threats found | — | — | — |
| Stinger | Clean | — | — | — | 13 = threats |
Reboot-required exit codes: AdwCleaner 2, HitmanPro 2, Emsisoft 2, ESET 2.
---
## Autologon / Cleanup Lifecycle
When any scanner exits with a reboot-required code (exit 2), the following sequence runs:
1. `Invoke-RebootCleanupSetup` writes `cleanup-state.json` with the original user, scan ID, and log path.
2. A hidden `GuruRMM-Temp` administrator account is created with a random password.
3. One-time autologon (`AutoLogonCount=1`) is configured for `GuruRMM-Temp`. Windows clears the password after the first use.
4. The account is hidden from the login screen via the `SpecialAccounts\UserList` registry key.
5. A logon-triggered scheduled task (`GuruRMM-PostRebootCleanup`) is registered for `GuruRMM-Temp`.
6. The machine reboots after a 15-second warning.
7. On next boot, Windows auto-logs in as `GuruRMM-Temp`. The WPF splash appears immediately (full-screen, black, cursor hidden).
8. `Invoke-PostRebootCleanup` runs: verifies pending operations cleared, removes scanner files, writes `logs-ready.json`, restores the original user's login name, clears autologon, removes the cleanup task.
9. A SYSTEM scheduled task (`GuruRMM-TempUserDelete`) is registered to delete the `GuruRMM-Temp` account 2 minutes later (cannot delete your own account while logged in).
10. The splash closes, `logoff` is called, and the machine returns to the normal login screen.
---
## Headless / SYSTEM Behavior
- `-Headless` passes `NoNewWindow` to all scanner launches, suppressing UI windows.
Use this when dispatching from an RMM agent that has no interactive desktop.
- ESET is automatically skipped when the script detects it is running as the SYSTEM
account (`[System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem`).
Pass `-SkipEset` explicitly to skip it under other accounts.
---
## Licensing
| Scanner | License for MSP use |
|---------|---------------------|
| RKill | Free (BleepingComputer) |
| AdwCleaner | Free for personal and commercial use |
| Emsisoft Command Line Scanner | Free for personal and MSP remediation use |
| HitmanPro | Commercial license required. Each scan uses trial mode; `Invoke-HitmanProTrialReset` resets the trial window. Verify current licensing terms at https://www.hitmanpro.com before deploying at scale. |
| ESET Online Scanner | Free for personal and commercial use |
Always verify current licensing terms with each vendor before large-scale deployment.
---
## Stand-alone Usage
```powershell
# Run all scanners in clean mode (default)
.\Invoke-GuruScan.ps1
# Detect only, then auto-remediate if threats found
.\Invoke-GuruScan.ps1 -ScanOnly -AutoRemediate
# Skip ESET (e.g. unattended run)
.\Invoke-GuruScan.ps1 -SkipEset
# Suppress scanner windows (RMM dispatch)
.\Invoke-GuruScan.ps1 -Headless
# View the latest scan results
.\Get-ScanSummary.ps1
# View with AI analysis (requires Ollama)
.\Get-ScanSummary.ps1 -AI
# Re-run clean pass against a prior scan
.\Invoke-Remediation.ps1 -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000"
# Download/refresh scanner EXEs
.\Download-Scanners.ps1
```
---
## Module Usage
The launcher scripts are thin wrappers. Import the module directly for
scripted/pipeline use:
```powershell
Import-Module .\GuruScan.psd1
# Disk sink (default) — writes results.json + CSV + zip
Invoke-GuruScan
# RMM sink — returns result object to the pipeline, no disk writes
$result = Invoke-GuruScan -OutputSink RMM -Headless
if ($result.total_threats -gt 0) { ... }
# Remediation from a prior scan
Invoke-Remediation -LogRoot "C:\ScanLogs\DESKTOP-20260523-143000"
# Summary report
Get-ScanSummary -AI
# Post-reboot cleanup (called by Invoke-PostRebootCleanup.ps1)
Invoke-PostRebootCleanup -StateFile "C:\GuruScan\cleanup-state.json"
```
---
## Module Structure
```
guru-scan\
GuruScan.psm1 # Core module — all helpers + exported cmdlets
GuruScan.psd1 # Module manifest
scanners.json # Scanner definitions (single source of truth)
Invoke-GuruScan.ps1 # Thin launcher -> Invoke-GuruScan
Invoke-Remediation.ps1 # Thin launcher -> Invoke-Remediation
Get-ScanSummary.ps1 # Thin launcher -> Get-ScanSummary
Invoke-PostRebootCleanup.ps1 # WPF splash + logoff; delegates cleanup to module
Download-Scanners.ps1 # Downloads scanner EXEs from scanners.json URLs
downloads\ # Scanner EXEs (gitignored)
```

View File

@@ -0,0 +1,143 @@
{
"scanners": [
{
"name": "RKill",
"category": "process-killer",
"exe": "C:\\GuruScan\\downloads\\rkill.exe",
"installer_exe": null,
"installer_args": null,
"run_update_after_install": false,
"download_url": "https://download.bleepingcomputer.com/grinler/rkill.exe",
"manual_download": false,
"manual_download_note": null,
"scan_args": ["-s", "-l \"{LOG_ROOT}\\rkill.log\""],
"clean_args": ["-s", "-l \"{LOG_ROOT}\\rkill.log\""],
"log_src": "{LOG_ROOT}\\rkill.log",
"timeout_min": 10,
"randomize_exe": false,
"pre_close_processes": [],
"pre_clean_paths": [],
"post_clean_paths": [],
"service_names": [],
"hitmanpro_trial_reset": false,
"whitelist_arg": null,
"wait_on_process": null
},
{
"name": "AdwCleaner",
"category": "adware",
"exe": "C:\\GuruScan\\downloads\\adwcleaner.exe",
"installer_exe": null,
"installer_args": null,
"run_update_after_install": false,
"download_url": "https://adwcleaner.malwarebytes.com/adwcleaner?channel=release",
"manual_download": true,
"manual_download_note": "Malwarebytes blocks automated downloads; download manually from https://www.malwarebytes.com/adwcleaner",
"scan_args": ["/eula", "/scan", "/noreboot"],
"clean_args": ["/eula", "/clean", "/noreboot"],
"log_src": "C:\\AdwCleaner\\Logs",
"timeout_min": 60,
"randomize_exe": false,
"pre_close_processes": [],
"pre_clean_paths": ["C:\\AdwCleaner"],
"post_clean_paths": ["C:\\AdwCleaner"],
"service_names": ["AdwCleanerSvc"],
"hitmanpro_trial_reset": false,
"whitelist_arg": null,
"wait_on_process": "AdwCleaner"
},
{
"name": "Emsisoft",
"category": "antimalware",
"exe": "C:\\EmsisoftCmd\\a2cmd.exe",
"installer_exe": "C:\\GuruScan\\downloads\\EmsisoftCommandlineScanner64.exe",
"installer_args": ["/S"],
"run_update_after_install": true,
"download_url": "https://dl.emsisoft.com/EmsisoftCommandlineScanner64.exe",
"manual_download": false,
"manual_download_note": null,
"scan_args": [
"/f=C:\\",
"/deep",
"/rk",
"/m",
"/t",
"/pup",
"/a",
"/n",
"/ac",
"/d",
"/wl=\"C:\\GuruScan\\whitelist.txt\"",
"/la=\"{LOG_ROOT}\\a2cmd_deep_log.txt\""
],
"clean_args": [
"/f=C:\\",
"/deep",
"/rk",
"/m",
"/t",
"/c",
"/pup",
"/a",
"/n",
"/ac",
"/d",
"/wl=\"C:\\GuruScan\\whitelist.txt\"",
"/la=\"{LOG_ROOT}\\a2cmd_deep_log.txt\""
],
"log_src": null,
"timeout_min": 120,
"randomize_exe": false,
"pre_close_processes": [],
"pre_clean_paths": ["C:\\EmsisoftCmd"],
"post_clean_paths": ["C:\\EmsisoftCmd"],
"service_names": [],
"hitmanpro_trial_reset": false,
"whitelist_arg": "emsisoft",
"wait_on_process": "a2cmd"
},
{
"name": "HitmanPro",
"category": "antimalware",
"exe": "C:\\GuruScan\\downloads\\HitmanPro_x64.exe",
"installer_exe": null,
"installer_args": null,
"run_update_after_install": false,
"download_url": null,
"manual_download": true,
"manual_download_note": "Requires a trial/license — download from https://www.hitmanpro.com/en-us/hmp.aspx",
"scan_args": [
"/noinstall",
"/scan",
"/log=\"{LOG_ROOT}\\HitmanPro_Scan_Log.txt\"",
"/excludelist=\"C:\\GuruScan\\whitelist.txt\""
],
"clean_args": [
"/noinstall",
"/clean",
"/log=\"{LOG_ROOT}\\HitmanPro_Scan_Log.txt\"",
"/excludelist=\"C:\\GuruScan\\whitelist.txt\""
],
"log_src": null,
"timeout_min": 60,
"randomize_exe": false,
"pre_close_processes": ["chrome", "firefox", "msedge", "brave", "opera", "iexplore", "operagx", "MicrosoftEdge"],
"pre_clean_paths": [
"C:\\ProgramData\\HitmanPro",
"C:\\ProgramData\\HitmanPro.Alert",
"%LOCALAPPDATA%\\HitmanPro",
"%LOCALAPPDATA%\\HitmanPro.Alert"
],
"post_clean_paths": [
"C:\\ProgramData\\HitmanPro",
"C:\\ProgramData\\HitmanPro.Alert",
"%LOCALAPPDATA%\\HitmanPro",
"%LOCALAPPDATA%\\HitmanPro.Alert"
],
"service_names": [],
"hitmanpro_trial_reset": true,
"whitelist_arg": "hitmanpro",
"wait_on_process": "HitmanPro_x64"
}
]
}