Private/Functions/Invoke-FileSink.ps1

function Invoke-FileSink {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][hashtable]$Options,
        [Parameter(Mandatory)][string]$Component,
        [Parameter(Mandatory)][string]$Message,
        [Parameter(Mandatory)][ValidateSet('Info','Success','Warning','Error','Debug','Verbose')][string]$Severity,
        [int]$IndentLevel = 0
    )

    $path = $Options.Path
    if (-not $path) { return }
    $format   = ($Options.Format   | ForEach-Object { $_ }) -as [string]; if (-not $format) { $format = 'Default' }
    $rotation = ($Options.Rotation | ForEach-Object { $_ }) -as [string]
    $maxSizeMB = [double]($(if ($Options.ContainsKey('MaxSizeMB')) { $Options.MaxSizeMB } else { 5 }))
    $maxRolls  = [int]  ($(if ($Options.ContainsKey('MaxRolls'))  { $Options.MaxRolls }  else { 3 }))
    $encoding  = $(if ($Options.ContainsKey('Encoding')) { $Options.Encoding } else { 'UTF8BOM' })

    $effectivePath = $path
    if ($rotation -match 'Daily') {
        $dir = Split-Path -Parent $path
        $leaf = Split-Path -Leaf $path
        $date = Get-Date -Format 'yyyy-MM-dd'
        if ($leaf -match '\.log$') {
            $base = [System.IO.Path]::GetFileNameWithoutExtension($leaf)
            $ext  = [System.IO.Path]::GetExtension($leaf)
            $effectivePath = Join-Path $dir ("{0}.{1}{2}" -f $base,$date,$ext)
        } else {
            $effectivePath = Join-Path $dir ("{0}.{1}" -f $leaf,$date)
        }
    }

    $dirToEnsure = Split-Path -Parent $effectivePath
    if ($dirToEnsure -and -not (Test-Path -LiteralPath $dirToEnsure)) { New-Item -ItemType Directory -Path $dirToEnsure -Force | Out-Null }

    # Rotation mode: NewFile -> archive existing file once per session before first write
    if ($rotation -match 'NewFile') {
        if (-not $script:FileSinkInitialized) { $script:FileSinkInitialized = @{} }
        $initKey = "newfile::" + $effectivePath
        if (-not $script:FileSinkInitialized.ContainsKey($initKey)) {
            try {
                if (Test-Path -LiteralPath $effectivePath) {
                    $dir = Split-Path -Parent $effectivePath
                    $base = Split-Path -Leaf $effectivePath
                    for ($i = $maxRolls; $i -ge 1; $i--) {
                        $dstLeaf = "$base.$i"
                        $dst = Join-Path $dir $dstLeaf
                        if (Test-Path -LiteralPath $dst) { Remove-Item -LiteralPath $dst -Force -ErrorAction SilentlyContinue }
                        if ($i -eq 1) {
                            if (Test-Path -LiteralPath $effectivePath) { Rename-Item -LiteralPath $effectivePath -NewName $dstLeaf -Force -ErrorAction SilentlyContinue }
                        } else {
                            $srcLeaf = "$base.$($i-1)"
                            $src = Join-Path $dir $srcLeaf
                            if (Test-Path -LiteralPath $src) { Rename-Item -LiteralPath $src -NewName $dstLeaf -Force -ErrorAction SilentlyContinue }
                        }
                    }
                }
            } catch { }
            $script:FileSinkInitialized[$initKey] = $true
        }
    }

    if ($rotation -match 'Size') {
        try {
            if (Test-Path -LiteralPath $effectivePath) {
                $lenBytes = (Get-Item -LiteralPath $effectivePath).Length
                $lenMB = $lenBytes / 1MB
                if ($lenMB -ge $maxSizeMB) {
                    $dir = Split-Path -Parent $effectivePath
                    $base = Split-Path -Leaf $effectivePath
                    for ($i = $maxRolls; $i -ge 1; $i--) {
                        $dstLeaf = "$base.$i"
                        $dst = Join-Path $dir $dstLeaf
                        if (Test-Path -LiteralPath $dst) { Remove-Item -LiteralPath $dst -Force -ErrorAction SilentlyContinue }
                        if ($i -eq 1) {
                            if (Test-Path -LiteralPath $effectivePath) { Rename-Item -LiteralPath $effectivePath -NewName $dstLeaf -Force -ErrorAction SilentlyContinue }
                        } else {
                            $srcLeaf = "$base.$($i-1)"
                            $src = Join-Path $dir $srcLeaf
                            if (Test-Path -LiteralPath $src) { Rename-Item -LiteralPath $src -NewName $dstLeaf -Force -ErrorAction SilentlyContinue }
                        }
                    }
                }
            }
        } catch { }
    }

    $indent = if ($IndentLevel -gt 0) { ' ' * ($IndentLevel * 2) } else { '' }
    $now = Get-Date

    switch -Regex ($format) {
        '^cmtrace$' {
            $ts = $now.ToString('MM-dd-yyyy HH:mm:ss.fff')
            $line = Format-LoggerLineCmtrace -Timestamp $ts -Severity $Severity -Component $Component -Message $Message -IndentLevel $IndentLevel
        }
        default {
            $ts = $now.ToString('yyyy-MM-dd HH:mm:ss')
            $line = Format-LoggerLineDefault -Timestamp $ts -Severity $Severity -Component $Component -Message $Message -IndentLevel $IndentLevel
        }
    }

    try {
        $encObj = switch -Regex ($encoding) {
            '^UTF8$'      { New-Object System.Text.UTF8Encoding($false); break }
            '^UTF8BOM$'   { New-Object System.Text.UTF8Encoding($true); break }
            '^ASCII$'     { [System.Text.Encoding]::ASCII; break }
            '^Unicode$'   { [System.Text.Encoding]::Unicode; break }
            '^UTF7$'      { [System.Text.Encoding]::UTF7; break }
            '^UTF32$'     { [System.Text.Encoding]::UTF32; break }
            '^Default$'   { [System.Text.Encoding]::Default; break }
            '^OEM$'       { [System.Text.Encoding]::GetEncoding([System.Console]::OutputEncoding.CodePage); break }
            default       { New-Object System.Text.UTF8Encoding($false) }
        }
        if (-not $script:FileSinkInitialized) { $script:FileSinkInitialized = @{} }
        $firstWrite = $false
        if (-not $rotation) {
            if (-not $script:FileSinkInitialized.ContainsKey($effectivePath)) {
                $script:FileSinkInitialized[$effectivePath] = $true
                $firstWrite = $true
            }
        }

        if ($firstWrite) {
            [System.IO.File]::WriteAllText($effectivePath, $line + [Environment]::NewLine, $encObj)
        } else {
            [System.IO.File]::AppendAllText($effectivePath, $line + [Environment]::NewLine, $encObj)
        }
    } catch { }
}