Hi together,
i created a LogFileParser (continuation from here), which can handle till now 4 different LogFileTypes:
SCCM-Logs
DISM
CBS
Upgrade-Logs
It´s written for Powershell v5, because it makes use of classes and i wanted to show you, how you can work with enums and classes.
Here is how you can make use of it: (can be found in Examples.ps1)
Download: https://github.com/ddneves/LogFileParser
Its code is written with Powershell classes which can be easily extended with additional helper functions or new LogFileTypes.
I integrated also a helper function to retrieve a range of cells before and after defined cell rownumbers. I used it to gather all rows, which contain “*error*” and all the 20(x) rows before and after for a whole file.
#requires -Version 5 <# .NOTES =========================================================================== Created on: 07.08.2016 Created by: David das Neves Version: 0.1 Project: LogAnalyzer Filename: LogFileParser.ps1 =========================================================================== .DESCRIPTION Parses Logfiles into the class ParsedLogFile, which is integrated in the class LogFileParser #> ## Enumeration of the LogFileTypes enum LogFileTypes { SCCM CBS Upgrade DISM } ## Class of the LogParser which can open n logfiles class LogFileParser { #region Props # Generic List of all parsed LogFiles of Type "ParsedLogfile" (Class) [System.Collections.Generic.List``1[ParsedLogFile]] $ParsedLogFiles # FilePath [String] $LogFilePath #endregion #region Funcs # Constructor LogFileParser($LogFilePath) { # Constructor Code if (Test-Path $LogFilePath) { $allLogFiles = Get-ChildItem $LogFilePath -Filter *.log -Recurse $this.ParsedLogFiles = New-Object -TypeName System.Collections.Generic.List``1[ParsedLogFile] $this.LogFilePath = $LogFilePath foreach ($file in $allLogFiles) { $fileType = Get-LogFileType $file.Name $this.ParsedLogFiles.Add([ParsedLogFile]::new($file.FullName, $fileType)) } } else { Write-Error -Message 'Path was not reachable. Please verify your Path.' } } #endregion } class ParsedLogFile { #region Props #Hidden variable for the keys in the logfile. hidden [string[]] $ColumnNames # FilePath of the parsed log. [string] $LogFilePath #The parsed logging data. $ParsedLogData #LogFileType for this file [LogFileTypes] $LogFileType #endregion #region Funcs ## standard constructor ## LogFileType SCCM is set ParsedLogFile($LogFilePath) { $this.LogFileType = [LogFileTypes]::SCCM $this.LogFilePath = $LogFilePath $this.LogFileType = $this.LogFileType $this.Init() } ## Constructr with LogFileType ParsedLogFile($LogFilePath, $LogFileType) { $this.LogFilePath = $LogFilePath $this.LogFileType = [LogFileTypes]$LogFileType $this.Init() } ## Initialization of class and log hidden Init() { if (Test-Path -Path $this.LogFilePath) { # Constructor Code $this.LogFilePath = $this.LogFilePath Write-Host -Object "Parsing LogFile $($this.LogFilePath) with LogfileType $($this.LogFileType)." $actualParsedLog = Get-RegExParsedLogfile -Path $this.LogFilePath -LogFileType $this.LogFileType Write-Host -Object 'Parsing done.' $this.ParsedLogData = $actualParsedLog.Log $this.ColumnNames = $actualParsedLog.Keys } else { Write-Error -Message "Path was not reachable. Please verify your Path: $($this.LogFilePath)." } } # Returns the column Keys [string[]] GetColumnNames() { return $this.ColumnNames } # Returns lines with errors [int[]] GetLinesWithErrors() { $LinesWithErrors = ($this.ParsedLogData).Where{ $_.Entry -like '*error*' } return $LinesWithErrors } # Returns lines with errors [int[]] GetLinesWithErrorsHeuristic() { $LinesWithErrors = (($this.ParsedLogData).Where{ $_.Entry -like '*error*' }).RowNum $RowList = Get-RowNumbersInRange $LinesWithErrors $ShowingRows = ($this.ParsedLogData).Where{ $_.RowNum -in $RowList } return $LinesWithErrors } # Returns lines with errors # Overload with Range [int[]] GetLinesWithErrorsHeuristic([int]$Range) { $LinesWithErrors = (($this.ParsedLogData).Where{ $_.Entry -like '*error*' }).RowNum $RowList = Get-RowNumbersInRange $LinesWithErrors -Range $Range $ShowingRows = ($this.ParsedLogData).Where{ $_.RowNum -in $RowList } return $LinesWithErrors } # Returns lines with errors [int[]] GetRowNumbersWithErrors() { $LinesWithErrors = (($this.ParsedLogData).Where{ $_.Entry -like '*error*' }).RowNum return $LinesWithErrors } #endregion } function Get-RowNumbersInRange { <# .Synopsis Get-RowNumbersInRange .DESCRIPTION Heuristic method, which returns a list of all transmitted rowlines addiing a number of lines n ($Range) before and after. .EXAMPLE $rowsWithErrors = 21,345,456 $allRowsToSHow = Get-RowNumbersInRange -RowNumbers $rowsWithErrors -Range 10 #> [CmdletBinding()] Param ( #Previous calculated set of rowNumbers. [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] $RowNumbers, [Parameter(Mandatory = $false, Position = 1)] [int]$Range = 20 ) Begin { } Process { $allShowingRowNumbers = New-Object -TypeName System.Collections.Generic.List``1[Int] foreach ($rowNum in $RowNumbers) { $min = $rowNum - $Range $max = $rowNum + $Range for ($x = $min; $x -lt $max; $x += 1) { $allShowingRowNumbers.Add($x) } } $allShowingRowNumbers } End { } } function Get-LogFileType { <# .Synopsis Get-LogFileType .DESCRIPTION Returns the type of the transmitted LogFile. .EXAMPLE $LogFileType = Get-LogFileType -LogFileName 'dism.log' #> [CmdletBinding()] Param ( #Name of the logFile [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] $LogFileName ) Begin { } Process { switch ($LogFileName) { #DISM { $_ -like 'dism*' } { [LogFileTypes]::DISM break } #Upgrade { $_ -like 'setupact*' } { [LogFileTypes]::Upgrade break } { $_ -like 'setuperr*' } { [LogFileTypes]::Upgrade break } #CBS { $_ -like 'cbs*' } { [LogFileTypes]::CBS break } #SCCM default { [LogFileTypes]::SCCM } } } End { } } function Get-RegExParsedLogfile { <# .SYNOPSIS Returns a ordered hashtable list for a log by using Regex. .DESCRIPTION The Regular Expression splits a single line of the log file into named keys. This is used for a whole log file and a ordered hashtable list is returned. .EXAMPLE $parsedLogFile = Get-RegExParsedLogfile -Path 'c:\windows\CCM\ccmexec.log' -LogFileType SCCM | Out-GridView .EXAMPLE Get-RegExParsedLogfile -Path 'c:\windows\logs\cbs\cbs.log' -LogFileType CBS | Out-GridView .EXAMPLE cls $parsedLogFile = Get-RegExParsedLogfile -Path 'c:\windows\logs\cbs\cbs.log' $parsedLogFile.Log.Line | Where-Object { $_ -like '*error*' } The Logfile is written into the hastable with the integrated key "Line". You can filter these with where. .EXAMPLE $rx = '(?<Date>\d{4}-\d{2}-\d{2})\s+(?<Time>(\d{2}:)+\d{2}),\s+(?<Type>\w+)\s+(?<Component>\w+)\s+(?<Message>.*)$' $parsedLogFile = Get-RegExParsedLogfile -Path 'c:\windows\logs\cbs\cbs.log' -RegexString $rx $parsedLogFile.Keys .EXAMPLE $rx='<!\[LOG\[(?<Entry>.*)]LOG]!><time="(?<Time>.*)\.\d{3}-\d{3}"\s+date="(?<Date>.*)"\s+component="(?<Component>.*)"\s+context="(?<Context>.*)"\s+type="(?<Type>.*)"\s+thread="(?<Thread>.*)"\s+file="(?<File>.*):(?<CodeLine>\d*)">' $parsedLogFile = Get-RegExParsedLogfile -Path 'c:\windows\CCM\ccmexec.log' -RegexString $rx #> [CmdletBinding()] param ( #Contains the log file destination. [Parameter(Mandatory = $true, Position = 0)] [System.String] $Path = 'c:\windows\logs\cbs\cbs.log', #Contains the RegEx with named keys [Parameter(Mandatory = $false, Position = 1)] [System.String] $RegexString = '(?<Line>.*)$', #ValidateSet of the differenct preconfigured LogFileTypes [Parameter(Mandatory = $false, Position = 2)] [LogFileTypes]$LogFileType = 'SCCM', #Filter [Parameter(Mandatory = $false, Position = 3)] [System.String] $GatherOnlyLinesWhichContain = '' ) $t = (Get-Content -Path $Path -ReadCount 1000).Split([System.Environment]::NewLine) if ($GatherOnlyLineWhichContain) { $t = $t| Select-String $GatherOnlyLinesWhichContain } [regex]$rx = $RegexString # for each LogFileType a different Regex-String is used to parse the log. switch ($LogFileType) { 'SCCM' { $rx = '<!\[LOG\[(?<Entry>.*)]LOG]!><time="(?<Time>.*)\.\d{3}-\d{3}"\s+date="(?<Date>.*)"\s+component="(?<Component>.*)"\s+context="(?<Context>.*)"\s+type="(?<Type>.*)"\s+thread="(?<Thread>.*)"\s+file="(?<File>.*):(?<CodeLine>\d*)">' break } 'CBS' { $rx = '(?<Date>\d{4}-\d{2}-\d{2})\s+(?<Time>(\d{2}:)+\d{2}),\s+(?<Type>\w+)\s+(?<Component>\w+)\s+(?<Message>.*)$' break } 'Upgrade' { $rx = '(?<Date>\d{4}-\d{2}-\d{2})\s+(?<Time>(\d{2}:)+\d{2}),\s+(?<Type>\w+)\s{1,17}(\[(?<ErrorCode>\w*)\])?(?<Component>\s\w+)?\s+(?<Message>.*)' break } 'DISM' { $rx = '(?<Date>\d{4}-\d{2}-\d{2})\s+(?<Time>(\d{2}:)+\d{2}),\s+(?<Type>\w+)\s{1,18}(?<Component>\w+)?\s+(?<Message>.*)' break } default { Write-Error -Message 'Not Type has been set or found.' } } [string[]]$names = 'RowNum' $names += $rx.GetGroupNames() | Where-Object -FilterScript { $_ -match '\w{2}' } [long]$rowNum = 0 $data = $t | ForEach-Object -Process { $rx.Matches($_) | ForEach-Object -Process { $match = $_ $names | ForEach-Object -Begin { $hash = [Ordered]@{} # $thisDate = $null } -Process { if ($_ -eq 'RowNum') { $rowNum += 1 $hash.Add($_, $rowNum) } elseif ($_ -eq 'Thread') { $hash.Add($_, [int]($match.groups["$_"].Value)) } else { $hash.Add($_, $match.groups["$_"].Value) } } -End { $thisDate=[datetime]($hash.Date + ' ' + $hash.Time) $hash.Add('DateTime', $thisDate) [PSCustomObject]$hash } } } $object = New-Object -TypeName PSObject $object | Add-Member -MemberType NoteProperty -Name Keys -Value $names $object | Add-Member -MemberType NoteProperty -Name Log -Value $data $object }
I hope, you like it.
Best regards,
David