Accedere a Exchange Online con PowerShell: sostituire EWS con Microsoft Graph
EWS termina in Exchange Online il 1° ottobre 2026. Ecco come registrare un'app, autenticare uno script PowerShell con un certificato, limitare l'accesso a singole cassette postali ed elaborare messaggi e allegati tramite Microsoft Graph.
Microsoft dismetterà Exchange Web Services (EWS) in Exchange Online il 1° ottobre 2026. Gli script che recuperano messaggi o allegati da una cassetta postale devono quindi passare a Microsoft Graph.
L’esempio di questo articolo viene eseguito senza accesso utente: scarica allegati ZIP da una cassetta postale, li estrae, sposta i messaggi elaborati e infine invia un report. A questo scopo sono necessari una registrazione dell’app, un certificato e due autorizzazioni Graph. Valori come example.com, ID tenant e ID app sono segnaposto.
1. Moduli PowerShell necessari
Sono sufficienti tre moduli dell’SDK Microsoft Graph, non l’intero meta-modulo Microsoft.Graph:
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.Actions -Scope AllUsers
2. Registrazione dell’app in Entra ID
Uno script non presidiato effettua l’accesso come applicazione con autorizzazioni proprie. Nel Centro di amministrazione di Entra creare una nuova registrazione in App registrations e assegnare queste due autorizzazioni in API permissions → Microsoft Graph → Application permissions:
-
Mail.ReadWrite: leggere le e-mail e spostarle dopo l’elaborazione -
Mail.Send: inviare l’e-mail di report
Concedere quindi il consenso dell’amministratore e annotare l’ID tenant e l’Application (Client) ID.
3. Certificato anziché Client Secret
Un’autenticazione app-only funziona con Client Secret o certificato. Per le attività pianificate, un certificato è la scelta migliore: la chiave privata rimane nell’archivio certificati e nello script non compare alcuna password. Creare il certificato sul server che esegue lo script ed esportare solo la parte pubblica:
$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
Caricare il file .cer-esportato nella registrazione dell’app, in Certificates & secrets. L’account dell’attività pianificata necessita dell’autorizzazione di lettura sulla chiave privata (certlm.msc → certificato → All Tasks → Manage Private Keys).
4. Limitare l’accesso a singole cassette postali
Inizialmente le Application Permissions sono valide per tutte le cassette postali del tenant. Limitare quindi l’app a un gruppo di sicurezza abilitato alla posta tramite un’Application Access Policy. Questo passaggio viene eseguito una sola volta in PowerShell di Exchange Online:
New-ApplicationAccessPolicy -AppId "<App-ID>" `
-PolicyScopeGroupId "graph-mailboxes@example.com" `
-AccessRight RestrictAccess `
-Description "eCall Graph: nur Log-Postfach"
# Verificare l'efficacia
Test-ApplicationAccessPolicy -AppId "<App-ID>" -Identity "ecall-logs@example.com"
5. Stabilire la connessione
L’autenticazione utilizza ID tenant, ID app e impronta digitale del certificato, senza alcuna interazione utente:
$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. Leggere i messaggi e scaricare gli allegati ZIP
Ora lo script può scorrere la posta in arrivo, salvare ed estrarre gli allegati ZIP e spostare i messaggi elaborati in «Posta eliminata». Il download avviene tramite l’endpoint /$value e Invoke-MgGraphRequest -OutputFilePath. In questo modo il contenuto grezzo viene salvato direttamente in un file, senza mantenere completamente in memoria un allegato di grandi dimensioni:
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)
}
# spostare l'e-mail elaborata in "Posta eliminata"
Move-MgUserMessage -UserId $Mailbox -MessageId $msg.Id -DestinationId "deleteditems" | Out-Null
}
Con più di 100 e-mail, lavorare con Get-MgUserMessage -All o con la paginazione; per un’esecuzione mensile di solito è sufficiente un batch.
7. Inviare l’e-mail di report tramite Graph
Anche Send-MailMessage è obsoleto. Con la stessa registrazione dell’app (autorizzazione Mail.Send) l’e-mail viene inviata direttamente tramite Graph, qui con un file come allegato codificato in base64:
$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. Esecuzione non presidiata
Come attività pianificata, lo script viene eseguito senza accesso perché il certificato si trova nello store dell’account:
$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")
L’esempio completo con registrazione e gestione degli errori è disponibile su GitHub: pfstr/eCall-Log-Analyzer.

Commenti
I commenti vengono caricati da GitHub / Giscus.