diff --git a/.gitignore b/.gitignore
index 303df77..43a84f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -397,3 +397,7 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
.idea
+nuget.exe
+PowerShell/ThreatCheck.exe
+PowerShell/CommandLine.dll
+PowerShell/System.Management.Automation.dll
diff --git a/CONTRIBUTING-HBV.md b/CONTRIBUTING-HBV.md
new file mode 100644
index 0000000..120958a
--- /dev/null
+++ b/CONTRIBUTING-HBV.md
@@ -0,0 +1,263 @@
+# HoneyBadger Vanguard — PowerShell Wrapper Contribution
+
+> **Upstream:** [rasta-mouse/ThreatCheck](https://github.com/rasta-mouse/ThreatCheck)
+> **Wrapper Repo:** [MoSLoF/ThreatCheck](https://github.com/MoSLoF/ThreatCheck)
+> **Author:** HoneyBadger Vanguard LLC · [ihbv.io](https://ihbv.io)
+> **MITRE ATT&CK:** T1562.001 · T1027
+
+---
+
+## What This Adds
+
+This contribution wraps `ThreatCheck.exe` in a production-grade PowerShell module that integrates cleanly into purple team workflows and SIEM pipelines.
+
+| Feature | Description |
+|---------|-------------|
+| **Pipeline support** | Accepts `FileInfo` objects from `Get-ChildItem` via `ValueFromPipeline` |
+| **Structured output** | Returns typed `[PSCustomObject]` per scan (filterable, exportable) |
+| **Glob expansion** | Wildcard paths resolved internally before scanning |
+| **Wazuh SIEM logging** | NDJSON events with MITRE ATT&CK mapping, ready for Wazuh decoder |
+| **HTML threat report** | Self-contained dark-theme report with stats, badges, hex dump display |
+| **AMSI engine support** | `-Engine AMSI -Type Script` for PowerShell script scanning |
+| **URL scanning** | `-Url` parameter for remote file analysis |
+| **Verbose/Debug modes** | `-Verbose` and `-Debug` switches pass through to underlying exe |
+
+---
+
+## Files
+
+```
+PowerShell/
+├── Invoke-ThreatCheck.psm1 # Module implementation (~880 lines)
+├── Invoke-ThreatCheck.psd1 # Module manifest
+├── ThreatCheck.exe # Compiled binary (see Build section)
+├── CommandLine.dll # CommandLineParser dependency
+└── System.Management.Automation.dll
+```
+
+---
+
+## Build ThreatCheck.exe
+
+The module auto-discovers `ThreatCheck.exe` alongside the `.psm1` file.
+You must compile it first:
+
+```powershell
+# 1. Restore NuGet packages (required for old-style .csproj)
+.\nuget.exe restore ThreatCheck.sln
+
+# 2. Build Release
+$msbuild = 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe'
+& $msbuild ThreatCheck.sln /p:Configuration=Release /p:Platform="Any CPU"
+
+# 3. Copy binaries to PowerShell folder
+$releaseDir = '.\ThreatCheck\bin\Release'
+$psDir = '.\PowerShell'
+Copy-Item "$releaseDir\ThreatCheck.exe" $psDir
+Copy-Item "$releaseDir\CommandLine.dll" $psDir
+Copy-Item "$releaseDir\System.Management.Automation.dll" $psDir
+```
+
+> **NuGet:** Download `nuget.exe` from https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
+
+---
+
+## Requirements
+
+- PowerShell 7.0+
+- Windows (Defender engine requires `MpCmdRun.exe`)
+- Administrator rights recommended for Defender engine scans
+- `C:\Temp` must exist (Defender engine writes temp files there)
+
+### Defender Exclusions
+
+To prevent false-positive blocks on the module itself and your tools directory, add Defender exclusions before use:
+
+```powershell
+Add-MpPreference -ExclusionPath 'D:\YourToolsPath\ThreatCheck'
+Add-MpPreference -ExclusionPath 'D:\YourToolsPath\ThreatCheck\PowerShell'
+```
+
+> **Note:** If you see `WARNING: True appearing in output` it typically means the Defender exclusion is missing and MpCmdRun is being blocked mid-scan. The module filters stray `True`/`False` lines from stdout, but incomplete scans may still produce unexpected output.
+
+---
+
+## Installation
+
+```powershell
+# Option 1 — Import directly
+Import-Module '.\PowerShell\Invoke-ThreatCheck.psd1'
+
+# Option 2 — Add to PowerShell profile for persistent availability
+$profileLine = "Import-Module 'D:\YourPath\ThreatCheck\PowerShell\Invoke-ThreatCheck.psd1'"
+Add-Content $PROFILE $profileLine
+```
+
+---
+
+## Usage
+
+### Single File — Defender (default)
+```powershell
+Invoke-ThreatCheck -Path C:\Tools\Rubeus.exe
+```
+
+### Single File — AMSI Engine
+```powershell
+Invoke-ThreatCheck -Path C:\Tools\Invoke-OffensiveTool.ps1 -Engine AMSI -Type Script
+```
+
+### Pipeline — Bulk Scan
+```powershell
+Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck
+```
+
+### Filter Flagged Only
+```powershell
+Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck | Where-Object { -not $_.Clean }
+```
+
+### Wazuh SIEM Integration
+```powershell
+Get-ChildItem C:\Tools\*.exe |
+ Invoke-ThreatCheck -WazuhLogFile C:\Logs\threatcheck.json
+```
+
+### HTML Report
+```powershell
+Get-ChildItem C:\Tools\*.exe |
+ Invoke-ThreatCheck -ReportFile C:\Reports\tc-report.html
+```
+
+### Full Purple Team Run
+```powershell
+Get-ChildItem C:\Tools\*.exe |
+ Invoke-ThreatCheck `
+ -WazuhLogFile C:\Logs\threatcheck.json `
+ -ReportFile C:\Reports\tc-report.html |
+ Where-Object { -not $_.Clean } |
+ Select-Object Target, FlaggedOffset, Engine
+```
+
+### Scan from URL
+```powershell
+Invoke-ThreatCheck -Url 'https://example.com/payload.bin' -Engine Defender
+```
+
+---
+
+## Output Object Schema
+
+Each scan emits one `[PSCustomObject]` with these properties:
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `Timestamp` | `DateTime` | Scan start time |
+| `Target` | `string` | Full path to scanned file |
+| `Engine` | `string` | `Defender` or `AMSI` |
+| `Type` | `string` | `Bin` or `Script` |
+| `Clean` | `bool` | `$true` if no threat detected |
+| `FlaggedOffset` | `string` | Hex offset of detection (if flagged) |
+| `ErrorMessage` | `string` | Error text (if scan failed) |
+| `HexDump` | `string[]` | Hex dump lines around flagged offset |
+| `RawOutput` | `string[]` | Raw ThreatCheck.exe stdout lines |
+| `ScanDuration` | `TimeSpan` | Wall-clock scan time |
+
+---
+
+## Wazuh Integration
+
+### Event Schema (NDJSON)
+
+```json
+{
+ "timestamp": "2026-03-06T15:05:00.0000000-06:00",
+ "program_name": "Invoke-ThreatCheck",
+ "hbv_version": "1.0.0",
+ "event_type": "scan_threat_found",
+ "target": "C:\\Tools\\payload.exe",
+ "engine": "Defender",
+ "file_type": "Bin",
+ "clean": false,
+ "flagged_offset": "0x1A3F00",
+ "error_message": null,
+ "scan_duration_ms": 148.32,
+ "mitre_technique": "T1562.001",
+ "mitre_tactic": "Defense Evasion",
+ "mitre_technique2": "T1027",
+ "mitre_tactic2": "Defense Evasion"
+}
+```
+
+### Wazuh Decoder (`/var/ossec/etc/decoders/threatcheck.xml`)
+
+```xml
+
+ \"program_name\":\"Invoke-ThreatCheck\"
+ JSON_Decoder
+
+```
+
+### Wazuh Rule (`/var/ossec/etc/rules/threatcheck_rules.xml`)
+
+```xml
+
+
+
+ invoke-threatcheck
+ scan_threat_found
+ ThreatCheck: AV signature detected in $(target)
+
+ T1562.001
+ T1027
+
+ pci_dss_11.4,gdpr_IV_35.7.d,
+
+
+
+ invoke-threatcheck
+ scan_clean
+ ThreatCheck: Clean scan — $(target)
+
+
+
+```
+
+---
+
+## MITRE ATT&CK Coverage
+
+| Technique | Tactic | Description |
+|-----------|--------|-------------|
+| [T1562.001](https://attack.mitre.org/techniques/T1562/001/) | Defense Evasion | Identifies AV signatures enabling bypass research |
+| [T1027](https://attack.mitre.org/techniques/T1027/) | Defense Evasion | Supports obfuscation analysis and detection tuning |
+
+---
+
+## Purple Team Philosophy
+
+> *"Understand Offense to Build Better Defense."*
+> — HoneyBadger Vanguard LLC
+
+`Invoke-ThreatCheck` is designed for **purple team operators** who need to:
+
+1. **Red side** — Identify exact byte offsets triggering AV detection to inform evasion research
+2. **Blue side** — Validate detection coverage across tool inventories and generate audit-ready reports for SIEM pipelines
+
+The Wazuh integration closes the loop: flagged offsets become SIEM events, enabling defenders to track which tools in their environment would be caught vs. missed by current AV configurations.
+
+---
+
+## Known Limitations
+
+- Defender engine requires `Administrator` and `C:\Temp` to exist
+- AMSI engine requires the target file to exist on disk (no URL support for AMSI)
+- ThreatCheck.exe targets .NET Framework 4.8 — requires .NET 4.8 runtime on the host
+- Large binaries (>50MB) may hit the 60-second scan timeout
+
+---
+
+## License
+
+MIT — same as upstream [rasta-mouse/ThreatCheck](https://github.com/rasta-mouse/ThreatCheck).
+Wrapper additions © 2026 HoneyBadger Vanguard LLC.
diff --git a/PowerShell/Invoke-ThreatCheck.psd1 b/PowerShell/Invoke-ThreatCheck.psd1
new file mode 100644
index 0000000..a9006a7
--- /dev/null
+++ b/PowerShell/Invoke-ThreatCheck.psd1
@@ -0,0 +1,21 @@
+@{
+ RootModule = 'Invoke-ThreatCheck.psm1'
+ ModuleVersion = '1.0.0'
+ GUID = 'a3f2c8d1-4b7e-4f9a-b2c3-d8e5f1a6b7c9'
+ Author = 'HoneyBadger Vanguard LLC'
+ CompanyName = 'HoneyBadger Vanguard LLC'
+ Copyright = '(c) 2025 HoneyBadger Vanguard LLC. MIT License.'
+ Description = 'PowerShell wrapper for rasta-mouse/ThreatCheck with pipeline support, structured output, Wazuh SIEM integration, and HTML reporting.'
+ PowerShellVersion = '7.0'
+ FunctionsToExport = @('Invoke-ThreatCheck')
+ CmdletsToExport = @()
+ AliasesToExport = @()
+ PrivateData = @{
+ PSData = @{
+ Tags = @('Security', 'RedTeam', 'PurpleTeam', 'AMSI', 'Defender', 'ThreatCheck', 'HoneyBadgerVanguard', 'Wazuh', 'SIEM')
+ ProjectUri = 'https://github.com/MoSLoF/ThreatCheck'
+ LicenseUri = 'https://github.com/MoSLoF/ThreatCheck/blob/master/LICENSE'
+ ReleaseNotes = '1.0.0 - Initial release. Pipeline support, structured output, Wazuh NDJSON logging, HTML reporting.'
+ }
+ }
+}
diff --git a/PowerShell/Invoke-ThreatCheck.psm1 b/PowerShell/Invoke-ThreatCheck.psm1
new file mode 100644
index 0000000..b8073d2
--- /dev/null
+++ b/PowerShell/Invoke-ThreatCheck.psm1
@@ -0,0 +1,496 @@
+#Requires -Version 7.0
+<#
+.SYNOPSIS
+ PowerShell wrapper module for ThreatCheck.exe with pipeline support,
+ structured output, Wazuh SIEM integration, and HTML reporting.
+
+.DESCRIPTION
+ Invoke-ThreatCheck wraps rasta-mouse's ThreatCheck.exe to provide:
+ - Pipeline-friendly input (ValueFromPipeline / ValueFromPipelineByPropertyName)
+ - Structured [PSCustomObject] output per scan result
+ - Glob/wildcard expansion for bulk scanning
+ - Wazuh NDJSON event logging with MITRE ATT&CK tagging
+ - Self-contained HTML threat report
+
+ ThreatCheck.exe must be compiled and accessible. The module will attempt
+ to auto-discover ThreatCheck.exe in the following order:
+ 1. -ThreatCheckPath parameter (explicit)
+ 2. Same directory as this .psm1 file
+ 3. Parent directory of this .psm1 file
+ 4. $env:PATH
+
+ NOTE: Defender scans require ThreatCheck.exe to write to C:\Temp and
+ invoke MpCmdRun.exe. Run as Administrator for Defender engine scans.
+
+.NOTES
+ Author : HoneyBadger Vanguard LLC (github.com/MoSLoF)
+ Version : 1.0.0
+ Repo : github.com/MoSLoF/ThreatCheck
+ Upstream : github.com/rasta-mouse/ThreatCheck
+ MITRE : T1562.001 (Impair Defenses: Disable or Modify Tools)
+ T1027 (Obfuscated Files or Information)
+ License : MIT
+
+.EXAMPLE
+ # Scan a single binary with Defender
+ Invoke-ThreatCheck -Path C:\Tools\Rubeus.exe
+
+.EXAMPLE
+ # Scan a PowerShell script with AMSI engine
+ Invoke-ThreatCheck -Path C:\Tools\OffensiveTool.ps1 -Engine AMSI -Type Script
+
+.EXAMPLE
+ # Pipeline: scan every .ps1 in a folder, AMSI engine
+ Get-ChildItem C:\Tools\*.ps1 | Invoke-ThreatCheck -Engine AMSI -Type Script
+
+.EXAMPLE
+ # Bulk scan binaries, show only flagged results
+ Get-ChildItem C:\Tools\*.exe | Invoke-ThreatCheck | Where-Object { -not $_.Clean }
+
+.EXAMPLE
+ # Full run: scan, log to Wazuh, generate HTML report
+ Get-ChildItem C:\Tools\*.exe |
+ Invoke-ThreatCheck -WazuhLogFile C:\Logs\threatcheck.json -ReportFile C:\Reports\tc.html
+
+.EXAMPLE
+ # Scan from URL
+ Invoke-ThreatCheck -Url 'https://example.com/payload.bin' -Engine Defender
+
+.LINK
+ https://github.com/rasta-mouse/ThreatCheck
+ https://github.com/MoSLoF/ThreatCheck
+#>
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+# Module-level accumulator — initialized once at load time, reset per invocation
+$script:_tcResultStore = [System.Collections.Generic.List[object]]::new()
+
+#region ── Internal Helpers ────────────────────────────────────────────────────
+
+function Resolve-ThreatCheckExe {
+ [OutputType([string])]
+ param([string]$ExplicitPath)
+
+ if ($ExplicitPath) {
+ if (Test-Path $ExplicitPath -PathType Leaf) { return $ExplicitPath }
+ throw "ThreatCheck.exe not found at specified path: $ExplicitPath"
+ }
+
+ $moduleDir = Split-Path -Parent $PSCommandPath
+ $candidate = Join-Path $moduleDir 'ThreatCheck.exe'
+ if (Test-Path $candidate) { return $candidate }
+
+ $parentDir = Split-Path -Parent $moduleDir
+ foreach ($rel in @('ThreatCheck\bin\Release\net48\ThreatCheck.exe','ThreatCheck\bin\Debug\net48\ThreatCheck.exe')) {
+ $candidate = Join-Path $parentDir $rel
+ if (Test-Path $candidate) { return $candidate }
+ }
+
+ $fromPath = Get-Command 'ThreatCheck.exe' -ErrorAction SilentlyContinue
+ if ($fromPath) { return $fromPath.Source }
+
+ throw "ThreatCheck.exe not found. Use -ThreatCheckPath to specify its location, or place it alongside Invoke-ThreatCheck.psm1."
+}
+
+function Invoke-ThreatCheckExe {
+ [OutputType([string[]])]
+ param(
+ [string]$ExePath,
+ [string[]]$Arguments
+ )
+
+ $psi = [System.Diagnostics.ProcessStartInfo]::new()
+ $psi.FileName = $ExePath
+ $psi.Arguments = $Arguments -join ' '
+ $psi.UseShellExecute = $false
+ $psi.RedirectStandardOutput = $true
+ $psi.RedirectStandardError = $true
+ $psi.RedirectStandardInput = $true
+ $psi.CreateNoWindow = $true
+
+ $proc = [System.Diagnostics.Process]::new()
+ $proc.StartInfo = $psi
+ $null = $proc.Start()
+
+ $stdout = $proc.StandardOutput.ReadToEnd()
+ $stderr = $proc.StandardError.ReadToEnd()
+ $null = $proc.WaitForExit(60000)
+
+ if (-not $proc.HasExited) {
+ $proc.Kill()
+ throw "ThreatCheck.exe timed out after 60 seconds"
+ }
+
+ # Filter blank lines and bare boolean lines ThreatCheck emits to stdout
+ $allOutput = [System.Collections.Generic.List[string]]::new()
+ foreach ($src in @($stdout, $stderr)) {
+ if ($src) {
+ foreach ($line in ($src -split "`r?`n")) {
+ $trimmed = $line.Trim()
+ if ($trimmed -ne '' -and $trimmed -ne 'True' -and $trimmed -ne 'False') {
+ $allOutput.Add($line)
+ }
+ }
+ }
+ }
+
+ return $allOutput.ToArray()
+}
+
+function Parse-ThreatCheckOutput {
+ [OutputType([hashtable])]
+ param(
+ [string[]]$Lines,
+ [string]$Target,
+ [string]$Engine,
+ [string]$Type
+ )
+
+ $result = @{
+ Clean = $true
+ FlaggedOffset = $null
+ Signature = $null
+ ErrorMessage = $null
+ HexDump = [System.Collections.Generic.List[string]]::new()
+ RawOutput = $Lines
+ }
+
+ foreach ($line in $Lines) {
+ if ($line -match '^\[!\].*offset\s+(0x[0-9A-Fa-f]+)') {
+ $result.Clean = $false
+ $result.FlaggedOffset = $Matches[1]
+ }
+ elseif ($line -match '^\[!\]') {
+ $result.Clean = $false
+ }
+ elseif ($line -match '^\[x\]') {
+ $result.ErrorMessage = $line -replace '^\[x\]\s*', ''
+ }
+ elseif ($line -match '^[0-9A-Fa-f]{8}\s') {
+ $result.HexDump.Add($line)
+ }
+ }
+
+ return $result
+}
+
+function Write-WazuhEvent {
+ param(
+ [string]$LogFile,
+ [PSCustomObject]$ScanResult
+ )
+
+ $event = [ordered]@{
+ timestamp = $ScanResult.Timestamp.ToString('o')
+ program_name = 'Invoke-ThreatCheck'
+ hbv_version = '1.0.0'
+ event_type = if ($ScanResult.Clean) { 'scan_clean' } else { 'scan_threat_found' }
+ target = $ScanResult.Target
+ engine = $ScanResult.Engine
+ file_type = $ScanResult.Type
+ clean = $ScanResult.Clean
+ flagged_offset = $ScanResult.FlaggedOffset
+ error_message = $ScanResult.ErrorMessage
+ scan_duration_ms = [math]::Round($ScanResult.ScanDuration.TotalMilliseconds, 2)
+ mitre_technique = 'T1562.001'
+ mitre_tactic = 'Defense Evasion'
+ mitre_technique2 = 'T1027'
+ mitre_tactic2 = 'Defense Evasion'
+ }
+
+ $json = $event | ConvertTo-Json -Compress
+ Add-Content -Path $LogFile -Value $json -Encoding UTF8
+}
+
+function New-HtmlReport {
+ param(
+ [PSCustomObject[]]$Results,
+ [string]$ReportFile
+ )
+
+ $ResultsArr = @($Results)
+ [int]$totalScans = $ResultsArr.Count
+ [int]$flaggedCount = @($ResultsArr | Where-Object { -not $_.Clean }).Count
+ [int]$cleanCount = @($ResultsArr | Where-Object { $_.Clean -and -not $_.ErrorMessage }).Count
+ [int]$errorCount = @($ResultsArr | Where-Object { $_.ErrorMessage }).Count
+ $generatedAt = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
+
+ $cards = foreach ($r in $Results) {
+ $statusColor = if (-not $r.Clean) { '#E94560' } elseif ($r.ErrorMessage) { '#FF9800' } else { '#00C853' }
+ $statusLabel = if (-not $r.Clean) { 'FLAGGED' } elseif ($r.ErrorMessage) { 'ERROR' } else { 'CLEAN' }
+ $offsetBadge = if ($r.FlaggedOffset) { "Offset: $($r.FlaggedOffset)" } else { '' }
+ $engineBadge = "$($r.Engine)"
+ $typeBadge = "$($r.Type)"
+ $durationMs = [math]::Round($r.ScanDuration.TotalMilliseconds)
+
+ $hexRows = if (@($r.HexDump).Count -gt 0) {
+ $hexContent = ($r.HexDump | ForEach-Object { [System.Web.HttpUtility]::HtmlEncode($_) }) -join "`n"
+ "
"
+ } else { '' }
+
+ @"
+
+
+
$([System.Web.HttpUtility]::HtmlEncode($r.Target))
+
$($r.Timestamp.ToString('HH:mm:ss.fff'))
+ $hexRows
+
+"@
+ }
+
+ $html = @"
+
+
+
+
+
+ThreatCheck Report — HoneyBadger Vanguard
+
+
+
+
+
+
+ 🛡 MITRE ATT&CK Coverage:
+ T1562.001 — Impair Defenses: Disable or Modify Tools
+ T1027 — Obfuscated Files or Information
+
+
+$($cards -join "`n")
+
+
+
+
+"@
+
+ Set-Content -Path $ReportFile -Value $html -Encoding UTF8
+}
+
+#endregion
+
+#region ── Public Function ─────────────────────────────────────────────────────
+
+function Invoke-ThreatCheck {
+<#
+.SYNOPSIS
+ Scans a file or URL with ThreatCheck.exe and returns structured results.
+
+.PARAMETER Path
+ Path(s) to file(s) on disk. Accepts pipeline input and wildcards.
+
+.PARAMETER Url
+ URL to download and scan. Mutually exclusive with -Path.
+
+.PARAMETER Engine
+ Scanning engine: Defender (default) or AMSI.
+
+.PARAMETER Type
+ File type hint: Bin (default) or Script.
+
+.PARAMETER ThreatCheckPath
+ Explicit path to ThreatCheck.exe.
+
+.PARAMETER WazuhLogFile
+ Path to append NDJSON events for Wazuh ingestion.
+
+.PARAMETER ReportFile
+ Path to write a self-contained HTML threat report.
+
+.PARAMETER Quiet
+ Suppress all console output.
+#>
+ [CmdletBinding(DefaultParameterSetName = 'File')]
+ [OutputType([PSCustomObject])]
+ param(
+ [Parameter(ParameterSetName='File', Mandatory=$true,
+ ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
+ [Alias('FullName','FilePath','PSPath')]
+ [string[]]$Path,
+
+ [Parameter(ParameterSetName='Url', Mandatory=$true)]
+ [string]$Url,
+
+ [Parameter()]
+ [ValidateSet('Defender','AMSI', IgnoreCase=$true)]
+ [string]$Engine = 'Defender',
+
+ [Parameter()]
+ [ValidateSet('Bin','Script', IgnoreCase=$true)]
+ [string]$Type = 'Bin',
+
+ [Parameter()] [string]$ThreatCheckPath,
+ [Parameter()] [string]$WazuhLogFile,
+ [Parameter()] [string]$ReportFile,
+ [Parameter()] [switch]$Quiet
+ )
+
+ begin {
+ try {
+ $script:tcExe = Resolve-ThreatCheckExe -ExplicitPath $ThreatCheckPath
+ } catch { throw $_ }
+
+ if (-not $Quiet) {
+ Write-Host "[*] ThreatCheck.exe : $script:tcExe" -ForegroundColor Cyan
+ Write-Host "[*] Engine : $Engine" -ForegroundColor Cyan
+ Write-Host "[*] Type : $Type" -ForegroundColor Cyan
+ if ($WazuhLogFile) { Write-Host "[*] Wazuh log : $WazuhLogFile" -ForegroundColor Cyan }
+ if ($ReportFile) { Write-Host "[*] HTML report : $ReportFile" -ForegroundColor Cyan }
+ }
+
+ $script:_tcResultStore = [System.Collections.Generic.List[object]]::new()
+ }
+
+ process {
+ $targets = [System.Collections.Generic.List[string]]::new()
+
+ if ($PSCmdlet.ParameterSetName -eq 'Url') {
+ $targets.Add($Url)
+ } else {
+ foreach ($p in $Path) {
+ $resolved = Resolve-Path -Path $p -ErrorAction SilentlyContinue
+ if ($resolved) { $targets.Add($resolved.ProviderPath) }
+ else { Write-Warning "Path not found: $p" }
+ }
+ }
+
+ foreach ($target in $targets) {
+ $isUrl = $PSCmdlet.ParameterSetName -eq 'Url'
+ $tcArgs = @(
+ if ($isUrl) { '-u', "`"$target`"" } else { '-f', "`"$target`"" }
+ '-e', $Engine
+ '-t', $Type
+ )
+
+ if (-not $Quiet) { Write-Host "`n[>] Scanning: $target" -ForegroundColor Yellow }
+
+ $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
+ try { $rawLines = Invoke-ThreatCheckExe -ExePath $script:tcExe -Arguments $tcArgs }
+ catch { $rawLines = @("[x] Execution error: $_") }
+ $stopwatch.Stop()
+
+ $parsed = Parse-ThreatCheckOutput -Lines $rawLines -Target $target -Engine $Engine -Type $Type
+
+ if (-not $Quiet) {
+ foreach ($line in $rawLines) {
+ switch -Regex ($line) {
+ '^\[\+\]' { Write-Host $line -ForegroundColor Green }
+ '^\[!\]' { Write-Host $line -ForegroundColor Red }
+ '^\[\*\]' { Write-Host $line -ForegroundColor Yellow }
+ '^\[x\]' { Write-Host $line -ForegroundColor Red }
+ '^[0-9A-Fa-f]{8}\s' { Write-Host $line -ForegroundColor DarkRed }
+ default { Write-Host $line }
+ }
+ }
+ }
+
+ $result = [PSCustomObject][ordered]@{
+ PSTypeName = 'HBV.ThreatCheck.ScanResult'
+ Timestamp = [datetime]::Now
+ Target = $target
+ Engine = $Engine
+ Type = $Type
+ Clean = $parsed.Clean
+ FlaggedOffset = $parsed.FlaggedOffset
+ ErrorMessage = $parsed.ErrorMessage
+ HexDump = $parsed.HexDump.ToArray()
+ RawOutput = $parsed.RawOutput
+ ScanDuration = $stopwatch.Elapsed
+ }
+
+ if ($WazuhLogFile) {
+ try { Write-WazuhEvent -LogFile $WazuhLogFile -ScanResult $result }
+ catch { Write-Warning "Wazuh log write failed: $_" }
+ }
+
+ if (-not $Quiet) {
+ if ($result.Clean -and -not $result.ErrorMessage) {
+ Write-Host "[+] CLEAN — $([System.IO.Path]::GetFileName($target))" -ForegroundColor Green
+ } elseif ($result.ErrorMessage) {
+ Write-Host "[!] ERROR — $([System.IO.Path]::GetFileName($target)): $($result.ErrorMessage)" -ForegroundColor Yellow
+ } else {
+ Write-Host "[!] FLAGGED — $([System.IO.Path]::GetFileName($target)) at offset $($result.FlaggedOffset)" -ForegroundColor Red
+ }
+ }
+
+ $script:_tcResultStore.Add($result)
+ Write-Output $result
+ }
+ }
+
+ end {
+ [int]$resultCount = $script:_tcResultStore.Count
+
+ if ($ReportFile -and $resultCount -gt 0) {
+ try {
+ Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue
+ New-HtmlReport -Results $script:_tcResultStore.ToArray() -ReportFile $ReportFile
+ if (-not $Quiet) { Write-Host "`n[+] HTML report written: $ReportFile" -ForegroundColor Cyan }
+ } catch { Write-Warning "HTML report generation failed: $_" }
+ }
+
+ if (-not $Quiet -and $resultCount -gt 0) {
+ [int]$flagged = @($script:_tcResultStore | Where-Object { -not $_.Clean }).Count
+ [int]$errors = @($script:_tcResultStore | Where-Object { $_.ErrorMessage }).Count
+ Write-Host "`n[=] Scan complete: $resultCount scanned | " -NoNewline -ForegroundColor Cyan
+ Write-Host "$flagged flagged" -NoNewline -ForegroundColor $(if ($flagged -gt 0) {'Red'} else {'Green'})
+ Write-Host " | $errors errors" -ForegroundColor $(if ($errors -gt 0) {'Yellow'} else {'Green'})
+ }
+ }
+}
+
+#endregion
+
+Export-ModuleMember -Function Invoke-ThreatCheck
diff --git a/ThreatCheck/Scanners/AmsiScanner.cs b/ThreatCheck/Scanners/AmsiScanner.cs
index 893c5a7..6d6f978 100644
--- a/ThreatCheck/Scanners/AmsiScanner.cs
+++ b/ThreatCheck/Scanners/AmsiScanner.cs
@@ -25,7 +25,6 @@ public void AnalyzeBytes(byte[] bytes)
_fileBytes = bytes;
var status = ScanBuffer(_fileBytes);
- Console.WriteLine($"status value: {status}");
if (status is not AmsiResult.AmsiResultDetected)
{