11 juli 2026 5 min läsning

Åtkomst till Exchange Online med PowerShell: ersätt EWS med Microsoft Graph

EWS upphör i Exchange Online den 1 oktober 2026. Så registrerar du en app, autentiserar ett PowerShell-skript med ett certifikat, begränsar åtkomsten till enskilda postlådor och hanterar meddelanden och bilagor via Microsoft Graph.

Microsoft avvecklar Exchange Web Services (EWS) i Exchange Online den 1 oktober 2026. Skript som hämtar meddelanden eller bilagor från en postlåda måste därför övergå till Microsoft Graph.

Exemplet i den här artikeln körs utan användarinloggning: det laddar ned ZIP-bilagor från en postlåda, packar upp dem, flyttar bearbetade meddelanden och skickar sedan en rapport. För detta behövs en appregistrering, ett certifikat och två Graph-behörigheter. Värden som example.com, klient-ID och app-ID är platshållare.

1. Nödvändiga PowerShell-moduler

Det räcker med tre moduler från Microsoft Graph SDK, inte hela metamodulen Microsoft.Graph:

Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.Actions -Scope AllUsers
Förklaring av alternativ
AlternativFunktion
Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.ActionsDe tre nödvändiga delmodulerna som positionsargument -Name: inloggning, e-post-cmdlets och åtgärder som Send-MgUserMail
-Scope AllUsersInstallerar modulerna systemomfattande under Program Files; krävs för att de även ska vara tillgängliga för det tjänstkonto som senare konfigureras för Scheduled Task (kräver administratörsbehörighet)

2. Appregistrering i Entra ID

Ett obevakat skript loggar in som en applikation med egna behörigheter. Skapa en ny registrering i Entra Admin Center under App registrations och tilldela dessa två behörigheter under API permissions → Microsoft Graph → Application permissions:

  • Mail.ReadWrite: läsa e-post och flytta den efter bearbetning

  • Mail.Send: skicka rapportmeddelandet

Bevilja sedan administratörsgodkännande och notera klient-ID samt Application (Client) ID.

3. Certifikat i stället för Client Secret

En app-only-inloggning fungerar med Client Secret eller certifikat. För schemalagda uppgifter är ett certifikat det bättre valet: den privata nyckeln stannar i certifikatarkivet och skriptet innehåller inget lösenord. Skapa certifikatet på servern som kör skriptet och exportera endast den offentliga delen:

$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
Förklaring av alternativ
AlternativFunktion
New-SelfSignedCertificate -SubjectCertifikatets ämnesnamn; används endast för igenkänning i certifikatarkivet
-CertStoreLocation "Cert:\LocalMachine\My"Lagrar certifikatet i datorarkivet, inte i användararkivet; det är därmed tillgängligt oberoende av den inloggade användaren
-KeyExportPolicy NonExportableFörhindrar export av den privata nyckeln; den lämnar inte servern
-KeySpec SignatureSkapar en signeringsnyckel; appinloggningen använder den för att signera token-request-assertion
-KeyLength 2048RSA-nyckellängd på 2048 bitar
-NotAfter (Get-Date).AddYears(2)Giltighetstidens slut om två år; därefter måste certifikatet förnyas och laddas upp på nytt
Export-Certificate -CertCertifikatobjekt som ska exporteras
-FilePathMålfil; innehåller som .cer endast den offentliga delen

Ladda upp den exporterade .cer-filen i appregistreringen under Certificates & secrets. Kontot för Scheduled Task behöver läsbehörighet till den privata nyckeln (certlm.msc → certifikat → All Tasks → Manage Private Keys).

4. Begränsa åtkomsten till enskilda postlådor

Application Permissions gäller inledningsvis för alla postlådor i klientorganisationen. Begränsa därför appen med en Application Access Policy till en e-postaktiverad säkerhetsgrupp. Detta steg körs en gång i Exchange Online PowerShell:

