11 luglio 2026 5 min di lettura

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
Spiegazione delle opzioni
OpzioneEffetto
Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.ActionsI tre sottomoduli necessari come argomento posizionale -Name: autenticazione, cmdlet di posta e azioni come Send-MgUserMail
-Scope AllUsersInstalla i moduli a livello di computer in Program Files; necessario affinché siano disponibili anche per l’account di servizio configurato successivamente per l’attività pianificata (richiede diritti di amministratore)

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
Spiegazione delle opzioni
OpzioneEffetto
New-SelfSignedCertificate -SubjectNome del soggetto del certificato; serve solo per identificarlo nell’archivio certificati
-CertStoreLocation "Cert:\LocalMachine\My"Archivia il certificato nell’archivio del computer, non in quello dell’utente; è quindi disponibile indipendentemente dall’utente connesso
-KeyExportPolicy NonExportableImpedisce l’esportazione della chiave privata; non lascia il server
-KeySpec SignatureCrea una chiave di firma; l’autenticazione dell’app firma con essa l’asserzione della richiesta di token
-KeyLength 2048Lunghezza della chiave RSA di 2048 bit
-NotAfter (Get-Date).AddYears(2)Scadenza tra due anni; dopo sarà necessario rinnovare e caricare nuovamente il certificato
Export-Certificate -CertOggetto certificato da esportare
-FilePathFile di destinazione; contiene come .cer solo la parte pubblica

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"
Spiegazione delle opzioni
OpzioneEffetto
New-ApplicationAccessPolicy -AppIdApplication (Client) ID della registrazione dell’app a cui si applica il criterio
-PolicyScopeGroupIdGruppo di sicurezza abilitato alla posta i cui membri definiscono l’ambito
-AccessRight RestrictAccessLimita l’app alle cassette postali del gruppo; l’alternativa DenyAccess bloccherebbe proprio queste cassette postali
-DescriptionTesto libero per documentare il criterio
Test-ApplicationAccessPolicy -IdentityVerifica per una specifica cassetta postale se l’app può accedervi (AccessCheckResult: Granted oppure Denied)

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
Spiegazione delle opzioni
OpzioneEffetto
Connect-MgGraph -TenantIdTenant presso cui l’app effettua l’accesso
-ClientIdApplication (Client) ID della registrazione dell’app
-CertificateThumbprintSeleziona il certificato di autenticazione dall’archivio certificati locale tramite la relativa impronta digitale; la combinazione di -ClientId e certificato consente un’autenticazione app-only senza utente
-NoWelcomeSopprime il messaggio di benvenuto dopo l’accesso; utile per output degli script e log

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
}
Spiegazione delle opzioni
OpzioneEffetto
Add-Type -AssemblyName System.IO.Compression.FileSystemCarica l’assembly .NET con la classe ZipFile per l’estrazione
Get-MgUserMessage -UserIdCassetta postale di cui vengono letti i messaggi; con l’autenticazione app-only l’indicazione è obbligatoria
-Top 100Limita la query a un massimo di 100 messaggi per chiamata
-Property id, subject, hasAttachmentsRichiede solo i campi necessari; riduce la risposta e accelera la chiamata
Get-MgUserMessageAttachment -MessageIdMessaggio di cui vengono elencati gli allegati
Invoke-MgGraphRequest -Method GETMetodo HTTP della chiamata diretta all’API Graph
-UriEndpoint richiamato; il /$value aggiunto restituisce il contenuto file grezzo dell’allegato anziché un oggetto JSON
-OutputFilePathScrive la risposta direttamente nel file di destinazione, senza mantenere l’allegato interamente in memoria
Move-MgUserMessage -DestinationId "deleteditems"Sposta il messaggio elaborato nella cartella di destinazione; deleteditems è il nome di cartella noto per «Posta eliminata»

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
Spiegazione delle opzioni
OpzioneEffetto
-UserIdCassetta postale a nome della quale viene inviata l’e-mail; deve rientrare nell’ambito dell’Application Access Policy
-BodyParameterL’intero messaggio come hashtable nello schema Graph: message con oggetto, corpo, destinatari e allegati, nonché saveToSentItems per il salvataggio in «Posta inviata»

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")
Spiegazione delle opzioni
OpzioneEffetto
New-ScheduledTaskAction -ExecuteProgramma da eseguire, qui powershell.exe
-ArgumentRiga di comando per il programma: -NoProfile salta gli script di profilo, -ExecutionPolicy Bypass aggira il criterio script per questa chiamata, -File indica lo script
New-ScheduledTaskTrigger -Daily -At 06:00Trigger giornaliero alle 06:00
Register-ScheduledTask -TaskNameNome dell’attività nell’Utilità di pianificazione
-Action / -TriggerCollega all’attività l’azione e il trigger creati in precedenza
-UserAccount con cui viene eseguita l’attività; la chiave privata deve essere leggibile nel suo archivio certificati
-Password (Read-Host "Passwort")Richiede interattivamente la password dell’account affinché l’attività possa avviarsi anche senza un utente connesso; in questo modo non finisce nello script né nel file della cronologia

L’esempio completo con registrazione e gestione degli errori è disponibile su GitHub: pfstr/eCall-Log-Analyzer.

Fonti

  1. Microsoft – «Retirement of Exchange Web Services in Exchange Online»

    annuncio e data di riferimento (1° ottobre 2026) per la fine di EWS in Exchange Online.

    https://techcommunity.microsoft.com/blog/exchange/retirement-of-exchange-web-services-in-exchange-online/3924440
  2. Microsoft Learn – «Get access without a user (App-only)»

    autenticazione app-only a Microsoft Graph con certificato.

    https://learn.microsoft.com/en-us/graph/auth-v2-service
  3. Microsoft Learn – «Limiting application permissions to specific mailboxes»

    Application Access Policy per limitare l’app a singole cassette postali.

    https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access

Commenti

I commenti vengono caricati da GitHub / Giscus.