Clean up Exchange log files with PowerShell

Sometimes the Exchange log and trace files can take up a lot of disk space, which is why I get requests from time to time asking how the logs can be cleaned up. In most cases, the Exchange partition threatens to fill up and in many cases this is also the system partition. Unfortunately, in many cases the system partition, on which Exchange is often installed, is much too small. We recommend 200 GB for Exchange alone, and if you add the operating system including some air (100 GB) and the static swap file (32 GB), you end up with a good 330 GB. A system partition with less than 100 GB is therefore simply too small in the long term.

Exchange cleans up most of its log files automatically and deletes older log files after a certain period of time. Unfortunately, this does not apply to all log files and some unnecessary files. For example, the IIS logs and the files in the "UnifiedContent" folder are not automatically deleted/cleaned.

If you run out of memory, you can first clean up the logs to give you some breathing space again. In most cases, I have always used the Script by Ali Tajran recommended. In Ali's script, only the paths need to be adapted to your own environment, after which the log files are deleted during a run.

I also used Ali's script as the basis for the following script and adapted and extended it slightly. Essentially, the "UnifiedContent (\TransportRoles\data\Temp\UnifiedContent)" folder is now also cleaned up and the script provides a small output showing how much storage space is released. To be on the safe side, there is also a small query as to whether the data should really be deleted.

The customized script can be found here:

# 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 really 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"
}
Clean up Exchange log files with PowerShell

Depending on the number of logs, the script needs some time to delete the files, so just wait until the script has run through. The script can also be found on GitHub and is also updated there as required.

17 thoughts on “Exchange Logfiles mit PowerShell bereinigen”

  1. DANKE, wie immer SPITZEN Hilfe! Mach bitte weiter so. :) Dein Blog hat mir schon manches Mal geholfen. :)

    Reply
  2. Danke Franky wieder ein toller Tip und mal eben 250 GB LogFiles bereinigt da meine Vorgänger nie etwas geändert haben

    Reply
  3. 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

    Reply
  4. Hallo,

    kann ich den Pfad: „X:\Microsoft\Exchange Server\V15\TransportRoles\Logs“ auch gefahrlos bereinigen?

    Gruss P.M.

    Reply
  5. 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.

    Reply
  6. 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.

    Reply
    • 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.

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

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

      Reply
  7. 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.

    Reply
  8. 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“

    Reply
  9. 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

    Reply

Leave a Comment