... Langzeitstatistik per Azure-Automation-Runbook.
Eine Transportregel, die Mails bekannter Spam-Domains mit der Aktion DeleteMessage verwirft, arbeitet unsichtbar. Es gibt keine Quarantäne-Benachrichtigung, keinen Eintrag im Postfach und keine Rückmeldung an den Absender.
Das ist gewollt, macht aber jede Frage nach der Wirkung schwer: Wie viele Nachrichten hat die Regel im letzten Quartal geschluckt, welche Domains schicken noch, welche kannst du aus der Bedingung streichen? Get-MailDetailTransportRuleReport beantwortet das nur für die letzten zehn Tage. Danach ist die Detailsicht weg.
Die Datei wächst Woche für Woche und lässt sich in Power BI auswerten. Der Ansatz ist gut, sein Beitrag lässt aber zwei Punkte offen: Seit dem 30. Juli 2026 liefert das Cmdlet ohne -EventType keine Regeldaten mehr, und welche SharePoint-Rechte die Managed Identity braucht, steht nicht darin. Diese Anleitung baut das Runbook mit dem richtigen EventType und mit Schreibzugriff auf genau eine Site.
Warum Message Trace & Traffic-Report nicht reichen
Get-MessageTraceV2 durchsucht 90 Tage, liefert aber höchstens zehn Tage pro Abfrage, maximal 5.000 Ergebnisse und akzeptiert nur 100 Abfragen in fünf Minuten. Für die Frage „welche Regel hat welche Domain verworfen“ ist Message Trace der falsche Einstieg, weil du pro Nachricht erst Get-MessageTraceDetailV2 aufrufen müsstest, um die Regelaktion zu sehen.
Get-MailTrafficPolicyReport reicht laut Microsoft Learn 90 Tage zurück und zählt Treffer pro Transportregel. Wenn dir eine Zahl pro Regel genügt, nimm diesen Report und spar dir das Runbook. Die Aufschlüsselung nach Absenderdomain liefert er nicht: Die Spalte Domain meint die akzeptierte Domain deines Tenants, nicht die Domain des Absenders. Die Absenderdomain steckt nur im Detail-Report, und der kennt keine 90 Tage. Die Historie musst du also selbst sammeln.
Stolperfalle seit dem 30. Juli 2026: EventType ist Pflicht
Mit MC1323250 vom 26. Mai 2026 hat Microsoft das Verhalten von Get-MailDetailTransportRuleReport und Get-MailTrafficPolicyReport geändert. Seit dem 30. Juli 2026 liefern beide Cmdlets Regeldaten nur noch, wenn du -EventType explizit mit TransportRuleHits oder TransportRuleActionHits angibst.
Ältere Skripte, die nur mit -Direction und -Action filtern, laufen weiter ohne Fehler, geben aber leere oder unvollständige Ergebnisse zurück. Das ist die unangenehmste Art von Breaking Change, weil kein Monitoring anschlägt. Prüf deine bestehenden Report-Skripte, bevor du sie als Vorlage verwendest.
TransportRuleHits zählt jede Regelübereinstimmung, TransportRuleActionHits die ausgeführte Aktion. Für eine Löschregel ist TransportRuleActionHits mit -Action DeleteMessage die passende Kombination, weil sie abbildet, was beim Empfänger nicht ankommt.
Schritt für Schritt
1: Automation-Konto & Runtime-Umgebung anlegen
Leg ein Automation-Konto mit systemseitig zugewiesener Managed Identity an. Für das Runbook erstellst du eine eigene Runtime-Umgebung auf Basis von PowerShell 7.4. In dieser Umgebung betreibt Redmond seine Runbooks, und dort hat er bestätigt, dass Graph SDK 2.41.2 wieder lädt. Die Versionen 2.41.0 und 2.41.1 scheiterten in 7.2- und 7.4-Umgebungen schon bei Connect-MgGraph.





