feat(guru-scan): fix exit code capture, add GURUSCAN_RESULT_JSON reporting, pre-scan hardening
Exit code fix: add $proc.Handle caching after Start-Process -PassThru to prevent
the handle from being released before ExitCode is readable (known PS5.1 bug).
GuruRMM reporting: launcher now finds results.json after each scan and emits
GURUSCAN_RESULT_JSON:<compressed> to stdout. Agent CommandResult captures it;
server stores it in commands.stdout for retrieval via GET /api/commands/:id.
Pre-scan hardening:
- Pre-flight EXE check: warns about missing scanner binaries before run starts
- Windows Defender exclusions added for scanner/log paths before scan, removed after
AdwCleaner: add /path {LOG_ROOT} arg so logs write directly to scan log root;
update log_src to {LOG_ROOT}\Logs to match.
HitmanPro: add /quiet to scan and clean args to suppress GUI in headless runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# GuruScan.psm1
|
||||
# Multi-engine malware scan orchestrator for GuruRMM.
|
||||
# PowerShell 5.1 compatible — no ternary, no ??, no ?. operators.
|
||||
# PowerShell 5.1 compatible -- no ternary, no ??, no ?. operators.
|
||||
#
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -73,7 +73,7 @@ function Wait-ProcessWithTimeout {
|
||||
.OUTPUTS
|
||||
$true if the process exited cleanly within the timeout, $false if it was killed.
|
||||
.NOTES
|
||||
Calls WaitForExit(5000) before returning to flush the exit code — required
|
||||
Calls WaitForExit(5000) before returning to flush the exit code -- required
|
||||
before the ExitCode property is reliably readable on fast-exit processes.
|
||||
#>
|
||||
param(
|
||||
@@ -89,7 +89,7 @@ function Wait-ProcessWithTimeout {
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
# Flush exit code — required before ExitCode property is readable
|
||||
# Flush exit code -- required before ExitCode property is readable
|
||||
$Process.WaitForExit(5000) | Out-Null
|
||||
return $true
|
||||
}
|
||||
@@ -184,7 +184,7 @@ function Get-ExitCodeThreats {
|
||||
'MSERT' { if ($ExitCode -ne 0) { return 1 } }
|
||||
'TDSSKiller' { if ($ExitCode -eq 1) { return 1 } }
|
||||
'Stinger' { if ($ExitCode -eq 13) { return 1 } }
|
||||
# RKill: exit 1 = processes were killed — not a threat count
|
||||
# RKill: exit 1 = processes were killed -- not a threat count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -399,7 +399,7 @@ function Invoke-ScanPass {
|
||||
Write-Host " Scanner : $($s.Name)" -ForegroundColor Cyan
|
||||
Write-Host " Mode : $ScanMode"
|
||||
|
||||
# Normalize property names — JSON objects use snake_case, PS hashtables use PascalCase
|
||||
# Normalize property names -- JSON objects use snake_case, PS hashtables use PascalCase
|
||||
$sName = if ($s.PSObject.Properties['Name']) { $s.Name } else { $s.name }
|
||||
$sExe = if ($s.PSObject.Properties['Exe']) { $s.Exe } else { $s.exe }
|
||||
$sInstallerExe = if ($s.PSObject.Properties['InstallerExe']) { $s.InstallerExe } else { $s.installer_exe }
|
||||
@@ -416,8 +416,10 @@ function Invoke-ScanPass {
|
||||
$sWaitOnProcess = if ($s.PSObject.Properties['WaitOnProcess']) { $s.WaitOnProcess } else { $s.wait_on_process }
|
||||
$sInstallerArgs = if ($s.PSObject.Properties['InstallerArgs']) { $s.InstallerArgs } else { $s.installer_args }
|
||||
$sRunUpdateAfterInstall = if ($s.PSObject.Properties['RunUpdateAfterInstall']) { $s.RunUpdateAfterInstall } else { $s.run_update_after_install }
|
||||
$sSession0Compatible = if ($s.PSObject.Properties['Session0Compatible']) { $s.Session0Compatible } else { $s.session0_compatible }
|
||||
if ($null -eq $sInstallerArgs) { $sInstallerArgs = @('/S') }
|
||||
if ($null -eq $sRunUpdateAfterInstall) { $sRunUpdateAfterInstall = $false }
|
||||
if ($null -eq $sSession0Compatible) { $sSession0Compatible = $true }
|
||||
|
||||
# Coerce null JSON values to empty arrays / nulls
|
||||
if ($null -eq $sInstallerExe) { $sInstallerExe = $null }
|
||||
@@ -428,6 +430,20 @@ function Invoke-ScanPass {
|
||||
if ($null -eq $sPostClean) { $sPostClean = @() }
|
||||
if ($null -eq $sServiceNames) { $sServiceNames = @() }
|
||||
|
||||
# Skip GUI-only scanners when running as SYSTEM in Session 0 (no interactive desktop)
|
||||
if (-not $sSession0Compatible -and (Test-RunningAsSystem)) {
|
||||
Write-Host " [WARNING] $sName requires an interactive user session -- skipping (running as SYSTEM)" -ForegroundColor Yellow
|
||||
$results.Add([pscustomobject]@{
|
||||
Scanner = $sName
|
||||
Status = 'SKIPPED (requires user session)'
|
||||
ExitCode = $null
|
||||
ThreatsFound = 0
|
||||
Duration = '0 min'
|
||||
LogPath = ''
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
$mainExePath = $sExe
|
||||
$installerExePath = $sInstallerExe
|
||||
$exeToCheck = if ($installerExePath) { $installerExePath } else { $mainExePath }
|
||||
@@ -524,6 +540,7 @@ function Invoke-ScanPass {
|
||||
}
|
||||
|
||||
$proc = Start-Process @startParams
|
||||
$null = $proc.Handle # force handle caching -- Start-Process -PassThru can lose the handle before exit, making ExitCode return null
|
||||
|
||||
$serviceArr = @()
|
||||
if ($sServiceNames -and $sServiceNames.Count -gt 0) {
|
||||
@@ -539,6 +556,7 @@ function Invoke-ScanPass {
|
||||
$status = 'TIMED OUT'
|
||||
Write-Host " [WARNING] $sName timed out after $effectiveTimeoutMin min" -ForegroundColor Yellow
|
||||
} else {
|
||||
$proc.WaitForExit() | Out-Null # no-arg form guarantees ExitCode is readable
|
||||
$exitCode = $proc.ExitCode
|
||||
}
|
||||
}
|
||||
@@ -619,7 +637,7 @@ function Register-ScannerCleanupTask {
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '------------------------------------------------------------' -ForegroundColor Yellow
|
||||
Write-Host ' Reboot recommended — registering post-reboot cleanup task' -ForegroundColor Yellow
|
||||
Write-Host ' Reboot recommended -- registering post-reboot cleanup task' -ForegroundColor Yellow
|
||||
Write-Host '------------------------------------------------------------' -ForegroundColor Yellow
|
||||
|
||||
# Write state for the cleanup script to read
|
||||
@@ -629,45 +647,14 @@ function Register-ScannerCleanupTask {
|
||||
created_at = (Get-Date).ToUniversalTime().ToString('o')
|
||||
} | ConvertTo-Json | Set-Content "$script:Base\cleanup-state.json" -Encoding UTF8
|
||||
|
||||
# Write the cleanup script that the scheduled task will run
|
||||
$cleanupScript = @'
|
||||
$Base = 'C:\GuruScan'
|
||||
$state = @{ scan_id = ''; log_root = '' }
|
||||
$stateFile = "$Base\cleanup-state.json"
|
||||
|
||||
if (Test-Path $stateFile) {
|
||||
try { $state = Get-Content $stateFile -Raw | ConvertFrom-Json } catch {}
|
||||
}
|
||||
|
||||
# Remove scanner installation files
|
||||
$scannerPaths = @(
|
||||
'C:\EmsisoftCmd',
|
||||
'C:\AdwCleaner',
|
||||
'C:\ProgramData\HitmanPro',
|
||||
'C:\ProgramData\HitmanPro.Alert'
|
||||
)
|
||||
foreach ($p in $scannerPaths) {
|
||||
Remove-Item -Path $p -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Remove scanner download EXEs
|
||||
Remove-Item -Path "$Base\downloads" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Flag logs for GuruRMM to pull
|
||||
@{
|
||||
scan_id = $state.scan_id
|
||||
log_root = $state.log_root
|
||||
zip_path = "$Base\reports\$($state.scan_id).zip"
|
||||
cleaned_at = (Get-Date).ToUniversalTime().ToString('o')
|
||||
} | ConvertTo-Json | Set-Content "$Base\logs-ready.json" -Encoding UTF8
|
||||
|
||||
# Remove state file
|
||||
Remove-Item -Path $stateFile -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Unregister this task
|
||||
Unregister-ScheduledTask -TaskName 'GuruRMM-ScannerCleanup' -Confirm:$false -ErrorAction SilentlyContinue
|
||||
'@
|
||||
$cleanupScript | Set-Content "$script:Base\Invoke-ScannerCleanup.ps1" -Encoding UTF8
|
||||
# Copy the static cleanup script from the module directory to C:\GuruScan\
|
||||
$cleanupSrc = Join-Path $script:ModuleRoot 'Invoke-ScannerCleanup.ps1'
|
||||
if (Test-Path $cleanupSrc) {
|
||||
Copy-Item -Path $cleanupSrc -Destination "$script:Base\Invoke-ScannerCleanup.ps1" -Force
|
||||
} else {
|
||||
Write-Host " [WARNING] Invoke-ScannerCleanup.ps1 not found at $cleanupSrc -- cleanup task will not run." -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
# Register as SYSTEM logon task with 30-minute delay
|
||||
try {
|
||||
@@ -776,11 +763,11 @@ function Invoke-GuruScan {
|
||||
[string]$OutputSink = 'Disk'
|
||||
)
|
||||
|
||||
# Whitelist — written to C:\GuruScan\whitelist.txt before any scanner runs.
|
||||
# Whitelist -- written to C:\GuruScan\whitelist.txt before any scanner runs.
|
||||
# Emsisoft and HitmanPro honour this; RKill and AdwCleaner do not.
|
||||
$whitelist = @('C:\GuruScan')
|
||||
|
||||
# ForceRemove blacklist — items removed after all scanners complete.
|
||||
# ForceRemove blacklist -- items removed after all scanners complete.
|
||||
$forceRemove = @()
|
||||
|
||||
$scanStamp = "$env:COMPUTERNAME-$(Get-Date -Format yyyyMMdd-HHmmss)"
|
||||
@@ -836,6 +823,31 @@ function Invoke-GuruScan {
|
||||
}
|
||||
}
|
||||
|
||||
# Pre-flight: warn about missing scanner EXEs so missing scanners are
|
||||
# visible up front rather than silently skipped mid-run.
|
||||
$missingExes = @()
|
||||
foreach ($s in $scannerList) {
|
||||
$sName = if ($s.PSObject.Properties['Name']) { $s.Name } else { $s.name }
|
||||
$sExe = if ($s.PSObject.Properties['Exe']) { $s.Exe } else { $s.exe }
|
||||
$sInstExe = if ($s.PSObject.Properties['InstallerExe']) { $s.InstallerExe } else { $s.installer_exe }
|
||||
if (-not (Test-Path $sExe) -and (-not $sInstExe -or -not (Test-Path $sInstExe))) {
|
||||
$missingExes += $sName
|
||||
}
|
||||
}
|
||||
if ($missingExes.Count -gt 0) {
|
||||
Write-Host "[WARNING] Missing scanner EXEs -- these will be skipped: $($missingExes -join ', ')" -ForegroundColor Yellow
|
||||
Write-Host " Run .\Download-Scanners.ps1 to download them." -ForegroundColor Yellow
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
# Add Windows Defender exclusions for scanner paths so Defender does not
|
||||
# quarantine scanner EXEs or log files mid-run.
|
||||
$defenderExclusions = @($script:Base, $script:LogRoot, 'C:\EmsisoftCmd', 'C:\AdwCleaner')
|
||||
try {
|
||||
Add-MpPreference -ExclusionPath $defenderExclusions -ErrorAction SilentlyContinue
|
||||
Write-Host "[INFO] Windows Defender exclusions added for scanner paths" -ForegroundColor Cyan
|
||||
} catch {}
|
||||
|
||||
# First pass
|
||||
$startedAt = Get-Date
|
||||
$passParams = @{
|
||||
@@ -964,7 +976,14 @@ function Invoke-GuruScan {
|
||||
Register-ScannerCleanupTask -ScanId $scanStamp -LogRoot $runLogRoot
|
||||
}
|
||||
|
||||
# Return result object (always returned; caller uses it for RMM sink)
|
||||
# Remove the Defender exclusions added at scan start.
|
||||
try {
|
||||
Remove-MpPreference -ExclusionPath $defenderExclusions -ErrorAction SilentlyContinue
|
||||
Write-Host "[INFO] Windows Defender exclusions removed" -ForegroundColor Cyan
|
||||
} catch {}
|
||||
|
||||
# Return result object (RMM sink) or suppress it (Disk sink lets the
|
||||
# launcher read results.json from disk for structured reporting).
|
||||
$resultRecord = [pscustomobject]$resultsObj
|
||||
if ($OutputSink -eq 'RMM') {
|
||||
return $resultRecord
|
||||
@@ -1093,10 +1112,10 @@ function Invoke-Remediation {
|
||||
|
||||
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 " 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 " [OK] Clean pass complete -- no threats detected in output." -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
@@ -1271,7 +1290,7 @@ function Get-ScanSummary {
|
||||
Write-Host " Full logs: $(Split-Path $ResultsFile)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Ollama AI analysis (optional — requires -AI switch)
|
||||
# Ollama AI analysis (optional -- requires -AI switch)
|
||||
if ($AI) {
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " AI Analysis (Ollama)" -ForegroundColor Cyan
|
||||
@@ -1311,7 +1330,7 @@ $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.
|
||||
Do not add commentary -- only the list.
|
||||
"@
|
||||
|
||||
$threatDetails = Invoke-Ollama -Prompt $threatPrompt -Model $OllamaModel -BaseUrl $OllamaUrl
|
||||
@@ -1320,7 +1339,7 @@ Do not add commentary — only the list.
|
||||
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
|
||||
Write-Host " [WARNING] Ollama unavailable -- skipping threat extraction." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Step 2: Prioritized remediation recommendations
|
||||
@@ -1345,7 +1364,7 @@ Plain numbered list, no markdown headers, no padding text. Be specific.
|
||||
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 " [WARNING] Ollama unavailable -- skipping recommendations." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
@@ -1363,7 +1382,7 @@ function Invoke-PostRebootCleanup {
|
||||
Normally this runs automatically via the GuruRMM-ScannerCleanup scheduled task.
|
||||
.PARAMETER StateFile
|
||||
Path to cleanup-state.json. Defaults to C:\GuruScan\cleanup-state.json.
|
||||
(Parameter kept for backward compatibility — the cleanup script reads it directly.)
|
||||
(Parameter kept for backward compatibility -- the cleanup script reads it directly.)
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
|
||||
@@ -52,4 +52,22 @@ if (-not (Test-Path $moduleManifest)) {
|
||||
}
|
||||
|
||||
Import-Module $moduleManifest -Force
|
||||
$scanStart = Get-Date
|
||||
Invoke-GuruScan @PSBoundParameters -OutputSink Disk
|
||||
|
||||
# Emit structured JSON to stdout for GuruRMM CommandResult capture.
|
||||
# Read from results.json written during this run (newer than $scanStart).
|
||||
$resultsFile = Get-ChildItem -Path 'C:\ScanLogs' -Recurse -Filter 'results.json' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.LastWriteTime -gt $scanStart } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($resultsFile) {
|
||||
$json = Get-Content -Path $resultsFile.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
if ($json) {
|
||||
# Compress to single line so the agent's stdout parser sees it as one line
|
||||
$compressed = ($json | ConvertFrom-Json | ConvertTo-Json -Depth 10 -Compress)
|
||||
Write-Output ''
|
||||
Write-Output "GURUSCAN_RESULT_JSON:$compressed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ Scanners run in this order. Each stage hands off to the next regardless of findi
|
||||
| # | 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. |
|
||||
| 2 | **AdwCleaner** | adware | Removes adware, PUPs, and browser hijackers. **Requires an interactive user session** (GUI app; no headless/SYSTEM mode). Skipped automatically when running as SYSTEM with no desktop. To include AdwCleaner, dispatch via GuruRMM with `context: user_session`. |
|
||||
| 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`. |
|
||||
|
||||
@@ -75,6 +75,11 @@ To run cleanup immediately without waiting (e.g. if the task was missed):
|
||||
|
||||
- `-Headless` passes `NoNewWindow` to all scanner launches, suppressing UI windows.
|
||||
Use this when dispatching from an RMM agent that has no interactive desktop.
|
||||
- Scanners with `session0_compatible: false` in `scanners.json` are automatically skipped
|
||||
when the module detects it is running as SYSTEM (Session 0). Currently: **AdwCleaner**.
|
||||
The result record shows `SKIPPED (requires user session)` rather than a failure.
|
||||
- To run AdwCleaner via GuruRMM, dispatch with `context: user_session` so it runs in
|
||||
the active user's desktop session (requires a logged-in user on the target machine).
|
||||
|
||||
---
|
||||
|
||||
@@ -156,6 +161,7 @@ guru-scan\
|
||||
Invoke-Remediation.ps1 # Thin launcher -> Invoke-Remediation
|
||||
Get-ScanSummary.ps1 # Thin launcher -> Get-ScanSummary
|
||||
Invoke-PostRebootCleanup.ps1 # Thin launcher -> Invoke-PostRebootCleanup (manual cleanup trigger)
|
||||
Invoke-ScannerCleanup.ps1 # Post-reboot cleanup script; copied to C:\GuruScan\ when reboot is needed
|
||||
Download-Scanners.ps1 # Downloads scanner EXEs from scanners.json URLs
|
||||
downloads\ # Scanner EXEs (gitignored)
|
||||
```
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"service_names": [],
|
||||
"hitmanpro_trial_reset": false,
|
||||
"whitelist_arg": null,
|
||||
"wait_on_process": null
|
||||
"wait_on_process": null,
|
||||
"session0_compatible": true
|
||||
},
|
||||
{
|
||||
"name": "AdwCleaner",
|
||||
@@ -33,18 +34,19 @@
|
||||
"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",
|
||||
"scan_args": ["/eula", "/scan", "/noreboot", "/path", "{LOG_ROOT}"],
|
||||
"clean_args": ["/eula", "/clean", "/noreboot", "/path", "{LOG_ROOT}"],
|
||||
"log_src": "{LOG_ROOT}\\Logs",
|
||||
"timeout_min": 60,
|
||||
"randomize_exe": false,
|
||||
"pre_close_processes": [],
|
||||
"pre_clean_paths": ["C:\\AdwCleaner"],
|
||||
"post_clean_paths": ["C:\\AdwCleaner"],
|
||||
"post_clean_paths": [],
|
||||
"service_names": ["AdwCleanerSvc"],
|
||||
"hitmanpro_trial_reset": false,
|
||||
"whitelist_arg": null,
|
||||
"wait_on_process": "AdwCleaner"
|
||||
"wait_on_process": "AdwCleaner",
|
||||
"session0_compatible": false
|
||||
},
|
||||
{
|
||||
"name": "Emsisoft",
|
||||
@@ -94,7 +96,8 @@
|
||||
"service_names": [],
|
||||
"hitmanpro_trial_reset": false,
|
||||
"whitelist_arg": "emsisoft",
|
||||
"wait_on_process": "a2cmd"
|
||||
"wait_on_process": "a2cmd",
|
||||
"session0_compatible": true
|
||||
},
|
||||
{
|
||||
"name": "HitmanPro",
|
||||
@@ -109,12 +112,14 @@
|
||||
"scan_args": [
|
||||
"/noinstall",
|
||||
"/scan",
|
||||
"/quiet",
|
||||
"/log=\"{LOG_ROOT}\\HitmanPro_Scan_Log.txt\"",
|
||||
"/excludelist=\"C:\\GuruScan\\whitelist.txt\""
|
||||
],
|
||||
"clean_args": [
|
||||
"/noinstall",
|
||||
"/clean",
|
||||
"/quiet",
|
||||
"/log=\"{LOG_ROOT}\\HitmanPro_Scan_Log.txt\"",
|
||||
"/excludelist=\"C:\\GuruScan\\whitelist.txt\""
|
||||
],
|
||||
@@ -137,7 +142,8 @@
|
||||
"service_names": [],
|
||||
"hitmanpro_trial_reset": true,
|
||||
"whitelist_arg": "hitmanpro",
|
||||
"wait_on_process": "HitmanPro_x64"
|
||||
"wait_on_process": "HitmanPro_x64",
|
||||
"session0_compatible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user