Exchange Logfiles mit PowerShell bereinigen

Mitunter können die Exchange Log und Trace Files einiges an Speicherplatz belegen, ich bekomme daher immer mal wieder Anfragen, wie die Logs bereinigt werden können. In den meisten Fällen droht die Exchange Partition vollzulaufen und in vielen Fällen ist dies auch die Systempartition. Leider ist in vielen Fällen die Systempartition, auf der auch oft Exchange installiert wird, viel zu klein dimensioniert. Allein für Exchange werden 200 GB empfohlen, rechnet man noch das Betriebssystem inkl. etwas Luft (100GB) und die statische Auslagerungsdatei (32GB) dazu, kommt man schon auf gut 330 GB. Eine Systempartition mit unter 100GB ist daher auf Dauer einfach zu klein.

Exchange bereinigt übrigens die meisten seiner Logfiles selbstständig und löscht ältere Logfiles nach einer gewissen Zeit. Dies trifft aber leider nicht auf alle Logfiles und teilweise nicht erforderliche Dateien zu. So werden zum Beispiel die IIS-Logs, sowie die Dateien im Ordner „UnifiedContent“ nicht automatisch gelöscht/bereinigt.

Wenn der Speicherplatz daher mal knapp wird, kann man zunächst die Logs bereinigen um wieder etwas Luft zum Atmen zu haben. Ich habe in den meisten Fällen immer auf das Script von Ali Tajran empfohlen. In Ali’s Script müssen nur die Pfade an die eigene Umgebung angepasst werden, danach werden bei einem Durchlauf die Logfiles gelöscht.

Ali’s Script habe ich auch als Basis für das folgende Script verwendet und es etwas angepasst bzw. erweitert. Im wesentlichen wird nun auch der Ordner „UnifiedContent (\TransportRoles\data\Temp\UnifiedContent)“ bereinigt und das Script liefert eine kleine Ausgabe, wie viel Speicherplatz freigegeben wird. Zur Sicherheit gibt es auch eine kleine Abfrage ob die Daten wirklich gelöscht werden sollen.

Hier findet sich das angepasste Script:

# Cleanup logs older than the set of days in numbers
$Days = 14

# Path of the logs that you like to cleanup
$IISLogPath = "C:\inetpub\logs\LogFiles\"
$ExchangeLoggingPath = "C:\Program Files\Microsoft\Exchange Server\V15\Logging\"
$ETLLoggingPath = "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Search\Ceres\Diagnostics\ETLTraces\"
$ETLLoggingPath2 = "C:\Program Files\Microsoft\Exchange Server\V15\Bin\Search\Ceres\Diagnostics\Logs\"
$UnifiedContentPath = "C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\data\Temp\UnifiedContent"

# Test if evelated Shell
Function Confirm-Administrator {
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() )
    if ($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator )) {
        return $true
    } else {
        return $false
    }
}

if (-not (Confirm-Administrator)) {
	Write-Output $msgNewLine
	Write-Warning "This script needs to be executed in elevated mode. Start the Exchange Management Shell as an Administrator and try again."
	$Error.Clear()
	Start-Sleep -Seconds 2
	exit
}

# Get size of all logfiles
Function Get-LogfileSize ($TargetFolder) {
    if (Test-Path $TargetFolder) {
        $Now = Get-Date
        $LastWrite = $Now.AddDays(-$days)
        $Files = Get-ChildItem $TargetFolder -Recurse | Where-Object { $_.Name -like "*.log" -or $_.Name -like "*.blg" -or $_.Name -like "*.etl" } | Where-Object { $_.lastWriteTime -le "$lastwrite" }
        $SizeGB = ($Files | Measure-Object -Sum Length).Sum / 1GB
        $SizeGBRounded = [math]::Round($SizeGB,2)
        return $SizeGBRounded
    }
    Else {
        Write-Output "The folder $TargetFolder doesn't exist! Check the folder path!"
    }
}

Function Get-UnifiedContentfileSize ($TargetFolder) {
    if (Test-Path $TargetFolder) {
        $Now = Get-Date
        $LastWrite = $Now.AddDays(-$days)
        $Files = Get-ChildItem $TargetFolder -Recurse | Where-Object { $_.lastWriteTime -le "$lastwrite" }
        $SizeGB = ($Files | Measure-Object -Sum Length).Sum / 1GB
        $SizeGBRounded = [math]::Round($SizeGB,2)
        return $SizeGBRounded
    }
    Else {
        Write-Output "The folder $TargetFolder doesn't exist! Check the folder path!"
    }
}

