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>
216 lines
9.5 KiB
PowerShell
216 lines
9.5 KiB
PowerShell
#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.
|
|
#>
|
|
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 {}
|
|
}
|
|
|
|
$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
|