New-ApplicationAccessPolicy -AppId "<App-ID>" `
    -PolicyScopeGroupId "graph-mailboxes@example.com" `
    -AccessRight RestrictAccess `
    -Description "eCall Graph: nur Log-Postfach"

# Kontrollera att den gäller
Test-ApplicationAccessPolicy -AppId "<App-ID>" -Identity "ecall-logs@example.com"
Förklaring av alternativ
AlternativFunktion
New-ApplicationAccessPolicy -AppIdApplication (Client) ID för den appregistrering som principen gäller
-PolicyScopeGroupIdE-postaktiverad säkerhetsgrupp vars medlemmar definierar omfattningen
-AccessRight RestrictAccessBegränsar appen till gruppens postlådor; alternativet DenyAccess skulle blockera just dessa postlådor
-DescriptionFritext för dokumentation av principen
Test-ApplicationAccessPolicy -IdentityKontrollerar för en specifik postlåda om appen får åtkomst till den (AccessCheckResult: Granted eller Denied)

5. Upprätta anslutningen

Inloggningen använder klient-ID, app-ID och certifikatets tumavtryck, helt utan användarinteraktion:

$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
Förklaring av alternativ
AlternativFunktion
Connect-MgGraph -TenantIdKlientorganisation som appen loggar in mot
-ClientIdApplication (Client) ID för appregistreringen
-CertificateThumbprintVäljer inloggningscertifikatet via dess tumavtryck från det lokala certifikatarkivet; kombinationen av -ClientId och certifikat ger en app-only-inloggning utan användare
-NoWelcomeUndertrycker välkomsttexten efter inloggning; lämpligt för skriptutdata och loggar

6. Läs meddelanden och ladda ned ZIP-bilagor

Nu kan skriptet gå igenom inkorgen, spara och packa upp ZIP-bilagor samt flytta bearbetade meddelanden till «Borttagna objekt». Nedladdningen sker via slutpunkten /$value och Invoke-MgGraphRequest -OutputFilePath. Därmed hamnar råinnehållet direkt i en fil utan att en stor bilaga behöver hållas helt i arbetsminnet:

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)
    }

    # flytta bearbetat e-postmeddelande till "Borttagna objekt"
    Move-MgUserMessage -UserId $Mailbox -MessageId $msg.Id -DestinationId "deleteditems" | Out-Null
}
Förklaring av alternativ
AlternativFunktion
Add-Type -AssemblyName System.IO.Compression.FileSystemLäser in .NET-assemblyn med klassen ZipFile för uppackning
Get-MgUserMessage -UserIdPostlådan vars meddelanden läses; vid app-only-inloggning är angivelsen obligatorisk
-Top 100Begränsar frågan till högst 100 meddelanden per anrop
-Property id, subject, hasAttachmentsBegär endast de fält som behövs; det minskar svaret och snabbar upp anropet
Get-MgUserMessageAttachment -MessageIdMeddelande vars bilagor listas
Invoke-MgGraphRequest -Method GETHTTP-metod för direktanropet mot Graph API
-UriAnropad slutpunkt; det bifogade /$value levererar bilagans råa filinnehåll i stället för ett JSON-objekt
-OutputFilePathSkriver svaret direkt till målfilen utan att hålla hela bilagan i arbetsminnet
Move-MgUserMessage -DestinationId "deleteditems"Flyttar det bearbetade meddelandet till målmappen; deleteditems är det välkända mappnamnet för «Borttagna objekt»

Vid fler än 100 e-postmeddelanden: arbeta med Get-MgUserMessage -All eller sidindelning; för en månadskörning räcker vanligtvis en batch.

7. Skicka rapportmeddelande via Graph

Även Send-MailMessage är föråldrat. Via samma appregistrering (behörigheten Mail.Send) skickas e-postmeddelandet direkt via Graph, här med en fil som base64-kodad bilaga:

$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
Förklaring av alternativ
AlternativFunktion
-UserIdPostlåda i vars namn e-postmeddelandet skickas; måste omfattas av Application Access Policy
-BodyParameterHela meddelandet som Hashtable i Graph-schemat: message med ämne, brödtext, mottagare och bilagor samt saveToSentItems för lagring i «Skickade objekt»

8. Kör obevakat

Som en schemalagd uppgift körs skriptet utan inloggning eftersom certifikatet finns i kontots arkiv:

$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")
Förklaring av alternativ
AlternativFunktion
New-ScheduledTaskAction -ExecuteProgram som ska köras, här powershell.exe
-ArgumentKommandorad för programmet: -NoProfile hoppar över პროფილskript, -ExecutionPolicy Bypass kringgår skriptprincipen för detta anrop och -File anger skriptet
New-ScheduledTaskTrigger -Daily -At 06:00Daglig utlösare klockan 06:00
Register-ScheduledTask -TaskNameUppgiftens namn i Aktivitetsschemaläggaren
-Action / -TriggerKopplar den tidigare skapade åtgärden och utlösaren till uppgiften
-UserKonto som uppgiften körs under; den privata nyckeln måste vara läsbar i dess certifikatarkiv
-Password (Read-Host "Passwort")Frågar interaktivt efter kontots lösenord så att uppgiften kan starta även utan en inloggad användare; på så sätt hamnar det inte i skriptet eller historikfilen

Det fullständiga exemplet med loggning och felhantering finns på GitHub: pfstr/eCall-Log-Analyzer.

Källor

  1. Microsoft – «Retirement of Exchange Web Services in Exchange Online»
  2. Microsoft Learn – «Get access without a user (App-only)»

    App-only-autentisering mot Microsoft Graph med certifikat.

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

    Application Access Policy för att begränsa appen till enskilda postlådor.

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

Kommentarer

Kommentarerna hämtas från GitHub / Giscus.