# Remove the logs
Function Remove-Logfiles ($TargetFolder) {
    if (Test-Path $TargetFolder) {
        $Now = Get-Date
        $LastWrite = $Now.AddDays(-$days)
        $Files = Get-ChildItem $TargetFolder -Recurse | Where-Object { $_.Name -like "*.log" -or $_.Name -like "*.blg" -or $_.Name -like "*.etl" } | Where-Object { $_.lastWriteTime -le "$lastwrite" }
        $FileCount = $Files.Count
        $Files | Remove-Item -force -ea 0
        return $FileCount
    }
    Else {
        Write-Output "The folder $TargetFolder doesn't exist! Check the folder path!"
    }
}

Function Remove-UnifiedContent ($TargetFolder) {
    if (Test-Path $TargetFolder) {
        $Now = Get-Date
        $LastWrite = $Now.AddDays(-$days)
        $Files = Get-ChildItem $TargetFolder -Recurse | Where-Object { $_.lastWriteTime -le "$lastwrite" }
        $FileCount = $Files.Count
        $Files | Remove-Item -force -ea 0
        return $FileCount
    }
    Else {
        Write-Output "The folder $TargetFolder doesn't exist! Check the folder path!"
    }
}

# Get logs and traces and write some stats
$IISLogSize = Get-LogfileSize $IISLogPath
$ExchangeLogSize = Get-LogfileSize $ExchangeLoggingPath
$ETL1LogSize = Get-LogfileSize $ETLLoggingPath
$ETL2LogSize = Get-LogfileSize $ETLLoggingPath2
$UnifiedContentSize = Get-UnifiedContentfileSize $UnifiedContentPath

$TotalLogSize = $IISLogSize + $ExchangeLogSize + $ETL1LogSize + $ETL2LogSize + $UnifiedContentSize
write-host "Total Log and Trace File Size is $TotalLogSize GB"

#Ask if script should realy delete the logs
$Confirmation = Read-Host "Delete Exchange Server log and trace files? [y/n]"
while($Confirmation -notmatch "[yYnN]") {
    if ($Confirmation -match "[nN]") {exit}
    $Confirmation = Read-Host "Delete Exchange Server log and trace files? [y/n]"
}

# Delete logs (if confirmed) and write some stats
if ($Confirmation  -match "[yY]") {
    $DeleteIISFiles = Remove-Logfiles $IISLogPath
    $DeleteExchangeLogs = Remove-Logfiles $ExchangeLoggingPath
    $DeleteETL1Logs = Remove-Logfiles $ETLLoggingPath
    $DeleteETL2Logs = Remove-Logfiles $ETLLoggingPath2
    $DeleteUnifiedContent = Remove-UnifiedContent $UnifiedContentPath

    $TotalDeletedFiles = $DeleteIISFiles + $DeleteExchangeLogs + $DeleteETL1Logs + $DeleteETL2Logs + $DeleteUnifiedContent
    write-host "$TotalDeletedFiles files deleted"
}
Exchange Logfiles mit PowerShell bereinigen

Je nach Anzahl der Logs benötigt das Script etwas Zeit um die Dateien zu löschen, daher einfach etwas abwarten bis das Script durchgelaufen ist. Das Script findet sich auch auf GitHub und wird dort auch bei Bedarf aktualisiert.

