Veeam Log File Analyse mit PowerShell

Vor ein paar Wochen habe ich ein Update der Veeam Backup & Replication Umgebung in meinem Lab durchgeführt. Um nach dem Update sicherzustellen, dass auch wirklich  alles ohne Probleme funktioniert hat, habe ich begonnen alle Windows Eventlogs und Log Files zu analysieren. Schnell hat sicher aber herausgestellt, ohne ein passendes Tools ist die Auswertung der Veeam log Files viel zu aufwendig. Da mein letztes öffentliches PowerShell Projekt bereits ein paar Monate zurück lag (und ich unbedingt einen Grund gesucht habe mit PowerShell Klassen zu arbeiten), habe ich meine Funktion zur Veeam Log File Analyse mit PowerShell begonnen.

Veeam Log File Analyse mit PowerShell - Invoke-VeeamLogParser -LogType PowerShell

Veeam Log File Analyse

Alle Veeam log files sind in der Standardkonfiguration unter ‚C:\ProgramData\Veeam\<Produkt>‘ abgelegt. Die meisten relevanten Veeam Services bzw. Produkte legen dort einen Unterordner an und erzeugen meist mehrere Log Files wie zum Beispiel ‚Svc.VeeamEndpointBackup.log‘ (Veeam Agent for Microsoft Windows).

Die meisten von mir analysierten Log Files halten sich an folgendes Schema für den Hauptteil:

[23.11.2016 23:07:16] <01> Info     ------- Veeam Endpoint Job Started -------

Die Analyse mit PowerShell

PowerShell ist ein super Hilfswerkzeug um schnell Daten aus Test Files zu extrahieren. Besonders gut funktioniert das, wenn die gewünschten Informationen in bestimmten Schemen (RegEx Pattern) vorliegen:

$Content = Get-Content -Path "C:\ProgramData\Veeam\Endpoint\Svc.VeeamEndpointBackup.log"
$Content | Select-String -Pattern "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sError"

Diese zwei kurzen Zeilen geben alle Error Meldungen aus dem Veeam Agent for Microsoft Windows Log File aus.

Diese Befehle aber nun für jedes einzelne Veeam Log File durchzugehen wäre nicht sehr komfortabel, daher habe ich beschlossen ein PowerShell Modul mit einer Funktion zur Veeam Log File Analyse zu erstellen. Die Funktion sollte als Input nur das gewünschte Log File, welches analysiert werden soll, und ein paar Ausgabeoptionen haben.

Dieses Beispiel zeigt die Ausgabe aller in der Funktion vorgesehener Log Files. Die Darstellung ist limitiert auf die letzte Error oder Warning Meldung pro Log File:

Veeam Log File Analyse mit PowerShell - Invoke-VeeamLogParser -LogType All

Wenn der optionale Parameter -Context 2  genutzt wird, werden alle Meldungen im Kontext ausgegeben. Die Meldungen selbst sind in der Ausgabe mit einem > gekennzeichnet:

Veeam log file analysis with PowerShell - Invoke-VeeamLogParser -LogType PowerShell

Hauptfunktion des PowerShell Moduls

