Private/Logging/Write-SecurityStructuredLogEntry.ps1

function Write-SecurityStructuredLogEntry {
    <#
    .SYNOPSIS
        Writes security log entries directly to file with mandatory audit trail
 
    .DESCRIPTION
        Specialized logging function for security and audit events that bypasses
        normal log level filtering to ensure all security events are captured
        for compliance purposes. Security logs are ALWAYS written to file
        regardless of the general log level configuration.
 
    .PARAMETER Message
        The security log message content
 
    .PARAMETER Level
        Logging level (Information, Warning, Error) - Debug/Verbose not allowed for security
 
    .PARAMETER Component
        Component generating the security log entry
 
    .PARAMETER ErrorRecord
        PowerShell ErrorRecord object for security error logging
 
    .PARAMETER Data
        Additional structured data for security context
 
    .PARAMETER CorrelationId
        Correlation ID for tracking operations
 
    .EXAMPLE
        PS> Write-SecurityStructuredLogEntry -Message "AD object access attempt" -Level Information -Component "SecurityAudit"
 
        Writes security log entry directly to file, bypassing log level filtering
 
    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
 
        COMPLIANCE: Security logs are written regardless of log level to ensure audit trail
        PERFORMANCE: Direct file write, minimal validation for security critical path
 
        TROUBLESHOOTING:
        - For security logging issues: .\Troubleshooting\Security\Security-Logging-Issues.md
        - For compliance requirements: .\Troubleshooting\Security\Compliance-Guide.md
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Message,

        [Parameter()]
        [ValidateSet('Critical', 'Error', 'Warning', 'Information')]  # No Debug/Verbose for security
        [string]$Level = 'Information',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Component = 'SecurityAudit',

        [Parameter()]
        [System.Management.Automation.ErrorRecord]$ErrorRecord,

        [Parameter()]
        [hashtable]$Data = @{},

        [Parameter()]
        [string]$CorrelationId
    )

    begin {
        Write-Verbose "Writing security structured log entry - Level: $Level, Component: $Component"

        # Get current logging system state
        $loggingState = Get-LoggingSystemState
        if (-not $loggingState.CorrelationId) {
            Write-Warning "Logging system not properly initialized for security logging"
        }
    }

    process {
        try {
            # Determine effective correlation ID
            $effectiveCorrelationId = if ($CorrelationId) {
                $CorrelationId
            } elseif ($loggingState.CorrelationId) {
                $loggingState.CorrelationId
            } else {
                [System.Guid]::NewGuid().ToString()
            }

            # Format the security log entry
            $additionalData = $Data
            if ($ErrorRecord) {
                $additionalData['ErrorRecord'] = @{
                    Exception = $ErrorRecord.Exception.Message
                    ScriptStackTrace = $ErrorRecord.ScriptStackTrace
                    FullyQualifiedErrorId = $ErrorRecord.FullyQualifiedErrorId
                }
            }
            
            $formattedEntry = Format-LogMessage -Message $Message -Level $Level -Component $Component -CorrelationId $effectiveCorrelationId -AdditionalData $additionalData

            # ALWAYS write to console for security events (with security prefix)
            Write-SecurityConsoleEntry -FormattedMessage $formattedEntry -Level $Level

            # ALWAYS write to file for security events - bypass log level filtering
            Write-SecurityFileEntry -FormattedMessage $formattedEntry

            # Update security metrics
            Update-SecurityLoggingMetrics -Level $Level
        }
        catch {
            # Security logging failures are critical - try multiple fallback mechanisms
            Write-Warning "CRITICAL: Security log entry failed: $($_.Exception.Message)"

            # Fallback 1: Try direct file write without formatting
            try {
                $fallbackEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') [$Component] [$Level] SECURITY: $Message (CorrelationId: $effectiveCorrelationId)"
                Write-SecurityFileEntryDirect -Message $fallbackEntry
            }
            catch {
                # Fallback 2: Write to temp directory as last resort
                try {
                    $emergencyEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') [SECURITY-EMERGENCY] [$Level] $Message (CorrelationId: $effectiveCorrelationId)"
                    $emergencyEntry | Out-File -FilePath "$env:TEMP\OrphanedSid-Security-Emergency.log" -Append -Encoding UTF8 -ErrorAction Stop -WhatIf:$false
                    Write-Warning "Security log written to emergency file: $env:TEMP\OrphanedSid-Security-Emergency.log"
                }
                catch {
                    Write-Error "CRITICAL SECURITY LOGGING FAILURE: Unable to write security audit entry: $Message"
                }
            }
        }
    }
}


