Public/Get-LogEntries.ps1
# Public\Get-LogEntries.ps1 function Get-LogEntries { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string[]]$Path, [string[]]$IncludeKeywords = @(), [string[]]$ExcludeKeywords = @(), [datetime]$StartTime, [datetime]$EndTime, [switch]$Ascending, [ValidateSet("Forward", "Reverse")] [string]$SortOrder = "Forward", [int[]]$EventId, [string[]]$Level, [string[]]$ProviderName, [string]$ExportPath, [ValidateSet("CSV", "JSON")] [string]$ExportFormat = "CSV", [int]$Tail, [int]$LineLimit, [switch]$Redact, [switch]$Colorize, [ValidateSet("System", "Application", "Security", "Custom")] [string]$LogType = "Custom" ) $SortOrder = if ($Ascending.IsPresent) { "Forward" } else { $SortOrder } if ($Ascending.IsPresent -and $PSBoundParameters.ContainsKey('SortOrder')) { Write-Warning "⚠️ Both -Ascending and -SortOrder were passed. Prioritizing -Ascending (Forward sort)." } if ($LogType -ne "Custom" -and -not $PSBoundParameters.ContainsKey('Path')) { if (-not $IsWindows) { throw "❌ LogType filtering is only supported on Windows event logs." } $Path = @($LogType) } foreach ($p in $Path) { if (($p -ne 'journalctl') -and ($p -notin @("System", "Application", "Security")) -and (-not (Test-Path $p))) { throw [System.IO.FileNotFoundException]::new("File not found: $($Path -join ', ')", $p) } } $allParsedEntries = [System.Collections.Generic.List[PSCustomObject]]::new() $includeRegex = if ($IncludeKeywords) { ($IncludeKeywords -join "|") } else { $null } $excludeRegex = if ($ExcludeKeywords) { ($ExcludeKeywords -join "|") } else { $null } foreach ($currentPath in $Path) { if ($currentPath -in @("System", "Application", "Security") -or $currentPath -match '\.(evtx|evt)$') { if (-not $IsWindows) { throw "❌ Windows event log parsing is only supported on Windows." } try { $queryParams = @{ Oldest = $true } if ($currentPath -in @("System", "Application", "Security")) { $queryParams.LogName = $currentPath } else { $queryParams.Path = $currentPath } $events = Get-WinEvent @queryParams | Where-Object { (!($StartTime) -or $_.TimeCreated -ge $StartTime) -and (!($EndTime) -or $_.TimeCreated -le $EndTime) -and (!($EventId) -or $EventId -contains $_.Id) -and (!($Level) -or $Level -contains $_.LevelDisplayName) -and (!($ProviderName) -or $ProviderName -contains $_.ProviderName) } $parsed = foreach ($e in $events) { $message = if ($Redact) { Protect-Message -Message $e.Message } else { $e.Message } [PSCustomObject]@{ Timestamp = $e.TimeCreated Level = $e.LevelDisplayName Provider = $e.ProviderName EventId = $e.Id Message = $message RawLine = $e.ToXml() } } $allParsedEntries.AddRange($parsed) } catch { throw "❌ Failed to parse Windows event log from '$currentPath': $_" } } elseif ($currentPath -eq 'journalctl' -and -not $IsWindows) { try { $cmd = @("journalctl", "--no-pager") if ($StartTime) { $cmd += "--since=$($StartTime.ToString("yyyy-MM-dd HH:mm:ss"))" } if ($EndTime) { $cmd += "--until=$($EndTime.ToString("yyyy-MM-dd HH:mm:ss"))" } $rawEntries = & $cmd foreach ($line in $rawEntries) { $tsMatch = if ($line -match '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}') { [datetime]::Parse($matches[0]) } else { $null } $msg = if ($Redact) { Protect-Message -Message $line } else { $line } $allParsedEntries.Add([PSCustomObject]@{ Timestamp = $tsMatch Message = $msg RawLine = $line }) } } catch { throw "❌ Failed to retrieve journalctl entries: $_" } } else { try { $lines = Get-Content -Path $currentPath -Encoding UTF8 -ErrorAction Stop foreach ($line in $lines) { $parsedEntry = Format-LogEntry -Line $line -Redact:$Redact if ($parsedEntry) { $allParsedEntries.Add($parsedEntry) } } } catch { throw "❌ Failed to read text log from '$currentPath': $_" } } } $finalEntries = $allParsedEntries | Where-Object { (!($StartTime) -or $_.Timestamp -ge $StartTime) -and (!($EndTime) -or $_.Timestamp -le $EndTime) -and (!($includeRegex) -or ($_.Level -match $includeRegex -or $_.Message -match $includeRegex)) -and (!($excludeRegex) -or -not ($_.Level -match $excludeRegex -or $_.Message -match $excludeRegex)) } $finalEntries = if ($SortOrder -eq "Reverse") { $finalEntries | Sort-Object Timestamp -Descending } else { $finalEntries | Sort-Object Timestamp -Ascending } if ($Tail) { $finalEntries = $finalEntries | Select-Object -Last $Tail } elseif ($LineLimit) { $finalEntries = $finalEntries | Select-Object -First $LineLimit } if ($Colorize) { $coloredEntries = foreach ($entry in $finalEntries) { $color = switch -Regex ($entry.Level) { 'ERROR' { 'Red' } 'WARN' { 'Yellow' } 'INFO' { 'Green' } default { $null } } $formatted = Format-LogEntry -Line $entry.RawLine -Redact:$Redact if ($color) { Write-Host $formatted -ForegroundColor $color } else { Write-Host $formatted } $entry } $finalEntries = $coloredEntries } if ($ExportPath) { try { if ($ExportFormat -eq "CSV") { $finalEntries | Export-Csv -Path $ExportPath -NoTypeInformation -Force } else { $finalEntries | ConvertTo-Json -Depth 3 | Out-File -FilePath $ExportPath -Encoding UTF8 } Write-Host "✅ Log entries exported to $ExportPath" -ForegroundColor Green } catch { Write-Warning "❌ Failed to export: $_" } } return $finalEntries } |