diff --git a/playbook/shopfloor-setup/common/GE-Enforce.ps1 b/playbook/shopfloor-setup/common/GE-Enforce.ps1 index 04e4e0b..dba42f8 100644 --- a/playbook/shopfloor-setup/common/GE-Enforce.ps1 +++ b/playbook/shopfloor-setup/common/GE-Enforce.ps1 @@ -46,13 +46,76 @@ Write-EnforceLog '============================================================== Write-EnforceLog "=== GE-Enforce session start (PID $PID, user $env:USERNAME) ===" Write-EnforceLog '================================================================' +$logRoots = @('C:\Logs\Shopfloor', 'C:\Logs\SFLD', 'C:\Logs\Keyence') + +# --- Roll undated logs so they can age --- +# The prune below drops a *.log by LastWriteTime. A log written to a FIXED +# filename is appended every cycle, so its LastWriteTime is always now and it +# can never be older than any cutoff - it grows forever. ntlars-backup.log, +# eventsaver.log and shopdb-collector-key.log all do this; eventsaver.log had +# reached 11,000 lines on a single PC. +# +# Rolling one to -YYYYMMDD.log stops it being written to, so the prune +# takes it 30 days later, and the script that owns it needs no change: they all +# append with Add-Content or Tee-Object, which recreate a missing file on the +# next write. A file already carrying a date stamp is left alone. +# +# Rolled under the date of its FIRST LINE, so the stamp matches the lines +# inside. That timestamp is also the only trustworthy signal available: +# CreationTime is not, because of NTFS file tunneling - rename a file away and +# let the owner recreate it within 15 seconds, and the new file INHERITS the +# old creation time. Keyed on that, a busy log would look stale the moment it +# was rolled and would roll again every cycle, forever. +$rolledCount = 0 +$today = (Get-Date).Date +foreach ($root in $logRoots) { + if (-not (Test-Path $root)) { continue } + Get-ChildItem -Path $root -Filter '*.log' -File -ErrorAction SilentlyContinue | + Where-Object { $_.BaseName -notmatch '\d{8}$' -and $_.Length -gt 0 } | + ForEach-Object { + $firstline = Get-Content -LiteralPath $_.FullName -TotalCount 1 -ErrorAction SilentlyContinue + # MUST be pre-typed. TryParse takes [ref][datetime], and passing a + # [ref] to an untyped $null throws "cannot find an overload", which + # would make this whole roll a silent no-op. + $started = [datetime]::MinValue + if ($firstline -match '(\d{4}-\d{2}-\d{2})') { + [void][datetime]::TryParse($matches[1], [ref]$started) + } + # No parseable date: fall back to size so a log with a format we do + # not recognise still cannot grow without limit. + if ($started -eq [datetime]::MinValue) { + if ($_.Length -lt 5MB) { return } + $started = $today.AddDays(-1) + } + if ($started.Date -ge $today) { return } + + $stamp = $started.ToString('yyyyMMdd') + $target = Join-Path $_.DirectoryName ('{0}-{1}.log' -f $_.BaseName, $stamp) + # An existing target means the roll already ran for that day; append + # to it rather than losing either side. + try { + if (Test-Path -LiteralPath $target) { + Get-Content -LiteralPath $_.FullName -ErrorAction Stop | + Add-Content -LiteralPath $target -ErrorAction Stop + Remove-Item -LiteralPath $_.FullName -Force -ErrorAction Stop + } else { + Move-Item -LiteralPath $_.FullName -Destination $target -ErrorAction Stop + } + $rolledCount++ + } catch {} + } +} +if ($rolledCount -gt 0) { + Write-EnforceLog "Rolled $rolledCount undated log file(s) so retention can age them" +} + # --- Log retention prune --- # Drops *.log files older than $retentionDays from the shopfloor log roots. # Cheap (flat dir scan, no recursion). Runs every cycle. Today's # enforce-YYYYMMDD.log is never touched (LastWriteTime = now). $retentionDays = 30 $prunedCount = 0 -foreach ($root in @('C:\Logs\Shopfloor', 'C:\Logs\SFLD', 'C:\Logs\Keyence')) { +foreach ($root in $logRoots) { if (-not (Test-Path $root)) { continue } $cutoff = (Get-Date).AddDays(-$retentionDays) Get-ChildItem -Path $root -Filter '*.log' -File -ErrorAction SilentlyContinue |