Public/Export-ReportPdf.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Export-ReportPdf {
    <#
    .SYNOPSIS
        Converts an HTML report to PDF via Edge or Chrome headless mode.
    .DESCRIPTION
        Takes an existing HTML report file and converts it to PDF using Microsoft Edge
        or Google Chrome in headless mode. Falls back gracefully with an error message
        if neither browser is available.
 
        Detection order: Edge → Chrome → Chromium.
    .PARAMETER HtmlPath
        Path to the HTML file to convert.
    .PARAMETER OutputPath
        Path for the PDF output. Default: same as input with .pdf extension.
    .PARAMETER BrowserPath
        Override path to the browser executable.
    .EXAMPLE
        Export-ReportPdf -HtmlPath ./PSGuerrilla-Executive-Summary.html
    .EXAMPLE
        Export-ExecutiveSummary -OutputPath ./summary.html; Export-ReportPdf -HtmlPath ./summary.html -OutputPath ./summary.pdf
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$HtmlPath,

        [string]$OutputPath,
        [string]$BrowserPath
    )

    if (-not (Test-Path $HtmlPath)) {
        Write-Warning "HTML file not found: $HtmlPath"
        return [PSCustomObject]@{ Success = $false; Message = "File not found: $HtmlPath"; Path = $null }
    }

    $resolvedHtml = (Resolve-Path $HtmlPath).Path
    if (-not $OutputPath) {
        $OutputPath = [System.IO.Path]::ChangeExtension($resolvedHtml, '.pdf')
    }

    # Find browser
    if (-not $BrowserPath) {
        $candidates = @(
            # Microsoft Edge
            "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe"
            "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe"
            "$env:LOCALAPPDATA\Microsoft\Edge\Application\msedge.exe"
            # Google Chrome
            "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe"
            "$env:ProgramFiles\Google\Chrome\Application\chrome.exe"
            "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
            # Linux/macOS
            '/usr/bin/microsoft-edge'
            '/usr/bin/google-chrome'
            '/usr/bin/chromium-browser'
            '/usr/bin/chromium'
            '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
            '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
        )

        foreach ($candidate in $candidates) {
            if (Test-Path $candidate) {
                $BrowserPath = $candidate
                break
            }
        }
    }

    if (-not $BrowserPath) {
        $msg = 'PDF export requires Microsoft Edge or Google Chrome. Install one and retry, or use the HTML report with print-friendly CSS (@media print).'
        Write-Warning $msg
        return [PSCustomObject]@{ Success = $false; Message = $msg; Path = $null }
    }

    Write-Verbose "Using browser: $BrowserPath"
    Write-Verbose "Converting: $resolvedHtml -> $OutputPath"

    $fileUri = "file:///$($resolvedHtml -replace '\\', '/')"

    $arguments = @(
        '--headless'
        "--print-to-pdf=$OutputPath"
        '--no-margins'
        '--disable-gpu'
        '--no-sandbox'
        '--disable-extensions'
        '--disable-dev-shm-usage'
        $fileUri
    )

    try {
        $process = Start-Process -FilePath $BrowserPath -ArgumentList $arguments -PassThru -NoNewWindow -Wait -ErrorAction Stop

        # Give it a moment to write the file (timeout = 30 seconds)
        $timeoutMs = 30000
        $elapsedMs = 0
        while (-not (Test-Path $OutputPath) -and $elapsedMs -lt $timeoutMs) {
            Start-Sleep -Milliseconds 500
            $elapsedMs += 500
        }

        if (Test-Path $OutputPath) {
            $fileSize = (Get-Item $OutputPath).Length
            return [PSCustomObject]@{
                PSTypeName = 'PSGuerrilla.PdfExport'
                Success    = $true
                Path       = (Resolve-Path $OutputPath).Path
                Message    = "PDF exported to $OutputPath ($([Math]::Round($fileSize / 1KB, 1)) KB)"
                FileSize   = $fileSize
                Browser    = [System.IO.Path]::GetFileName($BrowserPath)
            }
        } else {
            return [PSCustomObject]@{
                PSTypeName = 'PSGuerrilla.PdfExport'
                Success    = $false
                Message    = "PDF generation completed but output file not found at $OutputPath"
                Path       = $null
            }
        }
    } catch {
        return [PSCustomObject]@{
            PSTypeName = 'PSGuerrilla.PdfExport'
            Success    = $false
            Message    = "PDF export failed: $_"
            Path       = $null
        }
    }
}