In die Runtime-Umgebung gehören genau zwei Module: ExchangeOnlineManagement und Microsoft.Graph.Authentication. Kein Microsoft.Graph.Files, kein Microsoft.Graph.Sites, kein Sammelmodul Microsoft.Graph. Der Grund ist doppelt. Die Cmdlets aus Microsoft.Graph.Files laden in Azure Automation nicht.
Das Problem ist im GitHub-Repository des SDK als Issue 3741 dokumentiert und besteht laut Redmond auch in Version 2.40 noch. Dazu kommt die Versionskopplung: Jedes Graph-Modul setzt Microsoft.Graph.Authentication voraus, und Redmond hält in seinen Runtime-Umgebungen alle Graph-Module auf derselben Version. Je weniger Module, desto weniger kann beim nächsten Update auseinanderlaufen. Invoke-MgGraphRequest und Connect-MgGraph stecken beide im Authentication-Modul, mehr brauchst du nicht.
Das Sammelmodul Microsoft.Graph trägt in der PowerShell Gallery das Tag „AzureAutomationNotSupported“. Aktuell ist dort Version 2.41.2 vom 14. Oktober 2026. Redmond nennt 2.40 in seinem Beitrag die letzte „gute“ Version. Pinne die Version in der Runtime-Umgebung und aktualisiere erst nach einem Testlauf. Weil das Portal keine Versionsauswahl anbietet, installiert Redmond die Module per Azure-REST-API mit fester Version.
2: Exchange-Zugriff für die Managed Identity
Die Managed Identity braucht die API-Berechtigung Exchange.ManageAsApp und eine Entra-Rolle. Die folgenden Befehle laufen einmalig auf deinem Admin-Rechner, nicht im Runbook.
Connect-AzAccount
$MI_ID = (Get-AzADServicePrincipal -DisplayName "aa-mailflow-stats").Id
Connect-MgGraph -Scopes AppRoleAssignment.ReadWrite.All, Application.Read.All, RoleManagement.ReadWrite.Directory
# Exchange.ManageAsApp, die App-Rollen-ID ist in jedem Tenant gleich
$ExoSP = Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'"
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MI_ID -PrincipalId $MI_ID `
-AppRoleId "dc50a0fb-09a3-484d-be87-e023b12c6440" -ResourceId $ExoSP.Id
# Entra-Rolle, zuerst mit der lesenden Rolle testen
$RoleId = (Get-MgRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq 'Global Reader'").Id
New-MgRoleManagementDirectoryRoleAssignment -PrincipalId $MI_ID -RoleDefinitionId $RoleId -DirectoryScopeId "/"Bei der Rolle lohnt sich ein Test. Microsoft Learn nennt Global Reader als unterstützte Rolle für Managed Identities, stellt aber auch klar, dass für Anti-Spam-Funktionen und die zugehörigen Reports Exchange Administrator oder Global Administrator die sicheren Varianten sind und Security Administrator ausdrücklich nicht reicht.
Starte mit Global Reader. Liefert das Runbook im Testbereich einen Fehler, dass das Cmdlet nicht gefunden wird, tausch auf Exchange Administrator. Global Administrator für ein Reporting-Runbook ist nicht vertretbar, Microsoft beschränkt die Rolle selbst auf Notfallszenarien.
3: SharePoint-Zugriff auf genau eine Site
Welche SharePoint-Berechtigung Redmonds Managed Identity hat, nennt sein Beitrag nicht. Für ein Runbook, das eine einzige CSV-Datei pflegt, wären tenantweite Rechte wie Sites.ReadWrite.All zu viel.
Sites.Selected gibt der Managed Identity zunächst gar keinen Zugriff, erst eine explizite Freigabe auf Site-Ebene öffnet genau eine Site. Diese Freigabe setzt du mit einem Konto, das SharePoint Administrator oder höher und Mitglied der Ziel-Site ist, sonst lehnt Graph den Aufruf ab.
# App-Rolle Sites.Selected über ihren Namen suchen statt eine GUID zu hinterlegen
$GraphSP = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$SitesSelected = $GraphSP.AppRoles | Where-Object { $_.Value -eq "Sites.Selected" -and $_.AllowedMemberTypes -contains "Application" }
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MI_ID -PrincipalId $MI_ID `
-AppRoleId $SitesSelected.Id -ResourceId $GraphSP.Id
# Freigabe auf die Reporting-Site, braucht Sites.FullControl.All in deiner Admin-Sitzung
Connect-MgGraph -Scopes Sites.FullControl.All
$Site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/IT-Reporting"
$MI = Get-MgServicePrincipal -ServicePrincipalId $MI_ID
$Body = @{
roles = @("write")
grantedToIdentities = @(@{ application = @{ id = $MI.AppId; displayName = $MI.DisplayName } })
} | ConvertTo-Json -Depth 5
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/sites/$($Site.id)/permissions" -Body $Body -ContentType "application/json"4: Das Runbook
Microsoft Learn verlangt, dass Connect-ExchangeOnline der erste Befehl im Runbook ist. Deshalb steht die Organisation dort fest im Code und nicht in einer Variable davor.
Connect-ExchangeOnline -ManagedIdentity -Organization "contoso.onmicrosoft.com" -ShowBanner:$false
Connect-MgGraph -Identity -NoWelcome
$RuleName = "Junk-Domains verwerfen"
$SitePath = "contoso.sharepoint.com:/sites/IT-Reporting"
$TargetFile = "Mailflow/TransportRuleStats.csv"
# Fenster um einen Tag versetzt, weil Regeldaten bis zu 24 Stunden verzögert ankommen
$EndDate = (Get-Date).ToUniversalTime().Date.AddDays(-1)
$StartDate = $EndDate.AddDays(-7)
$WeekStart = $StartDate.ToString("yyyy-MM-dd")
$Events = [System.Collections.Generic.List[object]]::new()
$Page = 1
do {
[array]$Batch = Get-MailDetailTransportRuleReport -StartDate $StartDate -EndDate $EndDate `
-EventType TransportRuleActionHits -Direction Inbound -Action DeleteMessage `
-TransportRule $RuleName -Page $Page -PageSize 5000
if ($Batch) { $Events.AddRange($Batch) }
$Page++
} while ($Batch.Count -eq 5000)
# Nur Domain und Anzahl speichern, keine Empfängeradressen
$WeekRows = @($Events |
Group-Object { ($_.SenderAddress -split "@")[-1].ToLower() } |
ForEach-Object { [pscustomobject]@{ WeekStart = $WeekStart; Domain = $_.Name; Messages = $_.Count } })
if ($WeekRows.Count -eq 0) {
$WeekRows = @([pscustomobject]@{ WeekStart = $WeekStart; Domain = "(keine Treffer)"; Messages = 0 })
}
$Site = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/$SitePath"
$Drive = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/sites/$($Site.id)/drive"
$ContentUri = "https://graph.microsoft.com/v1.0/drives/$($Drive.id)/root:/$($TargetFile):/content"
$LocalFile = Join-Path ([System.IO.Path]::GetTempPath()) "TransportRuleStats.csv"
$History = @()
Invoke-MgGraphRequest -Method GET -Uri $ContentUri -OutputFilePath $LocalFile -SkipHttpErrorCheck -StatusCodeVariable "Status"
switch ([int]$Status) {
200 { $History = @(Import-Csv -Path $LocalFile | Where-Object { $_.WeekStart -ne $WeekStart }) }
404 { Write-Output "Keine Historie vorhanden, $TargetFile wird neu angelegt" }
default { throw "Download der Historie fehlgeschlagen (HTTP $Status), Lauf abgebrochen" }
}
@($History) + $WeekRows | Sort-Object WeekStart, Domain |
Export-Csv -Path $LocalFile -NoTypeInformation -Encoding utf8
Invoke-MgGraphRequest -Method PUT -Uri $ContentUri -InputFilePath $LocalFile -ContentType "text/csv" -ErrorAction Stop | Out-Null
Write-Output "Woche ab ${WeekStart}: $($Events.Count) verworfene Nachrichten aus $($WeekRows.Count) Domains"
Disconnect-ExchangeOnline -Confirm:$falseDrei Details in diesem Code verhindern Datenverlust. Der switch auf den HTTP-Status unterscheidet „Datei gibt es noch nicht“ von „SharePoint hatte gerade einen Aussetzer“. Ein simples try/catch würde bei einem HTTP 503 die Historie als leer behandeln und beim Upload mit nur einer Woche überschreiben.
Der Filter auf WeekStart sorgt dafür, dass ein manuell wiederholter Lauf dieselbe Woche ersetzt statt doppelt einträgt. Und die Zeile „(keine Treffer)“ macht Wochen ohne Spam sichtbar, sonst kannst du später nicht unterscheiden, ob die Regel nichts zu tun hatte oder das Runbook gar nicht gelaufen ist.
Der erste Lauf ist automatisch der 404-Fall. Starte ihn im Testbereich und kontrolliere, ob die Datei im Ordner Mailflow der Bibliothek auftaucht. Erst dann veröffentlichst du das Runbook.