function Write-SecurityConsoleEntry {
    <#
    .SYNOPSIS
        Writes security log entry to console with security indicators
 
    .DESCRIPTION
        Specialized console output for security logs with clear security marking
        and appropriate output streams for security event visibility.
        Security logs are only displayed to console for Critical/Error levels
        or when specifically enabled. Information level security logs are
        written to file only to reduce console noise.
 
    .PARAMETER FormattedMessage
        Pre-formatted security log message
 
    .PARAMETER Level
        Security log level
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '',
        Justification = 'Critical security alerts are coloured on purpose; Write-Information cannot colour')]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FormattedMessage,

        [Parameter(Mandatory)]
        [ValidateSet('Critical', 'Error', 'Warning', 'Information')]
        [string]$Level
    )

    # Check if console output is suppressed
    $loggingState = Get-LoggingSystemState
    if ($loggingState.SuppressConsoleOutput) {
        return
    }

    # Only show Critical and Error security events in console to reduce noise
    # Information level security events are logged to file only
    if ($Level -eq 'Information') {
        return
    }

    # Security logs for critical issues should be visible in console with security prefix
    $securityMessage = "[SECURITY] $FormattedMessage"

    # Use appropriate output method based on level
    switch ($Level) {
        'Critical' {
            Write-Host $securityMessage -ForegroundColor Red -BackgroundColor Yellow
        }
        'Error' {
            Write-Error $securityMessage -ErrorAction Continue
        }
        'Warning' {
            Write-Warning $securityMessage
        }
    }
}


function Write-SecurityFileEntry {
    <#
    .SYNOPSIS
        Writes security log entry directly to file, bypassing log level filtering
 
    .DESCRIPTION
        Ensures security log entries are written to the log file regardless
        of the general log level configuration for compliance requirements.
 
    .PARAMETER FormattedMessage
        Pre-formatted security log message
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FormattedMessage
    )

    $loggingState = Get-LoggingSystemState

    if (-not $loggingState.LogPath -or -not $loggingState.LogFileInitialized) {
        Write-Verbose "Log file not available for security logging - attempting emergency logging"
        Write-SecurityFileEntryDirect -Message $FormattedMessage
        return
    }

    try {
        # BYPASS log level filtering - always write security entries
        Add-Content -Path $loggingState.LogPath -Value $FormattedMessage -Encoding UTF8 -ErrorAction Stop -WhatIf:$false
        Write-Verbose "Security log entry written successfully to: $($loggingState.LogPath)"
    }
    catch {
        Write-Warning "Security log file write failed, attempting recovery: $($_.Exception.Message)"

        # Try to reinitialize the log file for security logging
        try {
            Write-SecurityFileEntryDirect -Message $FormattedMessage
        }
        catch {
            Write-Error "CRITICAL: Security log entry could not be written: $($_.Exception.Message)"
        }
    }
}


function Write-SecurityFileEntryDirect {
    <#
    .SYNOPSIS
        Direct security log file write with minimal dependencies
 
    .DESCRIPTION
        Last-resort security logging that attempts to write directly
        to log file or emergency locations when normal logging fails.
 
    .PARAMETER Message
        Security log message to write directly
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Message
    )

    $loggingState = Get-LoggingSystemState

    # Try normal log path first
    if ($loggingState.LogPath) {
        try {
            Add-Content -Path $loggingState.LogPath -Value $Message -Encoding UTF8 -ErrorAction Stop -WhatIf:$false
            return
        }
        catch {
            Write-Verbose "Direct log path failed: $($_.Exception.Message)"
        }
    }

    # Fallback to temp directory
    try {
        $tempLogPath = Join-Path $env:TEMP "OrphanedSid-Security-$(Get-Date -Format 'yyyyMMdd').log"
        Add-Content -Path $tempLogPath -Value $Message -Encoding UTF8 -ErrorAction Stop -WhatIf:$false
        Write-Warning "Security log written to emergency location: $tempLogPath"
    }
    catch {
        Write-Error "CRITICAL: Cannot write security log entry anywhere: $($_.Exception.Message)"
    }
}


function Update-SecurityLoggingMetrics {
    <#
    .SYNOPSIS
        Updates security-specific logging metrics
 
    .DESCRIPTION
        Tracks security logging events separately from general logging
        for security monitoring and compliance reporting.
 
    .PARAMETER Level
        Security log level that was written
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '',
        Justification = 'Metrics is a mass noun for the security logging counters')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Changes module state only, nothing on the system')]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Critical', 'Error', 'Warning', 'Information')]
        [string]$Level
    )

    # Initialize security metrics if not exists
    if (-not $script:SecurityLoggingMetrics) {
        $script:SecurityLoggingMetrics = @{
            TotalSecurityEntries = 0
            SecurityEntriesByLevel = @{
                Critical = 0
                Error = 0
                Warning = 0
                Information = 0
            }
            LastSecurityLogTime = $null
            SecurityLoggingErrors = 0
        }
    }

    # Update metrics
    $script:SecurityLoggingMetrics.TotalSecurityEntries++
    $script:SecurityLoggingMetrics.SecurityEntriesByLevel[$Level]++
    $script:SecurityLoggingMetrics.LastSecurityLogTime = Get-Date
}