Public/Export-InfraPulseReport.ps1

function Export-InfraPulseReport {
    <#
    .SYNOPSIS
        Exports InfraPulse reports to HTML, JSON, or CSV.

    .DESCRIPTION
        Collects InfraPulse.Report objects from the pipeline and writes a self-contained HTML dashboard, structured JSON array, or flattened CSV dataset.

    .PARAMETER InputObject
        One or more InfraPulse.Report objects.

    .PARAMETER Path
        Destination file path.

    .PARAMETER Format
        Html, Json, or Csv. When omitted, the format is inferred from the file extension and defaults to Html.

    .PARAMETER Title
        Title used in the HTML report.

    .PARAMETER Force
        Overwrites an existing file.

    .PARAMETER PassThru
        Returns the created file.

    .EXAMPLE
        Invoke-InfraPulse | Export-InfraPulseReport -Path .\out\infra-pulse.html -Force

    .EXAMPLE
        $reports | Export-InfraPulseReport -Path .\out\infra-pulse.json -Format Json -Force
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNull()]
        [object]$InputObject,

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

        [ValidateSet('Html', 'Json', 'Csv')]
        [string]$Format,

        [ValidateNotNullOrEmpty()]
        [string]$Title = 'Infrastructure Health Report',

        [switch]$Force,

        [switch]$PassThru
    )

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

    process {
        if ($null -eq $InputObject.PSObject.Properties['Results'] -or $null -eq $InputObject.PSObject.Properties['OverallStatus']) {
            throw 'InputObject is not an InfraPulse report.'
        }
        [void]$reports.Add($InputObject)
    }

    end {
        if ($reports.Count -eq 0) {
            throw 'No InfraPulse reports were supplied.'
        }

        $reportArray = @($reports.ToArray())
        $resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
        if (-not $PSBoundParameters.ContainsKey('Format')) {
            switch ([System.IO.Path]::GetExtension($resolvedPath).ToLowerInvariant()) {
                '.json' { $Format = 'Json' }
                '.csv' { $Format = 'Csv' }
                default { $Format = 'Html' }
            }
        }

        if ((Test-Path -LiteralPath $resolvedPath) -and -not $Force) {
            throw "File already exists: '$resolvedPath'. Use -Force to overwrite it."
        }

        $parent = Split-Path -Path $resolvedPath -Parent
        if (-not (Test-Path -LiteralPath $parent)) {
            $null = New-Item -Path $parent -ItemType Directory -Force
        }

        if (-not $PSCmdlet.ShouldProcess($resolvedPath, "Export InfraPulse $Format report")) {
            return
        }

        switch ($Format) {
            'Html' {
                $content = ConvertTo-InfraPulseHtmlReport -Reports $reportArray -Title $Title
            }
            'Json' {
                $content = ConvertTo-Json -InputObject (ConvertTo-InfraPulseSerializableValue -Value $reportArray) -Depth 12
            }
            'Csv' {
                $rows = foreach ($report in $reportArray) {
                    foreach ($result in @($report.Results)) {
                        [pscustomobject][ordered]@{
                            GeneratedAtUtc        = if ($report.GeneratedAtUtc) { ([datetime]$report.GeneratedAtUtc).ToUniversalTime().ToString('o') } else { '' }
                            ComputerName          = [string]$report.ComputerName
                            OverallStatus         = [string]$report.OverallStatus
                            CheckName             = [string]$result.CheckName
                            Category              = [string]$result.Category
                            Target                = [string]$result.Target
                            Status                = [string]$result.Status
                            Message               = [string]$result.Message
                            ObservedValue         = [string]$result.ObservedValue
                            WarningThreshold      = [string]$result.WarningThreshold
                            CriticalThreshold     = [string]$result.CriticalThreshold
                            Recommendation        = [string]$result.Recommendation
                            CheckDurationMs       = [double]$result.DurationMs
                            Error                 = [string]$result.Error
                            EvidenceJson          = ConvertTo-Json -InputObject (ConvertTo-InfraPulseSerializableValue -Value $result.Evidence) -Depth 8 -Compress
                        }
                    }
                }
                $content = @($rows | ConvertTo-Csv -NoTypeInformation) -join [Environment]::NewLine
            }
        }

        Write-InfraPulseUtf8File -Path $resolvedPath -Content $content
        if ($PassThru) {
            return Get-Item -LiteralPath $resolvedPath
        }
    }
}