function Invoke-VeeamLogParser {
<#
    .DESCRIPTION
       The Veeam Log Parser Function extracts Error and Warning Messages from the Veeam File Logs of various products and services.

    .NOTES
        File Name  : Invoke-VeeamLogParser.psm1
        Author     : Markus Kraus
        Version    : 1.0
        State      : Ready

    .LINK
        https://mycloudrevolution.com/

    .EXAMPLE
        Invoke-VeeamLogParser -LogType Endpoint -Limit 2

    .EXAMPLE
        Invoke-VeeamLogParser -LogType Endpoint -Limit 2 -Context 2

    .PARAMETER VeeamBasePath
        The Base Path of the Veeam Log Files

        Default: "C:\ProgramData\Veeam\"

    .PARAMETER Context
        Show messages in Context

    .PARAMETER Limit
        Show limited number of messages

    .PARAMETER LogType
        The products or services Log you want to show

        Valid Pattern:  "All","Endpoint","Mount","Backup","EnterpriseServer","Broker","Catalog","RestAPI","BackupManager",
                        "CatalogReplication","DatabaseMaintenance","WebApp","PowerShell"
    #>

[CmdletBinding()]
param(
    [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="The Base Path of the Veeam Log Files")]
    [ValidateNotNullorEmpty()]
        [String] $VeeamBasePath = "C:\ProgramData\Veeam\",
    [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Show messages in Context")]
    [ValidateNotNullorEmpty()]
        [int]$Context,
    [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Show limited number of messages")]
    [ValidateNotNullorEmpty()]
        [Int]$Limit,
    [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="The products or services Log you want to show")]
    [ValidateNotNullorEmpty()]
    [ValidateSet("All","Endpoint","Mount","Backup","EnterpriseServer","Broker","Catalog","RestAPI","BackupManager",
    "CatalogReplication","DatabaseMaintenance","WebApp","PowerShell")]
        [String]$LogType
)

Begin {

    class LogParser {
        #Properties
        [String]$Name
        [String]$BasePath
        [String]$Folder
        [String]$File

        #Static
        Static [String] $WarningPattern = "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sWarning"
        Static [String] $ErrorPattern = "\[\d+.\d+.\d+\s\d+\:\d+:\d+]\s\<\d+\>\sError"

        #Constructor
        LogParser ([String] $Name, [String]$BasePath, [String]$Folder, [String]$File) {
            $this.Name = $Name
            $this.BasePath = $BasePath
            $this.Folder = $Folder
            $this.File = $File
        }

        #Method
        [Bool]checkFolder() {
            return Test-Path $($this.BasePath + $this.Folder)
        }

        #Method
        [Bool]checkFile() {
            return Test-Path $($this.BasePath + $this.Folder + "\" + $this.File)
        }

        #Method
        [Array]getContent() {
            if ($this.checkFolder()) {
                if ($this.checkFile()) {
                    return Get-Content $($this.BasePath + $this.Folder + "\" + $this.File)
                }
                else {
                    return Write-Warning "File not found"
                }
            }
            else {
                return Write-Warning "Folder not found"
            }
        }

        #Method
        [Array]getErrors() {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::ErrorPattern) -AllMatches

        }

        #Method
        [Array]getErrors([int]$ContentB, [int]$ContentA) {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::ErrorPattern) -AllMatches -Context $ContentB, $ContentA
        }

        #Method
        [Array]getWarnings() {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::WarningPattern) -AllMatches

        }

        #Method
        [Array]getWarnings([int]$ContentB, [int]$ContentA) {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::WarningPattern) -AllMatches -Context $ContentB, $ContentA

        }

        #Method
        [Array]getErrorsAndWarnings() {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::ErrorPattern), $([LogParser]::WarningPattern) -AllMatches

        }

        #Method
        [Array]getErrorsAndWarnings([int]$ContentB, [int]$ContentA) {
            $Content = $this.getContent()
            return $Content | Select-String -Pattern $([LogParser]::ErrorPattern), $([LogParser]::WarningPattern) -AllMatches -Context $ContentB, $ContentA

        }

    }
    function Invoke-Output ($item) {
        if ($Context) {
            $Select = $item.getErrorsAndWarnings($Context, $Context)
        }
        else {
            $Select =  $item.getErrorsAndWarnings()
        }

        if ($Limit) {
            $Select | Select-Object -Last $Limit
        }
        else {
            $Select
        }

    }

    $LogTypes = @()
    $LogTypes += [LogParser]::new("Endpoint", $VeeamBasePath, "Endpoint", "Svc.VeeamEndpointBackup.log")
    $LogTypes += [LogParser]::new("Mount", $VeeamBasePath, "Backup", "Svc.VeeamMount.log")
    $LogTypes += [LogParser]::new("Backup", $VeeamBasePath, "Backup", "Svc.VeeamBackup.log")
    $LogTypes += [LogParser]::new("EnterpriseServer", $VeeamBasePath, "Backup", "Svc.VeeamBES.log")
    $LogTypes += [LogParser]::new("Broker", $VeeamBasePath, "Backup", "Svc.VeeamBroker.log")
    $LogTypes += [LogParser]::new("Catalog", $VeeamBasePath, "Backup", "Svc.VeeamCatalog.log")
    $LogTypes += [LogParser]::new("RestAPI", $VeeamBasePath, "Backup", "Svc.VeeamRestAPI.log")
    $LogTypes += [LogParser]::new("BackupManager", $VeeamBasePath, "Backup", "VeeamBackupManager.log")
    $LogTypes += [LogParser]::new("CatalogReplication", $VeeamBasePath, "Backup", "CatalogReplicationJob.log")
    $LogTypes += [LogParser]::new("DatabaseMaintenance", $VeeamBasePath, "Backup", "Job.DatabaseMaintenance.log")
    $LogTypes += [LogParser]::new("WebApp", $VeeamBasePath, "Backup", "Veeam.WebApp.log")
    $LogTypes += [LogParser]::new("PowerShell", $VeeamBasePath, "Backup", "VeeamPowerShell.log")

}

Process {

    if ($LogType -eq "All") {
        foreach ($item in $LogTypes) {
            Write-Host "`nProcessing '$($item.File)' in '$($item.BasePath + $item.Folder + "\")'" -ForegroundColor Gray
            Invoke-Output $item
        }
    }
    else {
        $item = $LogTypes | Where-Object {$_.Name -eq $LogType }
        if ($item) {
            Write-Host "`nProcessing '$($item.File)' in '$($item.BasePath + $item.Folder + "\")'" -ForegroundColor Gray
            Invoke-Output $item
        }
        else {
            Throw "Internal Error: LogType Missmatch"
        }
    }
}
}

An dem PowerShell Modul mitwirken

Falls bei der Verwendung des Moduls ein Fehler auftaucht oder ein Verbesserungsvorschlag aufkommt freue ich mich über jeden Pull Request und GitHub Issue

Download des PowerShell Moduls

Das PowerShell Modul ist verfügbar auf GitHub und in der PowerShell Gallery:

VeeamLogParser – GitHub VeeamLogParser – PS Gallery

 

 

2 Comments

  1. Falk Dübbert 14. September 2018
    • Markus Kraus 14. September 2018

Leave a Reply