Public/Save-CIEMScanResult.ps1

function Save-CIEMScanResult {
    <#
    .SYNOPSIS
        Saves CIEM scan results to persistent storage.
    .DESCRIPTION
        Persists scan results to PSU cache for retrieval by the Dashboard and Findings pages.
        Maintains scan history (last 10 scans) and stores both current and historical results.
    .PARAMETER Results
        Array of scan result objects returned from Invoke-CIEMScan.
    .PARAMETER Provider
        The cloud provider that was scanned (e.g., 'Azure').
    .PARAMETER Services
        Array of services that were scanned (e.g., @('Entra', 'IAM')).
    .PARAMETER Duration
        Duration string of the scan (e.g., '45s' or '2m 30s').
    .PARAMETER IncludePassed
        Whether passed checks were included in the scan results.
    .PARAMETER Timestamp
        The timestamp when the scan completed. Defaults to current time.
    .EXAMPLE
        $results = Invoke-CIEMScan -Provider Azure -Service Entra, IAM
        Save-CIEMScanResult -Results $results -Provider Azure -Services @('Entra', 'IAM') -Duration '45s'
    .OUTPUTS
        PSCustomObject with ScanId and summary statistics.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [AllowEmptyCollection()]
        [object[]]$Results,

        [Parameter(Mandatory)]
        [ValidateSet('Azure', 'AWS')]
        [string]$Provider,

        [Parameter(Mandatory)]
        [string[]]$Services,

        [Parameter(Mandatory)]
        [string]$Duration,

        [Parameter()]
        [bool]$IncludePassed = $false,

        [Parameter()]
        [datetime]$Timestamp = (Get-Date)
    )

    begin {
        $allResults = [System.Collections.ArrayList]::new()
    }

    process {
        foreach ($result in $Results) {
            [void]$allResults.Add($result)
        }
    }

    end {
        # Generate scan ID
        $scanId = [guid]::NewGuid().ToString()

        # Categorize results by status
        $failedResults = @($allResults | Where-Object { $_.Status -eq 'FAIL' })
        $passedResults = @($allResults | Where-Object { $_.Status -eq 'PASS' })
        $skippedResults = @($allResults | Where-Object { $_.Status -eq 'SKIPPED' })
        $manualResults = @($allResults | Where-Object { $_.Status -eq 'MANUAL' })

        # Create scan record for history
        $scanRecord = @{
            Id              = $scanId
            Date            = $Timestamp.ToString('o')
            Provider        = $Provider
            Services        = $Services
            TotalResults    = $allResults.Count
            FailedResults   = $failedResults.Count
            PassedResults   = $passedResults.Count
            SkippedResults  = $skippedResults.Count
            ManualResults   = $manualResults.Count
            Duration        = $Duration
            IncludePassed   = $IncludePassed
        }

        # Check if PSU cache is available
        $psuCacheAvailable = Get-Command -Name 'Set-PSUCache' -ErrorAction SilentlyContinue

        if ($psuCacheAvailable) {
            try {
                # Update scan history (keep last 10)
                $scanHistoryKey = 'CIEM:ScanHistory'
                $existingHistory = Get-PSUCache -Key $scanHistoryKey -ErrorAction SilentlyContinue
                if (-not $existingHistory) { $existingHistory = @() }

                $existingHistory = @($scanRecord) + @($existingHistory) | Select-Object -First 10
                Set-PSUCache -Key $scanHistoryKey -Value $existingHistory -Persist -ErrorAction Stop

                # Store results by scan ID (for historical viewing)
                $resultsKey = "CIEM:ScanResults:$scanId"
                Set-PSUCache -Key $resultsKey -Value @($allResults) -Persist -ErrorAction Stop

                # Store as current results (for Dashboard/Findings pages)
                Set-PSUCache -Key 'CIEM:CurrentScanResults' -Value @{
                    ScanId        = $scanId
                    Timestamp     = $Timestamp.ToString('o')
                    Results       = @($allResults)
                    IncludePassed = $IncludePassed
                } -Persist -ErrorAction Stop

                Write-Verbose "Persisted $($allResults.Count) scan results to cache (ScanId: $scanId)"
            }
            catch {
                Write-Warning "Failed to save scan results to PSU cache: $($_.Exception.Message)"
            }
        }
        else {
            Write-Verbose "PSU cache not available - scan results not persisted"
        }

        # Return summary object
        [PSCustomObject]@{
            ScanId         = $scanId
            Timestamp      = $Timestamp
            Provider       = $Provider
            Services       = $Services
            TotalResults   = $allResults.Count
            FailedResults  = $failedResults.Count
            PassedResults  = $passedResults.Count
            SkippedResults = $skippedResults.Count
            ManualResults  = $manualResults.Count
            Duration       = $Duration
        }
    }
}