Private/Logging/Write-PatchLog.ps1

function Write-PatchLog {
    <#
    .SYNOPSIS
        Writes a log entry in CMTrace-compatible format
    .DESCRIPTION
        Logs messages to a file in a format compatible with CMTrace log viewer.
        Also optionally writes to Windows Event Log.
    .PARAMETER Message
        The message to log
    .PARAMETER Type
        Log level: Info, Warning, or Error
    .PARAMETER Component
        The component or function name generating the log
    .PARAMETER LogFile
        Optional specific log file path. Defaults to daily log in module log directory.
    .PARAMETER NoEventLog
        Skip writing to Windows Event Log
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Message,
        
        [Parameter()]
        [ValidateSet('Info', 'Warning', 'Error')]
        [string]$Type = 'Info',
        
        [Parameter()]
        [string]$Component = 'PsPatchMyPC',
        
        [Parameter()]
        [string]$LogFile,
        
        [Parameter()]
        [switch]$NoEventLog
    )
    
    try {
        $config = Get-ModuleConfiguration
        
        # Determine log file path
        if (-not $LogFile) {
            $LogFile = Join-Path $config.LogPath "PsPatchMyPC_$(Get-Date -Format 'yyyyMMdd').log"
        }
        
        # Ensure log directory exists
        $logDir = Split-Path $LogFile -Parent
        if (-not (Test-Path $logDir)) {
            New-Item -Path $logDir -ItemType Directory -Force | Out-Null
        }
        
        # Map type to CMTrace type number
        $typeNum = switch ($Type) {
            'Info' { 1 }
            'Warning' { 2 }
            'Error' { 3 }
            default { 1 }
        }
        
        # Get caller info
        $caller = Get-PSCallStack | Select-Object -Skip 1 -First 1
        $callerComponent = if ($caller.Command) { $caller.Command } else { $Component }
        
        # Build CMTrace format log entry
        $time = Get-Date -Format 'HH:mm:ss.fff'
        $date = Get-Date -Format 'MM-dd-yyyy'
        $context = [Security.Principal.WindowsIdentity]::GetCurrent().Name
        
        $logEntry = "<![LOG[$Message]LOG]!>" +
            "<time=`"$time`" " +
            "date=`"$date`" " +
            "component=`"$callerComponent`" " +
            "context=`"$context`" " +
            "type=`"$typeNum`" " +
            "thread=`"$PID`" " +
            "file=`"`">"
        
        # Write to file (thread-safe with mutex)
        $mutex = New-Object System.Threading.Mutex($false, 'Global\PsPatchMyPCLogMutex')
        try {
            $null = $mutex.WaitOne(5000)
            $logEntry | Out-File -FilePath $LogFile -Append -Encoding UTF8
        }
        finally {
            $mutex.ReleaseMutex()
            $mutex.Dispose()
        }
        
        # Write to Event Log if enabled
        if (-not $NoEventLog -and $env:PSPMPC_EVENT_LOG -ne 'false') {
            Write-EventLogEntry -Message $Message -Type $Type -Component $callerComponent
        }
        
        # Also write to verbose stream for debugging
        switch ($Type) {
            'Info' { Write-Verbose $Message }
            'Warning' { Write-Warning $Message }
            'Error' { Write-Error $Message -ErrorAction SilentlyContinue }
        }
    }
    catch {
        # Fail silently to not break calling code
        Write-Verbose "Logging failed: $_"
    }
}