KIExchange nach dem Oktober-Patchday: Zähler überwachen statt Ausfall abwarten
Greift der Exchange-Workaround wirklich? Unser kostenloses PowerShell-Skript liest den RPC-Zähler aus und warnt per Mail vor dem nächsten Ausfall.
- Monitoring
- Updates
Seit dem Oktober-Patchday fallen Exchange-Server nach rund einem Tag aus, weil die Windows-Updates im RPC Endpoint Mapper eine neue Grenze von 512 gespeicherten Kontexten eingeführt haben. Microsoft empfiehlt als Workaround den Registry-Wert EpmapSavedContextCap. Die Hintergründe stehen in unserem Beitrag zu den Exchange-Ausfällen nach dem Oktober-Patchday. Offen bleiben zwei Fragen, die Microsoft nicht beantwortet: Wie voll ist der Zähler gerade? Und greift der Registry-Wert auf diesem Server wirklich? Dafür haben wir ein kleines Überwachungsskript gebaut, das wir hier kostenlos zur Verfügung stellen.
Gesetzt heißt nicht wirksam
Aus der PraxisBei einem von uns betreuten Exchange-Server war der Registry-Wert korrekt als DWORD 65536 gesetzt. Im Speicher des Endpoint Mappers stand trotzdem weiterhin 512 – der Wert war gesetzt worden, ohne dass der Server danach neu gestartet wurde. Der Zähler lag zu diesem Zeitpunkt bei 47. Ohne Blick in den Speicher wäre das erst am nächsten Morgen beim erneuten Ausfall aufgefallen. Genau diesen Fall erkennt das Skript jetzt automatisch.
Was hinter der Grenze steckt
Exchange legt im Endpoint Mapper laufend Suchkontexte an und gibt sie erst frei, wenn der zugehörige Prozess endet. Seit dem Oktober-Update zählt Windows diese Kontexte mit und verweigert ab 512 neue RPC-Verbindungen. Der Registry-Wert hebt diese Grenze an, wird aber nur beim Systemstart eingelesen. Denis Grebner hat in einem Kommentar in Borns IT- und Windows-Blog beschrieben, an welchen Stellen im Speicher Grenze und Zähler unter Windows Server 2025 liegen. Auf dieser Grundlage liest unser Skript beide Werte aus – ausschließlich lesend, ohne etwas am System zu verändern.
512
18 bis 24
rund 21 Stunden
Was das Skript macht
Funktionen im Überblick
Zähler und Grenze
Liest unter Windows Server 2025 die aktuelle Grenze und den Zählerstand des Endpoint Mappers aus.
Workaround-Prüfung
Meldet, wenn der Registry-Wert gesetzt ist, im Speicher aber noch 512 gilt – also ein Neustart aussteht.
Funktionstest
Prüft alle 15 Minuten die echte MAPI-Verbindung, die Warteschlangen und das Ereignis 1006 mit MdbOffline.
Warnmail
Schickt eine Warnmail, sobald der Zähler 90 Prozent der Grenze erreicht – vor dem Ausfall, nicht danach.
Protokoll und Ereignisse
Schreibt jeden Lauf in eine CSV-Datei und Warnungen ins Anwendungsprotokoll, damit vorhandenes Monitoring sie aufgreifen kann.
Einrichtung in Minuten
Ein Installer prüft die Voraussetzungen, fragt die Zieladresse ab und richtet alles ein. Ebenso einfach lässt es sich wieder entfernen.
Was das Skript auf welchem Server kann
Windows Server 2025 mit Oktober-Update
Zähler und Grenze werden ausgelesen, der Workaround wird geprüft, alle Funktionstests laufen.
Volle Überwachung inklusive Warnmail vor dem Ausfall.
Windows Server 2019 und 2022
Für diese Versionen sind die Speicheradressen noch nicht bekannt. MAPI-Test, Warteschlangen und Ereignis 1006 laufen trotzdem.
Überwachung meldet den Ausfall sofort, warnt aber nicht vorher.
Oktober-Update noch nicht aktiv
Ist das Oktober-Update noch nicht installiert oder wartet auf den Neustart, gibt es die Grenze noch nicht. Das Skript sagt das ausdrücklich.
Nach dem Neustart füllen sich die Spalten unter Server 2025 automatisch.
Installation in drei Schritten
- Skript speichern: Den vollständigen Code am Ende dieses Beitrags kopieren und auf dem Exchange-Server als Install-ExRpcWatch.ps1 speichern, zum Beispiel unter C:\Temp. Das Skript enthält bewusst keine Umlaute, damit es unabhängig von der Zeichenkodierung läuft.
- Als Administrator ausführen: In Windows PowerShell 5.1 (nicht PowerShell 7) mit den Befehlen unten. Der Installer prüft Administratorrechte und Exchange-Umgebung, liest den Servernamen aus und fragt nach der Adresse für Warnmails.
- Status und Testmail prüfen: Zum Schluss zeigt der Installer die aktuellen Werte an und verschickt auf Wunsch eine Testmail. Ab dann läuft die Überwachung alle 15 Minuten als geplante Aufgabe.
Befehle
Alle Befehle in einer Windows PowerShell mit Administratorrechten auf dem Exchange-Server ausführen.
cd C:\Temp Unblock-File .\Install-ExRpcWatch.ps1 powershell -ExecutionPolicy Bypass -File .\Install-ExRpcWatch.ps1
& 'C:\Scripts\ExRpcWatch\ExRpcWatch.ps1' -Status
& 'C:\Scripts\ExRpcWatch\ExRpcWatch.ps1' -TestMail
powershell -ExecutionPolicy Bypass -File .\Install-ExRpcWatch.ps1 -Uninstall
Optional lassen sich Zieladresse, Pfad, Intervall und Warnschwelle direkt übergeben, etwa mit -MailTo it@ihre-firma.de -InstallPath D:\Tools\ExRpcWatch -ThresholdPercent 80.
Was der Installer anlegt
- Verzeichnis C:\Scripts\ExRpcWatch: das Überwachungsskript ExRpcWatch.ps1, die Einstellungen in config.json sowie nach dem ersten Lauf die Messwerte in ExRpcWatch.csv und das Protokoll ExRpcWatch.log. Beide Dateien werden ab 5 MB automatisch beiseitegelegt.
- Geplante Aufgabe „ExRpcWatch“: läuft unter SYSTEM alle 15 Minuten, mit einem Zeitlimit von 5 Minuten und ohne parallele Läufe. Eine vorhandene Aufgabe gleichen Namens wird ersetzt.
- Ereignisquelle „ExRpcWatch“ im Anwendungsprotokoll für die Ereignisse 9001 und 9002.
- Registry-Wert EpmapSavedContextCap: nur, wenn Sie ausdrücklich zustimmen. Alle anderen Systemeinstellungen bleiben unverändert.
Was in der Protokolldatei steht
| Spalte | Bedeutung |
|---|---|
| UptimeStd | Laufzeit seit dem letzten Neustart in Stunden. |
| EpmapGrenze | Die im Speicher tatsächlich gültige Grenze: 512 ohne wirksamen Workaround, sonst der Registry-Wert. |
| EpmapZaehler | Aktuelle Zahl der gespeicherten Kontexte. Steigt gleichmäßig und beginnt nach jedem Neustart bei null. |
| RegWert | Der in der Registry eingetragene Wert. Weicht er von der Grenze im Speicher ab, steht ein Neustart aus. |
| MapiFehler | Zahl der Datenbanken, bei denen der echte MAPI-Verbindungstest fehlschlägt. |
| QueueSumme | Summe aller Nachrichten in den Warteschlangen des Servers. |
| Ereignis1006 | Exchange-Ereignisse mit MdbOffline in den letzten 15 Minuten. |
Ereignisse im Anwendungsprotokoll
| Ereignis-ID | Typ | Wann es geschrieben wird |
|---|---|---|
| 9001 | Fehler | Der Zähler hat die Warnschwelle erreicht, der MAPI-Test schlägt fehl oder Exchange meldet MdbOffline (Ereignis 1006). |
| 9002 | Warnung | Der Registry-Wert ist gesetzt, im Speicher gilt aber noch 512. Ein Neustart des Servers steht aus. |
Quelle ist jeweils „ExRpcWatch“. Beide Ereignisse lassen sich so direkt in ein vorhandenes Monitoring übernehmen.
Auswertung: Was tun, wenn …?
Typische Befunde und die passende Maßnahme
Grenze 512 trotz Registry-Wert
EpmapGrenze steht auf 512, RegWert auf 65536: Der Wert ist gesetzt, aber noch nicht aktiv.
Server in einem passenden Fenster neu starten. Danach muss EpmapGrenze 65536 zeigen.
Workaround fehlt
EpmapGrenze steht auf 512, RegWert ist leer: Der Server fällt rund 21 Stunden nach dem Start aus.
Registry-Wert setzen und neu starten oder bis zu einem Microsoft-Fix regelmäßig neu starten.
Zähler nahe der Grenze
Der Zähler hat 90 Prozent der Grenze erreicht. Exchange läuft noch, der Ausfall ist aber absehbar.
Kontrollierten Neustart einplanen, bevor die Grenze erreicht ist.
Exchange gestört
MapiFehler oder Ereignis1006 sind größer als null: Exchange nimmt keine Verbindungen mehr an.
Test-MAPIConnectivity und Warteschlangen prüfen, beim bekannten Fehlerbild den Server neu starten.
Workaround wirkt
EpmapGrenze steht auf 65536, der Zähler steigt langsam, MAPI-Test und Ereignisse sind unauffällig.
Nichts zu tun. Nach einem korrigierten Microsoft-Update den Wert nach Microsofts Angaben zurücknehmen.
Zähler und Grenze leer
Entweder ist das Oktober-Update noch nicht aktiv, oder für die DLL-Version sind keine Speicheradressen bekannt.
Mit dem Schalter -Status den Grund anzeigen lassen.
Häufige Fragen
Ist das Auslesen des Speichers gefährlich?+
Ersetzt das Skript den Workaround von Microsoft?+
Reicht ein Neustart der Exchange-Dienste, damit der Registry-Wert greift?+
Wie verschickt das Skript Mails?+
Warum bleiben Zähler und Grenze auf manchen Servern leer?+
Wie ändere ich Zieladresse oder Warnschwelle nachträglich?+
Der Installer verlangt Windows PowerShell 5.1 – warum?+
Die geplante Aufgabe schreibt keine Zeilen in die CSV-Datei+
MapiFehler meldet Fehler, obwohl Exchange läuft+
Die Testmail kommt nicht an+
Was passiert, wenn Microsoft das Problem behebt?+
Das vollständige Skript
Install-ExRpcWatch.ps1
Installer und Überwachungsskript in einer Datei. Der Installer legt das eigentliche Überwachungsskript selbst an.
<#
=====================================================================
Install-ExRpcWatch.ps1
Ueberwachung fuer Exchange-Server nach den Windows-Oktober-Updates 2026
(Grenze der gespeicherten RPC-Endpoint-Mapper-Kontexte,
Registry-Wert EpmapSavedContextCap)
Das Skript
- prueft die Voraussetzungen (Admin, Windows PowerShell 5.1, Exchange)
- liest den lokalen Servernamen aus
- fragt die Zieladresse fuer Warnmails ab
- legt Verzeichnis, Konfiguration und Ueberwachungsskript an
- richtet eine geplante Aufgabe ein (Standard: alle 15 Minuten)
- bietet optional den Microsoft-Workaround (Registry-Wert) an
- verschickt auf Wunsch eine Testmail
Aufruf (PowerShell als Administrator, NICHT PowerShell 7):
.\Install-ExRpcWatch.ps1
.\Install-ExRpcWatch.ps1 -MailTo it@example.com -InstallPath D:\Tools\ExRpcWatch
.\Install-ExRpcWatch.ps1 -Uninstall
Wichtig:
- Das Ueberwachungsskript liest den Prozessspeicher des Endpoint Mappers
ausschliesslich lesend. Es veraendert nichts am System.
- Zaehler und Grenze werden nur fuer bekannte Versionen von RpcEpMap.dll
ausgelesen (Stand: 10.0.26100.33648, Windows Server 2025). Bei anderen
Versionen laufen MAPI-Test, Warteschlangen- und Ereignispruefung weiter,
die Spalten fuer Zaehler und Grenze bleiben leer.
- Warnmails werden ueber das Pickup-Verzeichnis von Exchange zugestellt.
Es ist kein Relay-Connector und kein Kennwort noetig.
Stand: 19.10.2026 - codekunst systems GmbH
Nutzung auf eigene Verantwortung. Vor dem produktiven Einsatz testen.
=====================================================================
#>
[CmdletBinding()]
param(
[string]$MailTo,
[string]$InstallPath = 'C:\Scripts\ExRpcWatch',
[ValidateRange(5, 60)][int]$IntervalMinutes = 15,
[ValidateRange(50, 99)][int]$ThresholdPercent = 90,
[ValidateRange(1, 72)][int]$TicketLockHours = 12,
[switch]$Uninstall
)
$TaskName = 'ExRpcWatch'
$EventSource = 'ExRpcWatch'
$RegPath = 'HKLM:\SOFTWARE\Microsoft\Rpc'
$RegName = 'EpmapSavedContextCap'
function Write-Step([string]$Text) { Write-Host "==> $Text" -ForegroundColor Cyan }
function Write-Ok([string]$Text) { Write-Host " $Text" -ForegroundColor Green }
function Write-Warn([string]$Text) { Write-Host " $Text" -ForegroundColor Yellow }
function Ask-YesNo([string]$Question, [bool]$Default) {
$hint = if ($Default) { '[J/n]' } else { '[j/N]' }
$a = Read-Host "$Question $hint"
if ([string]::IsNullOrWhiteSpace($a)) { return $Default }
return ($a.Trim().ToLower() -in @('j', 'ja', 'y', 'yes'))
}
# ---------------------------------------------------------------------
# Voraussetzungen
# ---------------------------------------------------------------------
Write-Step 'Pruefe Voraussetzungen'
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) { throw 'Bitte PowerShell als Administrator starten.' }
if ($PSVersionTable.PSVersion.Major -ne 5) {
throw 'Bitte in Windows PowerShell 5.1 ausfuehren (powershell.exe), nicht in PowerShell 7 (pwsh.exe).'
}
if (-not $env:ExchangeInstallPath -or
-not (Get-PSSnapin -Registered -Name 'Microsoft.Exchange.Management.PowerShell.SnapIn' -ErrorAction SilentlyContinue)) {
throw 'Auf diesem Server ist keine Exchange-Verwaltungsumgebung installiert.'
}
Write-Ok 'Administrator, Windows PowerShell 5.1 und Exchange gefunden.'
# ---------------------------------------------------------------------
# Deinstallation
# ---------------------------------------------------------------------
if ($Uninstall) {
Write-Step 'Entferne ExRpcWatch'
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
Write-Ok "Geplante Aufgabe '$TaskName' entfernt."
}
if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) {
Remove-EventLog -Source $EventSource
Write-Ok "Ereignisquelle '$EventSource' entfernt."
}
if ((Test-Path $InstallPath) -and (Ask-YesNo "Verzeichnis $InstallPath inklusive Protokollen loeschen?" $false)) {
Remove-Item $InstallPath -Recurse -Force
Write-Ok "Verzeichnis $InstallPath geloescht."
}
Write-Warn "Der Registry-Wert $RegName wurde NICHT veraendert."
return
}
# ---------------------------------------------------------------------
# Server und Umgebung
# ---------------------------------------------------------------------
$computer = $env:COMPUTERNAME
$os = Get-CimInstance Win32_OperatingSystem
$dll = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:windir\System32\RpcEpMap.dll")
$dllVer = '{0}.{1}.{2}.{3}' -f $dll.FileMajorPart, $dll.FileMinorPart, $dll.FileBuildPart, $dll.FilePrivatePart
Write-Step 'Server'
Write-Ok "Name : $computer"
Write-Ok "Betriebssystem : $($os.Caption) (Build $($os.BuildNumber))"
Write-Ok "RpcEpMap.dll : $dllVer"
if ($dllVer -ne '10.0.26100.33648') {
Write-Warn 'Fuer diese DLL-Version sind keine Speicher-Offsets hinterlegt.'
Write-Warn 'Zaehler und Grenze werden nicht ausgelesen, die uebrigen Pruefungen laufen.'
}
# ---------------------------------------------------------------------
# Zieladresse
# ---------------------------------------------------------------------
Write-Step 'Zieladresse fuer Warnmails'
$mailPattern = '^[^@\s]+@[^@\s]+\.[^@\s]+$'
while (-not ($MailTo -match $mailPattern)) {
if ($MailTo) { Write-Warn "'$MailTo' ist keine gueltige Adresse." }
$MailTo = Read-Host 'Zieladresse (z. B. ticket@ihre-firma.de)'
}
Write-Ok "Warnmails gehen an: $MailTo"
# ---------------------------------------------------------------------
# Verzeichnis und Konfiguration
# ---------------------------------------------------------------------
Write-Step "Lege $InstallPath an"
New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
[pscustomobject]@{
Server = $computer
MailTo = $MailTo
ThresholdPercent = $ThresholdPercent
TicketLockHours = $TicketLockHours
IntervalMinutes = $IntervalMinutes
Installed = (Get-Date -Format s)
} | ConvertTo-Json | Set-Content -Path (Join-Path $InstallPath 'config.json') -Encoding ASCII
Write-Ok 'config.json geschrieben.'
# ---------------------------------------------------------------------
# Ueberwachungsskript
# ---------------------------------------------------------------------
$watch = @'
# =====================================================================
# ExRpcWatch.ps1 - angelegt von Install-ExRpcWatch.ps1
# Einstellungen in config.json im selben Verzeichnis.
#
# .\ExRpcWatch.ps1 normaler Lauf (geplante Aufgabe)
# .\ExRpcWatch.ps1 -Status aktuelle Werte anzeigen, nichts schreiben
# .\ExRpcWatch.ps1 -TestMail Testmail an die Zieladresse senden
#
# Liest den Speicher des Endpoint Mappers ausschliesslich lesend.
# =====================================================================
param([switch]$Status, [switch]$TestMail)
$base = Split-Path -Parent $MyInvocation.MyCommand.Path
$cfg = Get-Content (Join-Path $base 'config.json') -Raw | ConvertFrom-Json
$csv = Join-Path $base 'ExRpcWatch.csv'
$tlog = Join-Path $base 'ExRpcWatch.log'
# Bekannte Speicher-Offsets je DLL-Version (Build.Revision)
$offsets = @{
'26100.33648' = @{ Cap = 0x1C5EC; Cnt = 0x1CD50 }
}
# Protokolle begrenzen
foreach ($f in @($tlog, $csv)) {
if ((Test-Path $f) -and (Get-Item $f).Length -gt 5MB) { Move-Item $f "$f.old" -Force }
}
if (-not $Status) { Start-Transcript -Path $tlog -Append | Out-Null }
try {
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction Stop
if (-not ('EpMem' -as [type])) {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class EpMem {
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenProcess(uint access, bool inherit, int procId);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool ReadProcessMemory(IntPtr h, IntPtr addr, byte[] buf, int size, out IntPtr read);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr h);
}
"@
}
# --- Grunddaten ---
$svc = Get-CimInstance Win32_Service -Filter "Name='RpcEptMapper'"
$os = Get-CimInstance Win32_OperatingSystem
$uptimeMin = ((Get-Date) - $os.LastBootUpTime).TotalMinutes
$regVal = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Rpc' -Name EpmapSavedContextCap -ErrorAction SilentlyContinue).EpmapSavedContextCap
# --- MAPI-Test (sprachunabhaengig) ---
$mapi = Test-MAPIConnectivity -Server $env:COMPUTERNAME -ErrorAction SilentlyContinue
$mapiFail = $null
if ($mapi) {
$mapiFail = @($mapi | Where-Object {
-not [string]::IsNullOrWhiteSpace("$($_.Error)") -or
@('Success', 'Erfolgreich') -notcontains "$($_.Result)"
}).Count
}
# --- Warteschlangen ---
$queue = (Get-Queue -Server $env:COMPUTERNAME -ErrorAction SilentlyContinue | Measure-Object MessageCount -Sum).Sum
# --- Ereignis 1006 (nur Exchange + MdbOffline, nicht in den ersten 30 Min.) ---
$ev1006 = 0
if ($uptimeMin -gt 30) {
$ev1006 = @(Get-WinEvent -FilterHashtable @{ LogName = 'Application'; Id = 1006; StartTime = (Get-Date).AddMinutes(-15) } -ErrorAction SilentlyContinue |
Where-Object { $_.ProviderName -like 'MSExchange*' -and $_.Message -like '*MdbOffline*' }).Count
}
# --- Grenze und Zaehler aus dem Speicher (nur bekannte DLL-Versionen) ---
$cap = $null; $cnt = $null; $grund = $null
try {
$v = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:windir\System32\RpcEpMap.dll")
$ver = "$($v.FileBuildPart).$($v.FilePrivatePart)"
if (-not $offsets.ContainsKey($ver)) {
if ($v.FileBuildPart -eq 26100 -and $v.FilePrivatePart -lt 33648) {
$grund = "RpcEpMap.dll $ver ist aelter als das Oktober-Update 2026 - Update nicht installiert oder Neustart ausstehend. Die Grenze von 512 existiert hier noch nicht."
} else {
$grund = "Fuer RpcEpMap.dll $ver sind keine Speicher-Offsets hinterlegt (bisher nur Windows Server 2025, 10.0.26100.33648). MAPI-Test, Warteschlangen und Ereignisse werden trotzdem geprueft."
}
}
else {
$mod = (Get-Process -Id $svc.ProcessId).Modules | Where-Object { $_.ModuleName -eq 'RpcEpMap.dll' }
$h = [EpMem]::OpenProcess(0x0410, $false, $svc.ProcessId)
if ($h -ne [IntPtr]::Zero -and $mod) {
$rd = {
param($off)
$b = New-Object byte[] 4; $r = [IntPtr]::Zero
[void][EpMem]::ReadProcessMemory($h, [IntPtr]($mod.BaseAddress.ToInt64() + $off), $b, 4, [ref]$r)
[BitConverter]::ToUInt32($b, 0)
}
$c = & $rd $offsets[$ver].Cap
$n = & $rd $offsets[$ver].Cnt
[void][EpMem]::CloseHandle($h)
# Plausibilitaet: Grenze muss 512 (Standard) oder der Registry-Wert sein
if ($c -eq 512 -or ($regVal -and $c -eq [uint32]$regVal)) { $cap = $c; $cnt = $n }
else { $grund = "Unplausible Grenze im Speicher ($c) - Werte verworfen." }
}
}
} catch { $grund = "Speicher nicht lesbar: $($_.Exception.Message)" }
if ($grund -and -not $Status) { Write-Output "Zaehler/Grenze leer: $grund" }
$regAusstehend = ($regVal -and $cap -eq 512 -and [uint32]$regVal -ne 512)
$zeile = [pscustomobject]@{
Zeit = Get-Date -Format s
Server = $env:COMPUTERNAME
UptimeStd = [math]::Round($uptimeMin / 60, 1)
EpmapGrenze = $cap
EpmapZaehler = $cnt
RegWert = $regVal
MapiFehler = $mapiFail
QueueSumme = $queue
Ereignis1006 = $ev1006
}
if ($Status) {
$zeile | Format-List
if ($grund) { Write-Output "Hinweis zu Zaehler/Grenze: $grund" }
if ($regAusstehend) { Write-Output 'Hinweis: Registry-Wert gesetzt, aber noch nicht aktiv - Neustart erforderlich.' }
return
}
$zeile | Export-Csv $csv -Append -NoTypeInformation -Delimiter ';' -Encoding UTF8
# --- Bewertung ---
$schwelle = [double]$cfg.ThresholdPercent / 100
$kritisch = ($cnt -ne $null -and $cap -gt 0 -and $cnt -gt ($schwelle * $cap))
$ausfall = (($mapiFail -gt 0) -or ($ev1006 -gt 0))
if (-not [System.Diagnostics.EventLog]::SourceExists('ExRpcWatch')) {
New-EventLog -LogName Application -Source 'ExRpcWatch'
}
$info = "MAPI-Fehler: $mapiFail, Ereignis 1006 (MdbOffline): $ev1006, Queue: $queue, " +
"Uptime: $($zeile.UptimeStd) h, Zaehler: $cnt / Grenze: $cap, Registry: $regVal"
if ($kritisch -or $ausfall) {
Write-EventLog -LogName Application -Source 'ExRpcWatch' -EventId 9001 -EntryType Error -Message "ExRpcWatch: $info"
} elseif ($regAusstehend) {
Write-EventLog -LogName Application -Source 'ExRpcWatch' -EventId 9002 -EntryType Warning `
-Message "ExRpcWatch: Registry-Wert $regVal gesetzt, im Speicher gilt noch 512. Neustart erforderlich. $info"
}
# --- Mail ueber das Pickup-Verzeichnis ---
function Send-ExTicket([string]$Art, [string]$Betreff, [string[]]$Text, [switch]$Force) {
$marker = Join-Path $base "ticket_$Art.marker"
if (-not $Force -and (Test-Path $marker)) {
$z = (Get-Item $marker).LastWriteTime
if ($z -gt $os.LastBootUpTime -and $z -gt (Get-Date).AddHours(-[int]$cfg.TicketLockHours)) { return }
}
try {
$pickup = "$((Get-TransportService -Identity $env:COMPUTERNAME).PickupDirectoryPath)"
if (-not $pickup) { $pickup = Join-Path $env:ExchangeInstallPath 'TransportRoles\Pickup' }
$domain = "$((Get-AcceptedDomain | Where-Object { $_.Default }).DomainName)"
$mail = @(
"From: ExRpcWatch <exrpcwatch@$domain>",
"To: $($cfg.MailTo)",
"Subject: $Betreff",
"Date: $((Get-Date).ToUniversalTime().ToString('r'))",
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=us-ascii',
'X-ExRpcWatch: 1',
''
) + $Text
$datei = Join-Path $pickup ("ExRpcWatch_{0}_{1}.eml" -f $Art, (Get-Date -Format 'yyyyMMdd_HHmmss'))
[System.IO.File]::WriteAllText($datei, ($mail -join "`r`n"), [System.Text.Encoding]::ASCII)
if (-not $Force) { Set-Content -Path $marker -Value (Get-Date -Format s) }
Write-Output "Mail abgelegt: $datei"
} catch { Write-Output "Mail fehlgeschlagen: $($_.Exception.Message)" }
}
$details = @(
"Server : $env:COMPUTERNAME",
"Zeit : $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')",
"Laufzeit : $($zeile.UptimeStd) Stunden seit $($os.LastBootUpTime.ToString('dd.MM.yyyy HH:mm'))",
"Epmap-Zaehler : $cnt",
"Epmap-Grenze : $cap",
"Registry-Wert : $regVal",
"MAPI-Fehler : $mapiFail",
"Queue : $queue",
"Ereignis 1006 : $ev1006",
'',
'Automatisch erzeugt von ExRpcWatch.ps1'
)
if ($TestMail) {
Send-ExTicket -Art 'test' -Force -Betreff "[ExRpcWatch] TEST $env:COMPUTERNAME - Testmail" `
-Text (@('Dies ist eine Testmail von ExRpcWatch.', '') + $details)
}
if ($kritisch) {
Send-ExTicket -Art 'kritisch' -Betreff "[ExRpcWatch] $env:COMPUTERNAME - RPC-Zaehler kritisch ($cnt von $cap)" `
-Text (@('Der Zaehler der gespeicherten RPC-Endpoint-Mapper-Kontexte hat die Warnschwelle erreicht.',
'Bei Erreichen der Grenze faellt Exchange aus (MapiExceptionMdbOffline).',
'Massnahme: kontrollierten Neustart einplanen, EpmapSavedContextCap pruefen.', '') + $details)
}
if ($ausfall) {
Send-ExTicket -Art 'ausfall' -Betreff "[ExRpcWatch] $env:COMPUTERNAME - Exchange-Stoerung (MdbOffline/MAPI)" `
-Text (@('Exchange meldet MAPI-Fehler oder MdbOffline-Ereignisse.',
'Hinweis: Bei einem Ausfall kann diese Mail verzoegert zugestellt werden.', '') + $details)
}
}
catch { Write-Output "FEHLER: $($_.Exception.Message)" }
finally { if (-not $Status) { Stop-Transcript | Out-Null } }
'@
$watchPath = Join-Path $InstallPath 'ExRpcWatch.ps1'
Set-Content -Path $watchPath -Value $watch -Encoding ASCII
Write-Ok "Ueberwachungsskript angelegt: $watchPath"
# ---------------------------------------------------------------------
# Ereignisquelle
# ---------------------------------------------------------------------
if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) {
New-EventLog -LogName Application -Source $EventSource
Write-Ok "Ereignisquelle '$EventSource' registriert."
}
# ---------------------------------------------------------------------
# Geplante Aufgabe
# ---------------------------------------------------------------------
Write-Step "Richte geplante Aufgabe '$TaskName' ein (alle $IntervalMinutes Minuten)"
$psExe = "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe"
$action = New-ScheduledTaskAction -Execute $psExe -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$watchPath`""
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) -MultipleInstances IgnoreNew -StartWhenAvailable
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null
Write-Ok 'Geplante Aufgabe eingerichtet (Konto SYSTEM, Zeitlimit 5 Minuten).'
# ---------------------------------------------------------------------
# Registry-Workaround (optional)
# ---------------------------------------------------------------------
Write-Step 'Microsoft-Workaround EpmapSavedContextCap'
$regVal = (Get-ItemProperty $RegPath -Name $RegName -ErrorAction SilentlyContinue).$RegName
if ($regVal) {
Write-Ok "Bereits gesetzt: $regVal"
} else {
Write-Warn 'Nicht gesetzt. Microsoft empfiehlt den Wert 65536 (wirksam erst nach einem Neustart).'
if (Ask-YesNo 'Jetzt auf 65536 setzen?' $false) {
New-ItemProperty -Path $RegPath -Name $RegName -PropertyType DWord -Value 65536 -Force | Out-Null
Write-Ok 'Gesetzt. Bitte den Server in einem passenden Fenster neu starten.'
}
}
# ---------------------------------------------------------------------
# Erster Lauf und Testmail
# ---------------------------------------------------------------------
Write-Step 'Aktueller Status'
& $watchPath -Status
if (Ask-YesNo "Testmail an $MailTo senden?" $true) {
& $watchPath -TestMail | Out-Null
Write-Ok 'Testmail im Pickup-Verzeichnis abgelegt. Bitte Eingang pruefen.'
}
Write-Step 'Fertig'
Write-Ok "Protokolle : $InstallPath\ExRpcWatch.csv und ExRpcWatch.log"
Write-Ok "Status : & '$watchPath' -Status"
Write-Ok "Ereignisse : Anwendungsprotokoll, Quelle $EventSource, ID 9001 (Fehler) und 9002 (Neustart ausstehend)"
Write-Ok "Entfernen : .\Install-ExRpcWatch.ps1 -Uninstall -InstallPath '$InstallPath'"Den gesamten Inhalt mit der Kopieren-Schaltfläche übernehmen, in einem Editor einfügen und als Install-ExRpcWatch.ps1 speichern. Das Skript enthält nur ASCII-Zeichen.
Bitte beachten
Das Skript ist ein Hilfsmittel für Administratoren und keine offizielle Lösung von Microsoft. Die Speicheradressen stammen aus einer unabhängigen Analyse und können sich mit jedem Update ändern. Bitte vor dem produktiven Einsatz testen. Die Nutzung erfolgt auf eigene Verantwortung. Wenn Sie Unterstützung bei Einrichtung oder Auswertung brauchen, sprechen Sie uns gern an.
Quellen & weiterführende Links
- Borns IT- und Windows-BlogAchtung: Exchange Server Okt. 2026-SU führen zu Ausfällen (mit Analyse von Denis Grebner in den Kommentaren)
- Borns IT- und Windows-BlogMicrosoft Windows/Exchange Server-Probleme durch Oktober 2026-Updates analysiert (17.10.2026)
- Frankys WebExchange Server SE: Probleme mit den Oktober-Updates für Windows Server
- Microsoft Exchange TeamExchange servers may become unstable after installation of October 2026 Windows updates
Stand: 19. Oktober 2026. Unser Dank geht an Denis Grebner für die Analyse von RpcEpMap.dll.