16 Gedanken zu „Exchange Logfiles mit PowerShell bereinigen“

  1. Scheinbar seit Patchen von Exchange 2019 auf CU1 Nov21SU läuft das Script nicht mehr?

    Test-Path : Das Argument kann nicht an den Parameter „Path“ gebunden werden, da es NULL ist.
    In C:\Scripts\clean_Exchange_logs.ps1:40 Zeichen:15
    + if (Test-Path $TargetFolder) {
    + ~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Test-Path], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.TestPathCom
    mand

    VG, AD

    Antworten
  2. Ich halte das Löschen bzw. das Überschreiben der Logfiles für Kontraproduktiv. Im Falle einer Kompromittierung bietet die Auswertung der hoffentlich längerfristig als lediglich 30 Tage angelegten Logfiles die Möglichkeit, den Angriffsweg nachzuvollziehen, das Schadensausmaß zu verifizieren und die ausgenutzte Lücke zu schließen. Besser als die pauschale Löschung ist die Auslagerung und Archivierung der Logfiles.

    Antworten
  3. Bedanke mich auch für das tolle Skript. :)

    Lässt sich die Abfrage deaktivieren, bzw. direkt mit y bestätigen?
    Dann würde ich es automatisiert in der Aufgabenplanung laufen lassen.

    Antworten
    • Als ich damals in den Untiefen des Netzes einen ähnlichen Artikel zum Eingrenzen der Loggingaktivität des Exch gefunden habe und das dortige Script nicht gut lief, hab ich das angepasst.

      function Remove-ipxExchangeLogfiles {
      param (
      [Parameter(HelpMessage=’Minimales Alter der Logfiles die entfernt werden.‘)]
      [string]$Days=0,
      [Parameter(HelpMessage=’Welcher Logginggrad soll ausgegeben werden.‘)]
      [string]$Loglevel=0,
      [Parameter(HelpMessage=’Grundinstallationspfad des Exchange Servers‘)]
      [string]$Rootpath
      )

      $IISLogPath=“C:\inetpub\logs\LogFiles\“ #.log
      $ExchangeLoggingPath=$Rootpath + „\Logging\“ #.log
      $ETLLoggingPath=$Rootpath + „\Bin\Search\Ceres\Diagnostics\ETLTraces\“ #.etl
      $ETLLoggingPath2=$Rootpath + „\Bin\Search\Ceres\Diagnostics\Logs“ #.log

      Function CleanLogfiles($TargetFolder)
      {
      if (Test-Path $TargetFolder) {
      $LastWrite = (Get-Date).AddDays(-$days)

      if ($Loglevel -ge „1“ ) { Write-Host „Aktuell in: $TargetFolder“ }
      $Files = (Get-ChildItem $TargetFolder -Include *.log,*.etl -Recurse | Where-Object {$_.LastWriteTime -le „$LastWrite“})
      if ($Loglevel -ge „1“ ) { Write-Host „Dateien ermittelt, jetzt wird gelöscht.“ -ForegroundColor Red }
      foreach ($File in $Files) {
      if ($Loglevel -eq „2“) { Write-Host „Lösche: “ $File.Fullname }

      Remove-Item -Path $File.FullName -Force -ErrorAction SilentlyContinue
      }
      }
      Else {
      Write-Host „The folder $TargetFolder doesn’t exist! Check the folder path!“ -ForegroundColor „white“
      }
      }
      CleanLogfiles($IISLogPath)
      CleanLogfiles($ExchangeLoggingPath)
      CleanLogfiles($ETLLoggingPath)
      CleanLogfiles($ETLLoggingPath2)

      }

      Das parametrisierte Cmdlet ist in meinem zentralen Modul eingebettet und wird per profile.ps1 auf allen Rechnern verfügbar gemacht.
      Zusätzlich registriere ich beim PS Start einen Script ordner, in dem liegt dann die ps1 mit dem expliziten Aufruf:

      Remove-ipxExchangeLogfiles -Days 7 -Rootpath „D:\Exchange“

      Und weil alles integriert ist, ruft die Aufgabenplanung das ganze ohne Pfade auf:

      powershell.exe -noninteractive -command

      Das läuft seit etwa 4 Jahren unverändert so.

      Antworten
    • Zeilen 87 bis 94 löschen/auskommentieren.

      Aber verrat mir bitte mal, wie man eine elevated Exchange Management Shell in der Aufgabenplanung startet, danke!

      Antworten
  4. Tolles Script. Danke dafür.

    Ich würde aber den Zeitraum auf mind. 30 Tage setzen. Wenn ich an das Theater dieses Jahr mit den Exchange Hacks denke, da war es gut die Logs längere Zeit zu haben um nach Spuren zu suchen. Gerade das IIS Log.

    Antworten
  5. Hi,

    in der Version auf auf GitHub gibt es die folgenden Zeilen. Wurden die vergessen auszudokumentieren?

    Gruß Hardy

    #temp for testing

    $Days = 1
    $IISLogPath = „D:\IIS-Logs“
    $ExchangeLoggingPath = „D:\Exchange Server\Logging“
    $ETLLoggingPath = „D:\Exchange Server\Bin\Search\Ceres\Diagnostics\ETLTraces“
    $ETLLoggingPath2 = „D:\Exchange Server\Bin\Search\Ceres\Diagnostics\Logs“
    $UnifiedContentPath = „D:\Exchange Server\TransportRoles\data\Temp\UnifiedContent“

    Antworten
  6. Habe ich auch im Einsatz – lasse das 1x im Monat laufen.
    Ali und Frank sind TOP Männer :)

    Ali Tajran hat sie wie du Frank, einen super Blog. Hat mir schon etliche Male aus der Bredouille geholfen….

    LG

    Antworten

Schreibe einen Kommentar