Mit PowerShell auf Exchange Online zugreifen: EWS durch Microsoft Graph ersetzen
EWS endet in Exchange Online am 1. Oktober 2026. So registrieren Sie eine App, melden ein PowerShell-Skript per Zertifikat an, beschränken den Zugriff auf einzelne Postfächer und verarbeiten Nachrichten und Anhänge über Microsoft Graph.
Microsoft stellt Exchange Web Services (EWS) in Exchange Online am 1. Oktober 2026 ein. Skripte, die Nachrichten oder Anhänge aus einem Postfach abholen, müssen deshalb auf Microsoft Graph wechseln.
Das Beispiel in diesem Beitrag läuft ohne Benutzeranmeldung: Es lädt ZIP-Anhänge aus einem Postfach, entpackt sie, verschiebt verarbeitete Nachrichten und versendet anschliessend einen Bericht. Dafür braucht es eine App-Registrierung, ein Zertifikat und zwei Graph-Berechtigungen. Werte wie example.com, Tenant-ID und App-ID sind Platzhalter.
1. Benötigte PowerShell-Module
Es genügen drei Module des Microsoft-Graph-SDK, nicht das gesamte Meta-Modul Microsoft.Graph:
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.Actions -Scope AllUsers
2. App-Registrierung in Entra ID
Ein unbeaufsichtigtes Skript meldet sich als Anwendung mit eigenen Rechten an. Legen Sie im Entra Admin Center unter App registrations eine neue Registrierung an und vergeben Sie unter API permissions → Microsoft Graph → Application permissions diese beiden Rechte:
-
Mail.ReadWrite: Mails lesen und nach der Verarbeitung verschieben -
Mail.Send: die Report-Mail versenden
Erteilen Sie danach die Administratorzustimmung und notieren Sie Tenant-ID sowie Application (Client) ID.
3. Zertifikat statt Client Secret
Eine App-Only-Anmeldung funktioniert mit Client Secret oder Zertifikat. Für geplante Aufgaben ist ein Zertifikat die bessere Wahl: Der private Schlüssel bleibt im Zertifikatspeicher, im Skript steht kein Passwort. Erzeugen Sie das Zertifikat auf dem ausführenden Server und exportieren Sie nur den öffentlichen Teil:
$cert = New-SelfSignedCertificate -Subject "CN=eCall-Graph" `
-CertStoreLocation "Cert:\LocalMachine\My" `
-KeyExportPolicy NonExportable -KeySpec Signature `
-KeyLength 2048 -NotAfter (Get-Date).AddYears(2)
Export-Certificate -Cert $cert -FilePath .\eCall-Graph.cer
$cert.Thumbprint # -> im Skript als Thumbprint verwenden
Die exportierte .cer-Datei in der App-Registrierung unter Certificates & secrets hochladen. Das Konto des Scheduled Tasks braucht Leserecht auf den privaten Schlüssel (certlm.msc → Zertifikat → All Tasks → Manage Private Keys).
4. Zugriff auf einzelne Postfächer einschränken
Application Permissions gelten zunächst für alle Postfächer im Tenant. Begrenzen Sie die App deshalb mit einer Application Access Policy auf eine mailaktivierte Sicherheitsgruppe. Dieser Schritt wird einmalig in Exchange Online PowerShell ausgeführt:
New-ApplicationAccessPolicy -AppId "<App-ID>" `
-PolicyScopeGroupId "graph-mailboxes@example.com" `
-AccessRight RestrictAccess `
-Description "eCall Graph: nur Log-Postfach"
# Wirksamkeit pruefen
Test-ApplicationAccessPolicy -AppId "<App-ID>" -Identity "ecall-logs@example.com"
5. Verbindung aufbauen
Die Anmeldung nutzt Tenant-ID, App-ID und den Zertifikat-Thumbprint, ganz ohne Benutzerinteraktion:
$TenantId = "00000000-0000-0000-0000-000000000000"
$ClientId = "00000000-0000-0000-0000-000000000000"
$Thumbprint = "0000000000000000000000000000000000000000"
$Mailbox = "ecall-logs@example.com"
Import-Module Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.Actions
Connect-MgGraph -TenantId $TenantId -ClientId $ClientId `
-CertificateThumbprint $Thumbprint -NoWelcome
6. Nachrichten lesen und ZIP-Anhänge herunterladen
Nun kann das Skript den Posteingang durchlaufen, ZIP-Anhänge speichern und entpacken sowie verarbeitete Nachrichten nach «Gelöschte Elemente» verschieben. Der Download erfolgt über den Endpunkt /$value und Invoke-MgGraphRequest -OutputFilePath. Dadurch landet der Rohinhalt direkt in einer Datei, ohne einen grossen Anhang vollständig im Arbeitsspeicher zu halten:
Add-Type -AssemblyName System.IO.Compression.FileSystem
$Zielordner = "D:\Import\{0:yyyyMMdd_HHmmss}" -f (Get-Date)
$messages = Get-MgUserMessage -UserId $Mailbox -Top 100 `
-Property id, subject, hasAttachments
foreach ($msg in $messages) {
$ordner = Join-Path $Zielordner $msg.Id
New-Item -Path $ordner -ItemType Directory -Force | Out-Null
$anhaenge = Get-MgUserMessageAttachment -UserId $Mailbox -MessageId $msg.Id |
Where-Object {
$_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.fileAttachment' -and
$_.Name -like '*.zip'
}
foreach ($att in $anhaenge) {
$zip = Join-Path $ordner $att.Name
$uri = "https://graph.microsoft.com/v1.0/users/$Mailbox/messages/$($msg.Id)/attachments/$($att.Id)/`$value"
Invoke-MgGraphRequest -Method GET -Uri $uri -OutputFilePath $zip
[System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $ordner)
}
# verarbeitete Mail in "Geloeschte Elemente" verschieben
Move-MgUserMessage -UserId $Mailbox -MessageId $msg.Id -DestinationId "deleteditems" | Out-Null
}
Bei mehr als 100 Mails mit Get-MgUserMessage -All oder Paging arbeiten; für einen Monatslauf reicht meist ein Batch.
7. Report-Mail über Graph senden
Auch Send-MailMessage ist veraltet. Über dieselbe App-Registrierung (Recht Mail.Send) geht die Mail direkt via Graph hinaus, hier mit einer Datei als base64-kodiertem Anhang:
$pfad = "D:\Reports\report.csv"
$body = @{
message = @{
subject = "eCall Report"
body = @{ contentType = "HTML"; content = "<b>Lauf erfolgreich</b>" }
toRecipients = @(@{ emailAddress = @{ address = "empfaenger@example.com" } })
attachments = @(@{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = Split-Path $pfad -Leaf
contentBytes = [Convert]::ToBase64String([IO.File]::ReadAllBytes($pfad))
})
}
saveToSentItems = $true
}
Send-MgUserMail -UserId "reporting@example.com" -BodyParameter $body
8. Unbeaufsichtigt ausführen
Als geplante Aufgabe läuft das Skript ohne Anmeldung, weil das Zertifikat im Store des Kontos liegt:
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "D:\Scripts\graph-import.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At 06:00
Register-ScheduledTask -TaskName "eCall-Graph-Import" -Action $action -Trigger $trigger `
-User "DOMAIN\svc-ecall" -Password (Read-Host "Passwort")
Das vollständige Beispiel mit Protokollierung und Fehlerbehandlung liegt auf GitHub: pfstr/eCall-Log-Analyzer.

Kommentare
Die Kommentare werden von GitHub / Giscus geladen.