Private/Reporting/Export-DiagnosticData.ps1

function Export-DiagnosticData {
    <#
    .SYNOPSIS
        Exports comprehensive diagnostic information for troubleshooting and support analysis
 
    .DESCRIPTION
        Collects and exports detailed diagnostic information including system state,
        log files, configuration, and runtime metrics. Creates a timestamped diagnostic
        package containing sanitized data suitable for support analysis and troubleshooting.
 
        This function provides enterprise-grade diagnostic capabilities with:
        - Comprehensive system information collection
        - Memory usage and performance metrics
        - Log file analysis and inclusion
        - Sanitized configuration export
        - Structured diagnostic package creation
 
    .PARAMETER OutputPath
        Directory path where diagnostic data will be exported. The function will create
        a timestamped subdirectory within this path for the diagnostic package.
 
    .PARAMETER CorrelationId
        Correlation ID for tracking the diagnostic export operation across logs and
        audit trails. Auto-generated if not provided.
 
    .EXAMPLE
        PS> Export-DiagnosticData -OutputPath "C:\Diagnostics"
 
        DESCRIPTION: Exports diagnostic data with auto-generated correlation ID
        OUTPUT: Returns path to created diagnostic package
        USE CASE: Standard diagnostic export for troubleshooting
 
    .EXAMPLE
        PS> $diagnosticPath = Export-DiagnosticData -OutputPath "C:\Support" -CorrelationId "incident-12345"
        PS> Write-Output "Diagnostic package created at: $diagnosticPath"
 
        DESCRIPTION: Exports diagnostic data with specific correlation ID for incident tracking
        OUTPUT: Diagnostic package path with correlation tracking
        USE CASE: Support incident analysis with audit trail
 
    .EXAMPLE
        PS> try {
        PS> $path = Export-DiagnosticData -OutputPath $env:TEMP
        PS> Compress-Archive -Path $path -DestinationPath "diagnostic-package.zip"
        PS> } catch {
        PS> Write-Error "Diagnostic export failed: $_"
        PS> }
 
        DESCRIPTION: Exports and compresses diagnostic data for distribution
        OUTPUT: Compressed diagnostic package
        USE CASE: Preparing diagnostic data for remote support analysis
 
    .OUTPUTS
        System.String
        Returns the full path to the created diagnostic package directory.
 
    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
 
        SECURITY: All exported data is sanitized to remove sensitive information
        PERFORMANCE: Optimized for minimal impact on running operations
        COMPLIANCE: Supports audit requirements with correlation tracking
 
        TROUBLESHOOTING:
        - For export failures: .\Troubleshooting\Diagnostics\Export-Issues.md
        - For large log files: .\Troubleshooting\Performance\Memory-Optimization.md
        - For permissions: .\Troubleshooting\Security\Access-Control.md
 
        DEPENDENCIES:
        - Requires Write-StructuredLogEntry function for logging
        - Requires Get-LogFileSummary function for log analysis
        - Requires access to script-level logging variables ($script:LogPath)
    #>


    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            if (-not (Test-Path $_ -IsValid)) {
                throw "OutputPath must be a valid directory path"
            }
            $true
        })]
        [string]$OutputPath,

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

    begin {
        Write-Verbose "Starting diagnostic data export - CorrelationId: $CorrelationId"

        # Validate output directory exists or can be created
        if (-not (Test-Path $OutputPath)) {
            try {
                New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
                Write-Verbose "Created output directory: $OutputPath"
            }
            catch {
                Write-Error "Failed to create output directory '$OutputPath': $($_.Exception.Message)"
                throw
            }
        }
    }

    process {
        try {
            # Create timestamped diagnostic package directory
            $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
            $diagnosticPath = Join-Path $OutputPath "Diagnostic-$timestamp"

            if ($PSCmdlet.ShouldProcess($diagnosticPath, "Create diagnostic package")) {
                New-Item -Path $diagnosticPath -ItemType Directory -Force | Out-Null
                Write-Verbose "Created diagnostic package directory: $diagnosticPath"

                # Collect comprehensive system information
                $systemInfo = @{
                    PSVersion = $PSVersionTable.PSVersion.ToString()
                    PSEdition = $PSVersionTable.PSEdition
                    OSVersion = [System.Environment]::OSVersion.ToString()
                    OSArchitecture = [System.Environment]::Is64BitOperatingSystem
                    ProcessArchitecture = [System.Environment]::Is64BitProcess
                    MachineName = $env:COMPUTERNAME
                    UserName = $env:USERNAME
                    Domain = $env:USERDOMAIN
                    ProcessId = [System.Diagnostics.Process]::GetCurrentProcess().Id
                    StartTime = [System.Diagnostics.Process]::GetCurrentProcess().StartTime
                    ThreadCount = [System.Diagnostics.Process]::GetCurrentProcess().Threads.Count
                    HandleCount = [System.Diagnostics.Process]::GetCurrentProcess().HandleCount
                    ModuleCount = [System.Diagnostics.Process]::GetCurrentProcess().Modules.Count
                }

                # Collect memory and performance metrics
                $currentProcess = Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)
                $memoryInfo = @{
                    WorkingSetMB = [Math]::Round($currentProcess.WorkingSet64 / 1MB, 2)
                    PrivateMemoryMB = [Math]::Round($currentProcess.PrivateMemorySize64 / 1MB, 2)
                    VirtualMemoryMB = [Math]::Round($currentProcess.VirtualMemorySize64 / 1MB, 2)
                    PagedMemoryMB = [Math]::Round($currentProcess.PagedMemorySize64 / 1MB, 2)
                    NonPagedMemoryMB = [Math]::Round($currentProcess.NonpagedSystemMemorySize64 / 1MB, 2)
                    PeakWorkingSetMB = [Math]::Round($currentProcess.PeakWorkingSet64 / 1MB, 2)
                    TotalProcessorTime = $currentProcess.TotalProcessorTime.ToString()
                    UserProcessorTime = $currentProcess.UserProcessorTime.ToString()
                }

                # Get log file analysis if available
                $logInfo = $null
                try {
                    if (Get-Command Get-LogFileSummary -ErrorAction SilentlyContinue) {
                        $logInfo = Get-LogFileSummary -ErrorAction SilentlyContinue
                    }
                }
                catch {
                    Write-Verbose "Could not retrieve log file summary: $($_.Exception.Message)"
                    $logInfo = @{ Error = "Log summary unavailable: $($_.Exception.Message)" }
                }

                # Compile comprehensive diagnostic data
                $diagnosticData = @{
                    Timestamp = Get-Date -Format 'yyyy-MM-ddTHH:mm:ss.fffffffK'
                    CorrelationId = $CorrelationId
                    SystemInfo = $systemInfo
                    MemoryUsage = $memoryInfo
                    LogInfo = $logInfo
                    EnvironmentVariables = @{
                        PATH = $env:PATH
                        PSModulePath = $env:PSModulePath
                        TEMP = $env:TEMP
                        UserProfile = $env:USERPROFILE
                        ComputerName = $env:COMPUTERNAME
                        LogonServer = $env:LOGONSERVER
                    }
                    PowerShellConfiguration = @{
                        ExecutionPolicy = Get-ExecutionPolicy
                        CurrentLocation = Get-Location
                        PSVersionTable = $PSVersionTable
                    }
                }

                # Export main diagnostic data
                $diagnosticData | ConvertTo-Json -Depth 10 -Compress:$false |
                    Out-File -FilePath "$diagnosticPath\diagnostic-data.json" -Encoding UTF8
                Write-Verbose "Exported diagnostic data to diagnostic-data.json"

                # Copy current log file if it exists and is accessible
                if ($script:LogPath -and (Test-Path $script:LogPath -ErrorAction SilentlyContinue)) {
                    try {
                        Copy-Item $script:LogPath "$diagnosticPath\current-log.log" -ErrorAction Stop
                        Write-Verbose "Copied current log file to diagnostic package"
                    }
                    catch {
                        Write-Verbose "Could not copy log file: $($_.Exception.Message)"
                        # Create a summary instead
                        "Log file copy failed: $($_.Exception.Message)" |
                            Out-File "$diagnosticPath\log-copy-error.txt" -Encoding UTF8
                    }
                }

                # Export PowerShell session information
                $PSVersionTable | ConvertTo-Json -Depth 5 |
                    Out-File -FilePath "$diagnosticPath\powershell-info.json" -Encoding UTF8
                Write-Verbose "Exported PowerShell session information"

                # Export loaded modules information
                try {
                    $modules = Get-Module
                    $loadedModules = foreach ($module in $modules) {
                        [PSCustomObject]@{
                            Name = $module.Name
                            Version = $module.Version.ToString()
                            ModuleType = $module.ModuleType.ToString()
                            Path = $module.Path
                        }
                    }
                } catch {
                    Write-Verbose "Could not retrieve module information: $($_.Exception.Message)"
                    $loadedModules = @()
                }
                $loadedModules | ConvertTo-Json -Depth 3 |
                    Out-File -FilePath "$diagnosticPath\loaded-modules.json" -Encoding UTF8
                Write-Verbose "Exported loaded modules information"

                # Create diagnostic package summary
                $packageSummary = @{
                    PackagePath = $diagnosticPath
                    CreatedAt = Get-Date -Format 'yyyy-MM-ddTHH:mm:ss.fffffffK'
                    CorrelationId = $CorrelationId
                    FilesIncluded = (Get-ChildItem $diagnosticPath -File).Name
                    TotalSizeMB = [Math]::Round((Get-ChildItem $diagnosticPath -Recurse -File |
                        Measure-Object -Property Length -Sum).Sum / 1MB, 2)
                }

                $packageSummary | ConvertTo-Json -Depth 3 |
                    Out-File -FilePath "$diagnosticPath\package-summary.json" -Encoding UTF8

                # Log successful export
                if (Get-Command Write-StructuredLogEntry -ErrorAction SilentlyContinue) {
                    Write-StructuredLogEntry -Level Information -Message "Diagnostic data exported successfully" -Component 'Diagnostics' -Details @{
                        ExportPath = $diagnosticPath
                        FilesExported = $packageSummary.FilesIncluded.Count
                        TotalSizeMB = $packageSummary.TotalSizeMB
                        CorrelationId = $CorrelationId
                    } -CorrelationId $CorrelationId
                }

                Write-Verbose "Diagnostic package created successfully: $diagnosticPath"
                return $diagnosticPath
            }
        }
        catch {
            # Log export failure
            if (Get-Command Write-StructuredLogEntry -ErrorAction SilentlyContinue) {
                Write-StructuredLogEntry -Level Error -Message "Failed to export diagnostic data" -Component 'Diagnostics' -Details @{ ErrorMessage = $_.Exception.Message } -CorrelationId $CorrelationId
            }

            Write-Error "Diagnostic export failed: $($_.Exception.Message)"
            throw
        }
    }

    end {
        Write-Verbose "Diagnostic data export operation completed - CorrelationId: $CorrelationId"
    }
}