E-Mails via Powershell und Microsoft Graph API zu verschicken, ist leider nicht so einfach wie mit dem klassischen Send-MailMessage
Cmdlet. Zu dem klassischen Weg hatte ich ja auch kürzlich einen Blog-Post veröffentlicht.
Trotzdem gibt es einige Gründe, die für den Versand per Microsoft Graph sprechen. Im Oktober 2025 wird voraussichtlich die Plaintext Authentifizierung (“Basic Authentifizierung”) für den SMTP-Versand bei Exchange Online abgeschaltet. Falls also Exchange Online euer einziges E-Mail-System ist, aber ihr automatisiert E-Mails verschicken wollt, dann ist MS Graph die richtige Wahl. Alternativ könntet ihr natürlich auch ein zusätzliches E-Mail-System anschaffen (anmieten oder selbst betreiben).
Ich finde es ganz charmant, wenn wir per PowerShell E-Mails verschicken und dabei die Exchange Online Infrastruktur nutzen können (inklusive eventueller Transportregeln, bestehendem DKIM/DMARC/SPF Setup usw.).
Ich habe auch ein Video zum Versand von E-Mails per MS Graph gemacht: https://youtu.be/0kgKD3XsEXU
Die Herausforderung
Komplexer als beim einfachen Send-MailMessage
ist beim Versand per Microsoft Graph:
- Authentifizierung
- Authentifizierung “als App” möglich
- per Managed Identity, Zertifikat oder Client Secret
- in der Regel ohne Einschränkungen durch Conditional Access Policies
- Eher für Testzwecke: Authentifizierung auch als User möglich
- Authentifizierung “als App” möglich
- Syntax
- Das Microsoft Graph PowerShell SDK bietet zwar das Cmdlet
Send-MgUserMail
, allerdings müssen selbst einfachste Parameter als komische Hashtable angegeben werden, anstatt dass es einfach PowerShell Cmdlet Parameter sind 😓
- Das Microsoft Graph PowerShell SDK bietet zwar das Cmdlet
PowerShell Module…
Es wäre auch möglich komplett ohne Zusatzmodule die Microsoft Graph API in PowerShell zu verwenden. Dafür müssten dann die HTTP Requests selbst konstruiert werden und dann per Invoke-RestMethod
gesendet werden. Die Authentifizierung ist dann aber noch mal ein bisschen kniffliger.
Das PowerShell Graph SDK ist das offizielle Toolkit von Microsoft für die Verwendung der Microsoft Graph API in der PowerShell. Leider ist es nicht besonders gut. Es ist automatisch aus den API Spezifikationen generiert. Dadurch sind die Cmdlet-Namen teilweise lang und komisch, und die Parameter müssen oftmals komplex per Hashtable/JSON übergeben werden (anstatt das es einfach PowerShell Cmdlet Parameter sind). Es gibt auch Drittanbieter-Module für die Graph API, die Vorteile wie z.B. eine erhöhte Geschwindigkeit haben. Aber dafür ist das PowerShell Graph SDK von Microsoft. Ist also ein First-Party-Modul, dem ich ein höheres Vertrauen entgegenbringen würde. Man könnte es auch als den Standard-Weg bezeichnen.
Ich persönlich habe nicht grundsätzlich etwas gegen PowerShell Module, die nicht von Microsoft kommen. Aber für manche Organisationen ist das wichtig.
… für den E-Mail Versand
Am Automatisierungshost benötigt ihr für den E-Mail-Versand das Microsoft.Graph.Users.Actions
Modul.
|
|
… für die Einrichtung
Diese Module müssen nicht unbedingt auf dem System installiert werden, auf dem dann die Automatisierung (also der E-Mail-Versand) läuft. Sie werden nur für die Einrichtung von Berechtigungen benötigt und könnten z.B. auf einer Admin-Workstation installiert werden.
Microsoft.Graph.Applications
- um der App Registrierung bzw. Managed Identity grundsätzlich die Rechte zum E-Mail-Versand zu erteilenExchangeOnlineManagement
- um den Versand von E-Mails auf einzelne Absender zu beschränken
|
|
Authentifizierung und Berechtigungen
Bevor wir E-Mails verschicken können, müssen wir uns authentifizieren.
Für einfache Tests: Als User
Für einfache Testzwecke können wir uns als User authentifizieren und dann im eigenen Namen E-Mails versenden. Falls das PowerShell Skript aber regelmäßig unbeaufsichtigt laufen soll, ist das hier nicht dafür geeignet.
|
|
Nachdem ihr euch grundsätzlich authentifiziert habt, kommt vermutlich noch eine Abfrage ob ihr den Berechtigungen zustimmt. Eventuell sind die Rechte dafür in eurem Tenant eingeschränkt, dann dürft ihr das als normaler User gar nicht bestätigen.
Falls ihr euch gerade als Administrator authentifiziert habt, dann gibt es noch die Checkbox zum “Zustimmen im Namen der Organisation” (“Consent on behalf of your organization”). Den Haken solltet ihr normalerweise nicht setzen - falls ihr die Option aktiviert, dann dürften ab jetzt alle User in eurem Tenant das PowerShell Graph SDK verwenden um E-Mails zu verschicken.
Sicher und einfach: Managed Identity
Die beste Option für produktive Zwecke ist meiner Meinung nach per “Managed Identity”. Dabei werden die Zugangsdaten automatisch durch Microsoft Entra ID verwaltet. Das funktioniert aber nur für Azure Ressourcen wie z.B. Azure VMs, Azure Automation Accounts oder an Azure angebundene Systeme (per Azure Arc).
Dafür müsst ihr zunächst eurer Azure Ressource eine Managed Identity zuweisen. Das geht oft direkt bei der Erstellung, aber natürlich auch im Nachhinein.
Die Managed Identity hat dann einen Service Principal mit Objekt ID und kann darüber Berechtigungen erhalten. Die Zuweisung der Berechtigung zum E-Mail-Versand könnt ihr mit folgendem PowerShell Code machen. Diese Einrichtung muss nicht am Automatisierungs-Host erfolgen, sondern kann z.B. auf einem Admin-System gemacht werden (siehe auch: PowerShell Module für die Einrichtung).
Die Werte für die Variablen $TenantId
und $managedIdentityObjectId
solltet ihr natürlich passend für eure Umgebung setzen. Bei Bedarf könntet ihr der App auch noch weitere Graph Berechtigungen zuweisen, dafür das Array $appRoleNames
mit weiteren Einträgen befüllen.
⚠️ Wichtig: Im Standard darf die Managed Identity erstmal von allen Exchange-Absendern aus eurer Umgebung aus versenden. Ihr solltet die Berechtigungen einschränken - wie das geht erkläre ich im Abschnitt E-Mail-Versand nur auf bestimmte Absender einschränken.
|
|
Es kann manchmal einige Zeit dauern bis die Berechtigung aktiv wird. Ich hatte es am nächsten Morgen dann wieder probiert (10-12h später) und dann ging es. Ich würde aber eigentlich eher mit 1h rechnen.
Die eigentliche Authentifizierung in eurem PowerShell Skript (welches E-Mails versenden soll) erfolgt ganz einfach so:
|
|
Es müssen also überhaupt keine Zugangsdaten im Code referenziert werden 👍
App Registration und Zertifikat oder Client Secret
Falls Managed Identities bei euch nicht infrage kommen, dann gibt es auch die Möglichkeit eine App Registration in Entra ID anzulegen und dann wahlweise per Zertifikat (besser) oder per Client-Secret (schlechter) zu authentifizieren. Die Anlage erfolgt z.B. im Entra Admin Center unter “Applications” - “App Registrations” per Button “New registration”.
⚠️ Wichtig: Im Standard darf die App/der Service Principal (nach der nachfolgenden Konfiguration) erstmal von allen Exchange-Absendern aus eurer Umgebung aus versenden. Ihr solltet die Berechtigungen einschränken - wie das geht erkläre ich im Abschnitt E-Mail-Versand nur auf bestimmte Absender einschränken.
Der Name der App ist im Grunde frei wählbar. Bei “Supported Account Types” den Standard “Accounts in this organizational directory only […]” beibehalten. Eine Redirect URI müsst ihr nicht eintragen.
Nach der Anlage der App muss sie noch Berechtigungen erhalten. Für die Managed Identity im vorherigen Abschnitt hatten wir ja PowerShell verwendet. Hier mal wie es per GUI gehen würde:
Unter “API Permissions” auf “Add a permission” klicken.
Bei der Auswahl “Select an API” die Option “Microsoft Graph” auswählen.
Bei der Auswahl “What type of permissions does your application require?” die Option “Application permissions” wählen.
Bei der Auswahl “Select permission” die Berechtigung “Mail.Send” raussuchen und auswählen.
Die Standard-Berechtigung “User.Read” vom Type “Delegated” kann übrigens gelöscht werden - sie wird für den E-Mail-Versand nicht benötigt.
Anschließend der neuen API Berechtigung im Namen der Organisation zustimmen per “Grant admin consent for TENANTNAME” und per “Yes” bestätigen.
Zertifikat
Wenn ihr euch per Zertifikat authentifizieren möchtet, dann schaut euch die Dokumentation bei Microsoft an: https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-self-signed-certificate?wt.mc_id=MVP_330618
Client Secret
Wenn ihr euch per Client Secret authentifizieren möchtet, dann müsst ihr zunächst ein Secret erstellen. Das geht unter “Certificates & Secrets” - dort dann unter “Client secrets” auf “New client secret” klicken.
Tendenziell ist es aus Sicherheitsgründen empfehlenswert keine allzu lange Gültigkeitsdauer auszuwählen. Ich habe deshalb den Standardwert 180 Tage beibehalten.
Der Secret Value wird euch nur einmalig angezeigt. Ihr solltet ihn also sofort kopieren. Wenn ihr später zu der Seite zurück kommt, dann wird er nicht mehr vollständig angezeigt. Das Secret ist mit einem Passwort gleich zu setzen - sollte also auch nicht in Klartext in Dokumentationen aufgenommen werden und auch nicht im Klartext im PowerShell Code abgelegt werden.
Das Secret hier im Screenshot ist natürlich nicht mehr gültig 😉
Eine Möglichkeit das Secret einigermaßen sicher abzuspeichern wäre als exportieres PowerShell Credential Objekt. Das kann nur durch den User (am gleichen Computer) entschlüsselt werden, der es auch verschlüsselt hat. Alternativ ist das PowerShell Modul SecretManagement auch noch ein interessanter Ansatz.
So könnt ihr die Credentials abfragen abspeichern - als Username die Application ID von der Hauptseite der App Registration angeben.
|
|
Und dann später in eurem Skript könnt ihr es so importieren und euch damit bei MS Graph authentifizieren. Bitte auch daran denken die TenantID einzutragen:
|
|
E-Mail-Versand nur auf bestimmte Absender einschränken
Wie bereits erwähnt darf ein Service Principal/Managed Identity mit der Mail.Send
Berechtigung erstmal alle Exchange-Objekte aus eurer Umgebung als Absender verwenden. Ich würde empfehlen diese Rechte immer einzuschränken, sodass nur durch bestimmte Absender verschickt werden darf.
Die Konfiguration so einer Einschränkung erfolgt per Exchange Online PowerShell. Das muss nicht am Automatisierungshost gemacht werden, sondern kann auch von einer Admin-VM o.ä. erfolgen (siehe auch: PowerShell Module für die Einrichtung).
Zu den Parameterwerten für New-ApplicationAccessPolicy
:
-AppId
die Application ID (auch “Client ID” genannt) von eurer App Registration bzw. eurer Managed Identity*-PolicyGroupScopeId
wahlweise ein einzelnes Postfach (auch Shared Mailboxes werden unterstützt) oder eine Mail-Enabled Security Group, die die Exchange Objekte enthält von denen aus gesendet werden soll-Description
die Beschreibung ist frei wählbar-AccessRight
der WertRestrictAccess
sorgt dafür, dass die App nur auf die bei-PolicyGroupScopeId
genannten Mailboxen zugreifen darf
|
|
*Die Application ID eurer Managed Identity ist übrigens nicht die Object ID. Falls ihr die Application ID herausfinden möchtet, dann schaut im Entra Portal unter “Enterprise Applications” und ändert den Filter “Application type” auf “Managed Identities”.
Beispiele für den E-Mail Versand
Puh… Die Authentifizierung und Einschränkung auf bestimmte Absender-Adressen haben wir jetzt also. Hier ein paar Beispiele für den eigentlichen Versand von E-Mails.
Es gibt noch zahlreiche weitere Optionen, für die ihr die $params
Hashtable anpassen könnt. Diese “complex Parameters” werden in der Dokumentation zum Cmdlet Send-MgUserMail
im Abschnitt “Notes” aufgeführt.
Beispiel 1: Plain-Text E-Mail
|
|
Beispiel 2: HTML E-Mail
|
|
Beispiel 3: Mehrere Empfänger
Die Syntax für mehrere Empfänger ist ein wenig eigen. Hier mal ein Beispiel für zwei Empfänger:
Die Eigenschaft toRecipients
ist ein Array, welches wiederum zwei Hashtables enthält (eine pro Empfänger), die jeweils eine Hashtable enthalten. Cool 😐
|
|
Alternativen
Da das Rumhantieren mit den Hashtables etwas umständlich ist, gibt es einige alternative Module aus der Community.
Zum Beispiel Mailozaurr von Microsoft MVP Przemysław Kłys finde ich ganz cool. Das Modul stellt das neue Cmdlet Send-EmailMessage
bereit (also statt dem Standard Send-MailMessage
). Es werden auch noch andere Protokolle und Authentifizierungen unterstützt (nicht nur MS Graph). Leider werden zurzeit keine Managed Identities unterstützt, deshalb habe ich es nur einmal kurz zu Testzwecken verwendet.