Private/Write-BlackCatLog.ps1

function Write-BlackCatLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowEmptyString()]
        [string]$Message,

        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateSet("TRACE", "DEBUG", "INFO", "WARN", "WARNING", "ERROR", "FATAL")]
        [string]$Level = "INFO",

        [Parameter(Mandatory = $false)]
        [string]$FunctionName,

        [Parameter(Mandatory = $false)]
        [string]$LogPath,

        [Parameter(Mandatory = $false)]
        [switch]$WriteHost,

        [Parameter(Mandatory = $false)]
        [switch]$NoTimestamp,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Black", "Blue", "Cyan", "DarkBlue", "DarkCyan", "DarkGray", "DarkGreen",
            "DarkMagenta", "DarkRed", "DarkYellow", "Gray", "Green", "Magenta", "Red", "White", "Yellow")]
        [string]$Color
    )

    begin {
        # Normalize log level (WARNING is an alias for WARN)
        if ($Level -eq "WARNING") {
            $Level = "WARN"
        }

        # Auto-detect calling function if not provided
        if (-not $FunctionName) {
            try {
                $CallStack = Get-PSCallStack
                if ($CallStack.Count -gt 1) {
                    $FunctionName = $CallStack[1].FunctionName
                    # Clean up function name (remove module prefixes, etc.)
                    if ($FunctionName -eq '<ScriptBlock>') {
                        $FunctionName = $CallStack[1].ScriptName | Split-Path -Leaf
                    }
                }
                else {
                    $FunctionName = "Unknown"
                }
            }
            catch {
                $FunctionName = "Unknown"
            }
        }

        # Use default log path from session variables if available and not specified
        if (-not $LogPath -and $script:SessionVariables -and $script:SessionVariables.ContainsKey('defaultLogPath')) {
            $LogPath = $script:SessionVariables.defaultLogPath
        }

        # Default colors for different log levels
        if (-not $Color) {
            $ColorMap = @{
                "TRACE" = "Gray"
                "DEBUG" = "Cyan"
                "INFO"  = "Green"
                "WARN"  = "Yellow"
                "ERROR" = "Red"
                "FATAL" = "Magenta"
            }
            $Color = $ColorMap[$Level]
        }
    }
    
    process {
        # Build log entry components
        $Components = @()

        # Add timestamp unless suppressed
        if (-not $NoTimestamp) {
            $Components += "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')]"
        }

        # Add log level
        $Components += "[$Level]"

        # Add function context
        if ($FunctionName -and $FunctionName -ne "Unknown") {
            $Components += "[$FunctionName]"
        }

        # Add the message
        $Components += $Message

        # Combine all components
        $LogEntry = $Components -join " "

        # Output to console based on log level and WriteHost switch
        if ($WriteHost -or $Level -in @("ERROR", "FATAL")) {
            # Use Write-Host for user-facing messages or errors
            Write-Host $LogEntry -ForegroundColor $Color
        }
        elseif ($Level -eq "WARN") {
            # Use Write-Warning for warnings
            Write-Warning $LogEntry
        }
        elseif ($Level -in @("TRACE", "DEBUG")) {
            # Use Write-Debug for debug messages (only shown with -Debug)
            Write-Debug $LogEntry
        }
        else {
            # Use Write-Verbose for INFO and other levels
            Write-Verbose $LogEntry
        }

        # Write to log file if path is provided
        if ($LogPath) {
            try {
                # Ensure directory exists
                $LogDir = Split-Path -Path $LogPath -Parent
                if ($LogDir -and -not (Test-Path -Path $LogDir)) {
                    New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
                }

                # Append to log file
                Add-Content -Path $LogPath -Value $LogEntry -Encoding UTF8 -ErrorAction SilentlyContinue
            }
            catch {
                # Silently continue if file write fails to avoid disrupting the main operation
                # Could optionally write to Windows Event Log or other fallback here
            }
        }
    }
    <#
.SYNOPSIS
    Generic logging function for the BlackCat PowerShell module.
 
.DESCRIPTION
    The Write-BlackCatLog function provides standardized logging capabilities across the entire BlackCat module.
    It supports multiple log levels, optional file output, function context tracking, and can be used by any
    BlackCat function for consistent logging behavior. The function handles errors gracefully and provides
    both console output and file logging capabilities.
 
.PARAMETER Message
    The message to log. This parameter is mandatory and supports multi-line strings.
 
.PARAMETER Level
    The log level for the message. Valid values are: TRACE, DEBUG, INFO, WARN, WARNING, ERROR, FATAL.
    Default is "INFO". WARNING is treated as an alias for WARN.
 
.PARAMETER FunctionName
    The name of the calling function for context tracking. If not provided, it will attempt to
    automatically detect the calling function name.
 
.PARAMETER LogPath
    Optional path to a log file where the message should be written in addition to console output.
    If not provided, uses the module's default log path if configured.
 
.PARAMETER WriteHost
    Switch to write INFO and WARN messages to the host using Write-Host instead of Write-Verbose.
    Useful for user-facing messages that should always be visible.
 
.PARAMETER NoTimestamp
    Switch to exclude timestamps from log entries. Useful for formatted output or when timestamps
    are not needed.
 
.PARAMETER Color
    Console color for Write-Host output. Valid PowerShell colors. Defaults are:
    - TRACE: Gray
    - DEBUG: Cyan
    - INFO: Green
    - WARN: Yellow
    - ERROR: Red
    - FATAL: Magenta
 
.EXAMPLE
    Write-BlackCatLog "Starting Azure resource enumeration"
 
    Logs an informational message with auto-detected function context.
 
.EXAMPLE
    Write-BlackCatLog "Authentication failed" "ERROR" -FunctionName "Connect-AzureAccount"
 
    Logs an error message with explicit function context.
 
.EXAMPLE
    Write-BlackCatLog "Processing 100 storage accounts" "INFO" -WriteHost
 
    Logs an informational message that will be visible to the user via Write-Host.
 
.EXAMPLE
    Write-BlackCatLog "Detailed debugging information" "DEBUG" -LogPath ".\debug.log"
 
    Logs a debug message to a specific log file.
 
.EXAMPLE
    Write-BlackCatLog "Critical system failure" "FATAL" -WriteHost -Color "Red"
 
    Logs a fatal error that will be displayed prominently to the user.
 
.NOTES
    This function is designed to be used throughout the BlackCat module by any function that needs logging.
    It provides consistent formatting, error handling, and supports both development debugging and user output.
 
    The function automatically detects the calling function name when not explicitly provided.
    File I/O errors are handled gracefully to avoid disrupting the main operation.
 
    Log levels follow standard logging conventions:
    - TRACE: Most detailed, for tracing program flow
    - DEBUG: Detailed information for debugging
    - INFO: General information about program execution
    - WARN: Warning messages for potentially harmful situations
    - ERROR: Error events that might allow the application to continue
    - FATAL: Critical errors that may cause termination
#>

}