Public/Export-OffsetThreatReport.ps1

function Export-OffsetThreatReport {
    <#
    .SYNOPSIS
        Renders one or more threat-scan results into a report-ready Markdown or HTML file.

    .DESCRIPTION
        Consumes the OffsetInspect.ThreatScanResult objects produced by
        Invoke-OffsetThreatScan -PassThru (a single result or a pipeline of many) and writes
        a self-contained detection-boundary report: per-file summary, provider/signature/engine
        metadata for reproducibility, the full ProbeLog audit trail, and any warnings. HTML
        output is self-contained and HTML-encodes every value. The command reads results only
        and never re-scans, so it is fully cross-platform.

    .PARAMETER Result
        One or more threat-scan result objects, typically piped from Invoke-OffsetThreatScan -PassThru.

    .PARAMETER Path
        Destination file path. Written as BOM-less UTF-8.

    .PARAMETER Format
        Markdown (default) or Html.

    .PARAMETER Title
        Report heading.

    .EXAMPLE
        Invoke-OffsetThreatScan .\sample.ps1 -Engine AMSI -PassThru |
            Export-OffsetThreatReport -Path .\report.html -Format Html

    .EXAMPLE
        $results | Export-OffsetThreatReport -Path .\engagement.md
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [pscustomobject[]]$Result,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [ValidateSet('Markdown', 'Html')]
        [string]$Format = 'Markdown',

        [ValidateNotNullOrEmpty()]
        [string]$Title = 'OffsetInspect Detection-Boundary Report',

        [switch]$IncludeIoc
    )

    begin {
        $collected = New-Object 'System.Collections.Generic.List[object]'
    }

    process {
        foreach ($item in $Result) {
            if ($null -ne $item) { $collected.Add($item) }
        }
    }

    end {
        if ($collected.Count -eq 0) {
            throw 'No threat-scan results were supplied to Export-OffsetThreatReport.'
        }

        $records = $collected.ToArray()

        if ($IncludeIoc) {
            foreach ($record in $records) {
                $fileProperty = $record.PSObject.Properties['File']
                if ($null -ne $fileProperty -and -not [string]::IsNullOrWhiteSpace([string]$record.File) -and (Test-Path -LiteralPath $record.File)) {
                    try {
                        $ioc = Get-OffsetIOC -FilePath $record.File
                        Add-Member -InputObject $record -NotePropertyName 'IocPanel' -NotePropertyValue $ioc -Force
                    }
                    catch {
                        Write-Verbose "IOC enrichment failed for $($record.File): $($_.Exception.Message)"
                    }
                }
            }
        }

        $content = if ($Format -eq 'Html') {
            ConvertTo-OIThreatReportHtml -Result $records -Title $Title
        }
        else {
            ConvertTo-OIThreatReportMarkdown -Result $records -Title $Title
        }

        $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
        [System.IO.File]::WriteAllText($fullPath, $content, (New-Object System.Text.UTF8Encoding($false)))
        Write-Verbose "Threat report ($Format, $($records.Count) record(s)) written to $fullPath"
        return $fullPath
    }
}