Private/Reporting/Write-ProcessingSummary.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Processing summary and result reporting module for Find-UnknownSID
 
.DESCRIPTION
    Provides comprehensive result formatting, summary generation, and export
    capabilities for Find-UnknownSID processing results. Focused solely on
    result presentation and data export functionality.
 
.NOTES
    Author: Jeffrey Stuhr
    Blog: https://www.techbyjeff.net
    LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    Last Updated: 2025-07-04
    Version: 2.0.0
 
    TROUBLESHOOTING:
    - For output issues: .\Troubleshooting\Common\Output-Issues.md
    - For CSV export problems: .\Troubleshooting\Common\File-Export-Issues.md
#>



function Write-ProcessingSummary {
    <#
    .SYNOPSIS
        Write comprehensive processing summary with statistics and results
 
    .DESCRIPTION
        Generates and displays a comprehensive summary of processing results
        including performance statistics, findings, and recommendations for
        enterprise reporting and audit requirements.
 
        BUSINESS VALUE:
        - Provides executive-level summary for decision making
        - Supports compliance reporting and audit requirements
        - Enables performance monitoring and optimization
        - Delivers actionable insights for security teams
        - Facilitates automation integration and monitoring
 
        REPORTING FEATURES:
        - Multi-format output for different audiences
        - Comprehensive performance metrics and statistics
        - Automated CSV export for downstream processing
        - Structured JSON output for automation integration
        - Color-coded console output for immediate assessment
 
    .PARAMETER ProcessingResults
        [Object] (Mandatory) Hashtable containing processing results and statistics.
        Must include required properties for different operation types.
 
        EXPECTED PROPERTIES:
        - TotalObjectsProcessed: Number of objects analyzed
        - ProcessingDuration: Time taken for processing
        - ProcessingErrors: Count of errors encountered
        - CorrelationId: Tracking identifier for audit
 
        OPERATION-SPECIFIC PROPERTIES:
        Discovery/Removal: OrphanedSIDsFound, RemovalResults, StreamingManager
        Restore: SuccessfulRestores, FailedRestores, RestoreResults
 
    .PARAMETER OutputPath
        [String] (Optional) Path for CSV export of results.
        Enables downstream processing and analysis.
 
        VALIDATION RULES:
        - Directory must exist or be creatable
        - Must have write permissions to specified location
        - File extension should be .csv for consistency
 
        BUSINESS CONTEXT:
        Supports integration with reporting systems, compliance
        documentation, and change management processes.
 
    .PARAMETER AutomationMode
        [Switch] (Optional) Whether to output structured data for automation.
        Enables integration with CI/CD pipelines and monitoring systems.
 
        AUTOMATION BENEFITS:
        - Structured JSON output for programmatic consumption
        - Standardized status codes for success/failure determination
        - Comprehensive metadata for downstream processing
 
    .PARAMETER CorrelationId
        [String] (Optional) Correlation ID for tracking this operation.
        Auto-generated if not provided for audit purposes.
 
    .OUTPUTS
        [Void] Writes summary to console and optionally exports to files.
        Side effects include console output and file creation if specified.
 
    .EXAMPLE
        PS> Write-ProcessingSummary -ProcessingResults $results -OutputPath ".\results.csv"
 
        DESCRIPTION: Write summary and export results to CSV
        OUTPUT: Console summary and CSV file export
        USE CASE: Standard result reporting and documentation
        COMPLIANCE: Supports audit trail requirements
 
    .EXAMPLE
        PS> Write-ProcessingSummary -ProcessingResults $results -AutomationMode
 
        DESCRIPTION: Generate structured output for automation
        OUTPUT: JSON-formatted automation data
        INTEGRATION: Enables CI/CD pipeline integration
        MONITORING: Supports automated alerting and dashboards
 
    .EXAMPLE
        PS> Write-ProcessingSummary -ProcessingResults $restoreResults
 
        DESCRIPTION: Display restore operation summary
        OUTPUT: Detailed restore operation results
        BUSINESS CASE: Change management and rollback reporting
        AUDIT: Supports compliance documentation requirements
 
    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
 
        SECURITY CONSIDERATIONS:
        - Results may contain sensitive information about AD structure
        - Export files should be protected with appropriate permissions
        - Correlation IDs enable security event tracking
        - Summary data supports security audit requirements
 
        PERFORMANCE CHARACTERISTICS:
        - Summary generation typically completes in <1 second
        - CSV export time varies with result count (1-10 seconds for large datasets)
        - Memory usage minimal for summary display
        - Streaming export for large result sets
 
        TROUBLESHOOTING:
        - For output issues: .\Troubleshooting\Common\Output-Issues.md
        - For CSV export problems: .\Troubleshooting\Common\File-Export-Issues.md
        - For automation integration: .\Troubleshooting\Integration\Automation-Issues.md
        - For performance optimization: .\Troubleshooting\Performance\Output-Performance.md
 
        KNOWN LIMITATIONS:
        - Large result sets may impact console display performance
        - CSV export requires sufficient disk space
        - Color output may not display correctly in all terminals
        - Automation mode requires JSON parsing capabilities in consuming systems
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object]$ProcessingResults,

        [Parameter()]
        [string]$OutputPath,

        [Parameter()]
        [switch]$AutomationMode,

        [Parameter()]
        [string]$CorrelationId = [System.Guid]::NewGuid().ToString()
    )

    begin {
        Write-StructuredLog "Generating processing summary" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
    }

    process {
        try {
            # Display processing summary
            Write-StructuredLog "Processing summary details:" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            Write-StructuredLog "=== PROCESSING SUMMARY ===" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            Write-StructuredLog "Total objects processed: $($ProcessingResults.TotalObjectsProcessed)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            Write-StructuredLog "Processing duration: $($ProcessingResults.ProcessingDuration.ToString('hh\:mm\:ss\.fff'))" -Level Information -Component 'Summary' -CorrelationId $CorrelationId

            # Handle different result types
            if ($ProcessingResults.PSObject.Properties.Name -contains 'ObjectsPerSecond') {
                Write-StructuredLog "Processing rate: $([Math]::Round($ProcessingResults.ObjectsPerSecond, 2)) objects/second" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            }

            if ($ProcessingResults.PSObject.Properties.Name -contains 'PeakMemoryUsageMB') {
                Write-StructuredLog "Peak memory usage: $($ProcessingResults.PeakMemoryUsageMB) MB" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            }

            # Check if this is a restore operation
            if ($ProcessingResults.PSObject.Properties.Name -contains 'SuccessfulRestores') {
                # Restore operation summary
                Write-StructuredLog "=== RESTORE SUMMARY ===" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Successful restores: $($ProcessingResults.SuccessfulRestores)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Failed restores: $($ProcessingResults.FailedRestores)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Backup source: $($ProcessingResults.BackupPath)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "WhatIf mode: $($ProcessingResults.WhatIfMode)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId

                # Show detailed restore results
                if ($ProcessingResults.RestoreResults -and $ProcessingResults.RestoreResults.Count -gt 0) {
                    Write-StructuredLog "=== RESTORE DETAILS ===" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                    foreach ($result in $ProcessingResults.RestoreResults) {
                        $status = if ($result.Success) { "SUCCESS" } else { "FAILED" }
                        $entriesInfo = if ($result.EntriesRestored) { " ($($result.EntriesRestored) entries)" } else { "" }
                        Write-StructuredLog " [$status] $($result.ObjectDN)$entriesInfo" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                        if (-not $result.Success -and $result.ErrorMessage) {
                            Write-StructuredLog " Error: $($result.ErrorMessage)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                        }
                    }
                }

                # Overall restore operation status
                if ($ProcessingResults.FailedRestores -eq 0) {
                    Write-StructuredLog "All restore operations completed successfully!" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                } else {
                    Write-StructuredLog "Some restore operations failed. Review the details above." -Level Warning -Component 'Summary' -CorrelationId $CorrelationId
                }
            } else {
                # Standard discovery/removal operation summary
                if ($ProcessingResults.PSObject.Properties.Name -contains 'ProcessingErrors') {
                    Write-StructuredLog "Processing errors: $($ProcessingResults.ProcessingErrors)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                }

                if ($ProcessingResults.PSObject.Properties.Name -contains 'OrphanedSIDsFound') {
                    Write-StructuredLog "Orphaned SIDs found: $($ProcessingResults.OrphanedSIDsFound)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                }
            }

            # Removal summary if applicable
            if ($ProcessingResults.RemovalResults -and $ProcessingResults.RemovalResults.Count -gt 0) {
                # @(...): on Windows PowerShell 5.1 a single object from Where-Object has no Count, so a run
                # with one removal logged an empty number
                $successfulRemovals = @($ProcessingResults.RemovalResults | Where-Object Success).Count
                $failedRemovals = @($ProcessingResults.RemovalResults | Where-Object { -not $_.Success }).Count

                Write-StructuredLog "Removal summary details:" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "=== REMOVAL SUMMARY ===" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Removal operations attempted: $($ProcessingResults.RemovalResults.Count)" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Successful removals: $successfulRemovals" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                Write-StructuredLog "Failed removals: $failedRemovals" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
            }

            # Export results to CSV if path provided
            # Check if we have streaming results or restore results
            $hasStreamingResults = $ProcessingResults.StreamingManager -and $ProcessingResults.StreamingManager.Summary.TotalResults -gt 0
            $hasRestoreResults = $ProcessingResults.RestoreResults -and $ProcessingResults.RestoreResults.Count -gt 0

            if ($hasStreamingResults -or $hasRestoreResults) {
                if ($OutputPath) {
                    try {
                        if ($hasStreamingResults) {
                            # Use StreamingManager's efficient CSV export
                            $ProcessingResults.StreamingManager.ExportToCsv($OutputPath)
                        } elseif ($hasRestoreResults) {
                            $ProcessingResults.RestoreResults | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
                        }
                        Write-StructuredLog "Results exported to: $OutputPath" -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                    }
                    catch {
                        Write-StructuredLog "Failed to export results to CSV: $($_.Exception.Message)" -Level Error -Component 'Summary' -CorrelationId $CorrelationId
                    }
                }
            } else {
                # Handle different operation types appropriately
                if ($ProcessingResults.PSObject.Properties.Name -contains 'SuccessfulRestores') {
                    # This is a restore operation with no results - shouldn't happen but handle gracefully
                    Write-StructuredLog "No restore operations were performed." -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                } else {
                    # This is a discovery/removal operation
                    if ($ProcessingResults.PSObject.Properties.Name -contains 'ProcessingErrors' -and $ProcessingResults.ProcessingErrors -gt 0) {
                        Write-StructuredLog "Consider reviewing the error log for detailed information." -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                    } else {
                        Write-StructuredLog "No orphaned SIDs found. Active Directory ACLs appear to be clean." -Level Information -Component 'Summary' -CorrelationId $CorrelationId
                    }
                }
            }

            # Automation output if requested
            if ($AutomationMode) {
                $automationOutput = [PSCustomObject]@{
                    CorrelationId = $CorrelationId
                    ProcessingResults = $ProcessingResults
                    Summary = if ($ProcessingResults.PSObject.Properties.Name -contains 'SuccessfulRestores') {
                        # Restore operation summary
                        @{
                            TotalObjectsProcessed = $ProcessingResults.TotalObjectsProcessed
                            SuccessfulRestores = $ProcessingResults.SuccessfulRestores
                            FailedRestores = $ProcessingResults.FailedRestores
                            Duration = $ProcessingResults.ProcessingDuration.ToString()
                            WhatIfMode = $ProcessingResults.WhatIfMode
                            BackupPath = $ProcessingResults.BackupPath
                        }
                    } else {
                        # Discovery/removal operation summary
                        $summaryData = @{
                            TotalObjectsProcessed = $ProcessingResults.TotalObjectsProcessed
                            Duration = $ProcessingResults.ProcessingDuration.ToString()
                        }

                        if ($ProcessingResults.PSObject.Properties.Name -contains 'OrphanedSIDsFound') {
                            $summaryData.OrphanedSIDsFound = $ProcessingResults.OrphanedSIDsFound
                        }

                        if ($ProcessingResults.PSObject.Properties.Name -contains 'ProcessingErrors') {
                            $summaryData.ProcessingErrors = $ProcessingResults.ProcessingErrors
                        }

                        if ($ProcessingResults.PSObject.Properties.Name -contains 'PeakMemoryUsageMB') {
                            $summaryData.PeakMemoryMB = $ProcessingResults.PeakMemoryUsageMB
                        }

                        $summaryData
                    }
                    Status = if ($ProcessingResults.PSObject.Properties.Name -contains 'SuccessfulRestores') {
                        if ($ProcessingResults.FailedRestores -eq 0) { "Success" } else { "Warning" }
                    } else {
                        if ($ProcessingResults.PSObject.Properties.Name -contains 'ProcessingErrors' -and $ProcessingResults.ProcessingErrors -eq 0) {
                            "Success"
                        } elseif ($ProcessingResults.PSObject.Properties.Name -contains 'ProcessingErrors') {
                            "Warning"
                        } else {
                            "Success"
                        }
                    }
                    Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                }

                Write-Information "##[section]Script Completion" -InformationAction Continue
                Write-Information ($automationOutput | ConvertTo-Json -Depth 3) -InformationAction Continue
            }

            Write-StructuredLog "Processing summary completed" -Level Information -Component 'Summary' -CorrelationId $CorrelationId

        }
        catch {
            Write-StructuredLog "Error generating processing summary: $($_.Exception.Message)" -Level Error -Component 'Summary' -CorrelationId $CorrelationId
            throw
        }
    }
}