5: Zeitplan und Abgleich
Verknüpfe das Runbook mit einem wöchentlichen Zeitplan, etwa montags um 6 Uhr. Den Versatz um einen Tag im Code braucht es, weil Microsoft für den Transportregel-Report im EAC eine Verzögerung von bis zu 24 Stunden dokumentiert. Ein Fenster, das bis „heute“ reicht, würde die letzten Stunden regelmäßig verlieren. Weil Start und Ende jede Woche um genau sieben Tage weiterwandern, entstehen trotzdem keine Lücken, solange der Zeitplan am selben Wochentag läuft.
Für den Plausibilitätscheck vergleichst du die Wochensumme aus der Runbook-Ausgabe mit dem Diagramm im EAC unter Reports, Mail flow, Exchange transport rule report. Weichen die Zahlen für dieselbe Woche deutlich ab, stimmt meist der Regelname im Skript nicht oder der Filter -EventType fehlt.
In Power BI liest du die Datei über den SharePoint-Ordner-Connector ein und baust eine Matrix aus WeekStart und Domain. Domains, die acht Wochen in Folge keinen Treffer mehr hatten, sind Kandidaten zum Streichen aus der Regelbedingung. Das hält die Liste pflegbar.
Tenant-Grenzen & CSV als Nachweis
Eine Managed Identity existiert nur im Entra-Tenant der Azure-Subscription, in der das Automation-Konto liegt. Für jeden weiteren Tenant brauchst du deshalb entweder ein Automation-Konto in einer Azure-Subscription dieses Tenants (etwa über einen CSP-Azure-Plan) oder App-only-Authentifizierung mit Zertifikat. Das Runbook bleibt gleich, nur die beiden Connect-Zeilen ändern sich.
Der Ablageort der CSV entscheidet auch über den Datenschutz. DeleteMessage löscht ohne Quarantäne, und nach zehn Tagen gibt es im Tenant keinen Beleg mehr dafür, welche Nachrichten die Regel entfernt hat. Fragt ein Geschäftspartner, warum seine Mails nie ankamen, ist die wöchentliche Statistik das Einzige, was du vorlegen kannst. Sie gehört deshalb in den SharePoint des Tenants, dessen Mails sie beschreibt. Eine zentrale Datei mit Absenderdaten aus mehreren Tenants schafft ein Auftragsverarbeitungsthema, das du nicht brauchst.
Leg für die Bibliothek eine Aufbewahrungsrichtlinie fest, damit die Historie nicht versehentlich verschwindet. Das Runbook speichert bewusst nur Domain und Anzahl, keine Empfängeradressen und keine Betreffzeilen. Für eine Wirksamkeitsstatistik reicht das, und es hält den Anteil personenbezogener Daten klein, weil keine einzelne Mailadresse in der Datei landet.
Fazit
Begrenzte Vorhaltezeiten sind in Exchange Online normal, selbst Message Trace endet nach 90 Tagen. Bei einer Löschregel wiegt das zehntägige Fenster schwerer, weil ohne eigene Protokollierung danach nichts mehr belegt, was die Regel getan hat. Ein wöchentliches Runbook mit einer CSV in SharePoint schließt diese Lücke mit wenig Aufwand, und die Daten liegen in einem Format, das jedes Auswertungswerkzeug lesen kann.
MC1323250 war seit dem 26. Mai 2026 angekündigt, im Skript wirkt die Änderung trotzdem still: kein Fehler, nur leere Ergebnisse. Prüf jedes Skript, das Get-MailDetailTransportRuleReport oder Get-MailTrafficPolicyReport aufruft, auf den Parameter -EventType. Und die Fehlerbehandlung beim Download der Historie entscheidet darüber, ob deine Statistik nach einem SharePoint-Aussetzer noch existiert.
Bei der Frage Graph-SDK-Cmdlets oder direkte Graph-Requests vertritt Redmond die Position, dass Administratoren mit den SDK-Cmdlets besser fahren und direkte Requests eher etwas für Entwickler sind. Für interaktive Skripte stimmt das. Für Runbooks sehe ich es anders. Ein Runbook mit vier Graph-Aufrufen braucht weder den Retry-Handler noch die automatische Paginierung des SDK, profitiert aber davon, dass in der Runtime-Umgebung nur noch ein einziges Graph-Modul liegt.
Jedes zusätzliche Modul ist eine Abhängigkeit, die bei einem SDK-Update brechen kann. Allein im Oktober 2026 ließ sich Connect-MgGraph mit den Versionen 2.41.0 und 2.41.1 in 7.2- und 7.4-Umgebungen nicht laden, erst 2.41.2 lief wieder. Wer Runbooks für den Dauerbetrieb baut, sollte Connect-MgGraph für das Token nutzen und den Rest mit Invoke-MgGraphRequest erledigen.
weitere Links
| Message Center Archive, MC1323250: EventType-Pflicht für Transportregel-Reports | https://mc.merill.net/message/MC1323250 |
| Microsoft Learn, Get-MailDetailTransportRuleReport: Parameter und Zehn-Tage-Fenster | https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-maildetailtransportrulereport |
| Microsoft Learn, Get-MailTrafficPolicyReport: 90 Tage, SummarizeBy und Domain-Spalte | https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-mailtrafficpolicyreport |
| Microsoft Learn, Get-MessageTraceV2: Zeitraum, ResultSize, Throttling | https://learn.microsoft.com/en-us/powershell/module/exchange/get-messagetracev2 |
| Microsoft Learn, Exchange Online PowerShell mit Managed Identity: Rollen, Runbook-Reihenfolge | https://learn.microsoft.com/en-us/powershell/exchange/connect-exo-powershell-managed-identity |
| Microsoft Learn, Exchange transport rule report im EAC: Verzögerung bis 24 Stunden | https://learn.microsoft.com/en-us/exchange/monitoring/mail-flow-reports/mfr-exchange-transport-rule-report |
| Microsoft Learn, Invoke-MgGraphRequest: SkipHttpErrorCheck, StatusCodeVariable, Datei-Parameter | https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/invoke-mggraphrequest |
| Microsoft Learn, Graph API site-post-permissions: Freigabe für Sites.Selected | https://learn.microsoft.com/en-us/graph/api/site-post-permissions?view=graph-rest-1.0 |
| PowerShell Gallery, Microsoft.Graph 2.41.2: Versionshistorie und Tag AzureAutomationNotSupported | https://www.powershellgallery.com/packages/Microsoft.Graph/2.41.2 |
| GitHub msgraph-sdk-powershell, Issue 3741: Microsoft.Graph.Files in Azure Automation | https://github.com/microsoftgraph/msgraph-sdk-powershell/issues/3741 |
💬 Kommentare
0 KommentareSei der Erste und starte die Diskussion mit einem hilfreichen Beitrag.
Kommentar hinterlassen
Dein Beitrag wird vor der Veröffentlichung kurz geprüft — fachlich, respektvoll und auf den Punkt ist hier genau richtig.