Private/ODBLogProcessor.ps1

class ODBLogProcessor {

    # ── Properties ──────────────────────────────────────────────────────────────
    [System.IO.FileInfo] $SourceFile      # validated FileInfo for the input .cab/.zip
    [string]             $DateFilter      # e.g. "2023-10-31"
    [string]             $Platform        # "Windows" | "Mac"
    [string]             $WorkingDir      # parent directory of the source file
    [string]             $ExtractedDir    # full path to the extracted subfolder

    # ── Constructor ──────────────────────────────────────────────────────────────
    ODBLogProcessor([string]$Path, [string]$Date, [string]$Platform) {
        try {
            $this.SourceFile = Get-Item -LiteralPath $Path -ErrorAction Stop
        }
        catch {
            throw "Source file not found at '$Path': $_"
        }

        $this.DateFilter   = $Date
        $this.Platform     = $Platform
        $this.WorkingDir   = $this.SourceFile.DirectoryName
        $this.ExtractedDir = Join-Path $this.WorkingDir $this.SourceFile.BaseName
    }

    # ── Methods ──────────────────────────────────────────────────────────────────

    # Step 1 — Extract the source file.
    # Windows .cab → extrac32.exe (native Cabinet tool)
    # Mac .zip → Expand-Archive (PowerShell built-in)
    [void] ExtractSource() {
        if ($this.Platform -eq 'Windows') {
            $proc = Start-Process -FilePath 'extrac32.exe' `
                                  -ArgumentList "`"$($this.SourceFile.FullName)`"", '/E', '/L', "`"$($this.ExtractedDir)`"" `
                                  -Wait `
                                  -PassThru
            if ($proc.ExitCode -ne 0) {
                throw "extrac32.exe exited with code $($proc.ExitCode). Extraction may have failed."
            }
        }
        else {
            Expand-Archive -Path $this.SourceFile.FullName -DestinationPath $this.ExtractedDir -Force
        }
    }

    # Step 2 — Collect ODL log files (files to KEEP).
    # Windows adds *.odlsent; Mac does not (Mac ODB does not generate them).
    [System.IO.FileInfo[]] CollectOdlFiles() {
        $extensions = @('*.odl', '*.odlgz', '*.etlgz')
        if ($this.Platform -eq 'Windows') {
            $extensions += '*.odlsent'
        }

        $files = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
        foreach ($ext in $extensions) {
            foreach ($f in (Get-ChildItem -Path $this.ExtractedDir -Recurse -Filter $ext)) {
                $files.Add($f)
            }
        }
        return $files.ToArray()
    }

    # Step 3 — Collect the four excluded file categories.
    # Returns a hashtable keyed by the zip-suffix label used in CompressExcluded.
    [hashtable] CollectExcludedFiles() {
        return @{
            'EventLogsFiles' = @(Get-ChildItem -Path $this.ExtractedDir -Recurse -Filter '*.evtx')
            'DbFiles'        = @(Get-ChildItem -Path $this.ExtractedDir -Recurse -Filter '*.db')
            'DbWalFiles'     = @(Get-ChildItem -Path $this.ExtractedDir -Recurse -Filter '*.db-wal')
            'SetupLogFiles'  = @(Get-ChildItem -Path $this.ExtractedDir -Recurse -Filter '*.log')
        }
    }

    # Step 4 — Rename each excluded file with its immediate parent folder as a prefix
    # to avoid collisions when files from different subdirs are compressed together.
    # Returns a hashtable of the same keys, now holding [string[]] of new absolute paths.
    [hashtable] RenameExcludedFiles([hashtable]$ExcludedGroups) {
        $renamed = @{}
        foreach ($key in $ExcludedGroups.Keys) {
            $group = $ExcludedGroups[$key]
            if ($group.Count -gt 0) {
                $group | Rename-Item -NewName {
                    $parentLeaf = Split-Path $_.DirectoryName -Leaf
                    "${parentLeaf}_$($_.Name)"
                }
                $renamed[$key] = $group | ForEach-Object {
                    $parentLeaf = Split-Path $_.DirectoryName -Leaf
                    Join-Path $_.DirectoryName "${parentLeaf}_$($_.Name)"
                }
            }
            else {
                $renamed[$key] = @()
            }
        }
        return $renamed
    }

    # Step 5 — Compress each excluded category into its own .zip side archive.
    [void] CompressExcluded([hashtable]$RenamedGroups) {
        foreach ($key in $RenamedGroups.Keys) {
            $paths = $RenamedGroups[$key]
            if ($paths.Count -gt 0) {
                $destination = Join-Path $this.WorkingDir "$($this.SourceFile.BaseName)-${key}.zip"
                $paths | Compress-Archive -DestinationPath $destination -CompressionLevel Optimal
            }
        }
    }

    # Step 6a — Delete excluded files after they have been archived.
    # Windows suppresses deletion errors (mirrors original script behaviour);
    # Mac lets errors surface. Both use explicit -ErrorAction on Remove-Item
    # because class methods cannot assign to scope-based preference variables.
    [void] DeleteExcluded([hashtable]$RenamedGroups) {
        $errorAction = if ($this.Platform -eq 'Windows') { 'SilentlyContinue' } else { 'Continue' }
        foreach ($paths in $RenamedGroups.Values) {
            if ($paths.Count -gt 0) {
                $paths | Remove-Item -Force -ErrorAction $errorAction
            }
        }
    }

    # Step 6b — Delete ODL files whose BaseName does not match the date filter.
    [void] DeleteNonMatchingOdl([System.IO.FileInfo[]]$OdlFiles) {
        $toDelete = $OdlFiles | Where-Object { $_.BaseName -notmatch $this.DateFilter }
        if ($toDelete) {
            $toDelete | Remove-Item -Force
        }
    }

    # Step 8 — Remove the extracted working directory and its contents.
    [void] Cleanup() {
        if (Test-Path -LiteralPath $this.ExtractedDir) {
            Remove-Item -LiteralPath $this.ExtractedDir -Recurse -Force -ErrorAction SilentlyContinue
        }
    }

    # Step 7 — Compress remaining logs into the final Parsed archive.
    # Windows → {basename}-Parsed.cab via makecab.exe (preserves folder structure via DDF).
    # Mac → {basename}-Parsed.zip via Compress-Archive.
    [string] CompressParsed() {
        if ($this.Platform -eq 'Windows') {
            $destination = Join-Path $this.WorkingDir "$($this.SourceFile.BaseName)-Parsed.cab"
            return New-CabinetFile -SourceDirectory $this.ExtractedDir -Destination $destination
        }
        else {
            $destination = Join-Path $this.WorkingDir "$($this.SourceFile.BaseName)-Parsed.zip"
            Compress-Archive -Path "$($this.ExtractedDir)\*" -DestinationPath $destination -CompressionLevel Optimal
            return $destination
        }
    }
}