{"id":405,"date":"2023-12-07T15:46:09","date_gmt":"2023-12-07T14:46:09","guid":{"rendered":"https:\/\/sparrowte.ch\/?p=405"},"modified":"2024-11-04T20:22:51","modified_gmt":"2024-11-04T19:22:51","slug":"405","status":"publish","type":"post","link":"https:\/\/sparrow365.de\/index.php\/2023\/12\/07\/405\/","title":{"rendered":"Arbeiten mit Entra ID Directory Extensions"},"content":{"rendered":"<h2>Entra ID Directory Extensions<\/h2>\n<p>Haben Sie schon einmal Informationen in Entra ID speichern wollen, konnten aber kein passendes Attribut finden, um Ihre Daten zu abzulegen?<br \/>\nZum Beispiel, um den Spitznamen einer Person auf eine nutzbare Weise zu hinterlegen? Oder ben\u00f6tigen Sie ein spezifisches Attribut aus Ihrer Personalverwaltungssoftware f\u00fcr Single Sign-On oder Autorisierung? Oder f\u00fcr dynamische Gruppen?<\/p>\n<p>Wenn Sie Active Directory-Attribute synchronisiert haben, die standardm\u00e4\u00dfig nicht in Entra ID verf\u00fcgbar sind, haben Sie m\u00f6glicherweise unbewusst Directory Extensions erstellt &#8211; und jetzt m\u00fcssen Sie diese Werte auslesen.<\/p>\n<p>Diese Informationen sind in der Entra ID GUI (Stand Dezember 2023) nicht verf\u00fcgbar, daher bleibt Ihnen nur die Graph API &#8211; meine pers\u00f6nliche Schnittstelle der Wahl ist PowerShell. Und da der <a href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/extensibility-overview\">Microsoft Dokumentation<\/a> einige Snippets fehlen, dachte ich, es k\u00f6nnte interesant sein, meinen Ausflug niederzuschreiben.<\/p>\n<p><br class=\"\"><\/p>\n<blockquote>\n<p>Ich verwende eine separate JSON-Datei, um Informationen zu speichern, die ich nicht unbedingt im Internet teilen m\u00f6chte:<br \/>\n<strong>tenantId<\/strong> : GUID des Entra ID Tenant<br \/>\n<strong>clientID<\/strong> : App ID meiner App Registration \/ Enterprise App<br \/>\n<strong>thumb<\/strong> : Fingerabdruck des Zertifikats, das ich zur Authentifizierung als App-Registrierung verwende<br \/>\n<strong>exampleUser<\/strong> : Benutzer-UPN meines Demo-Benutzers, Objekt-ID funktioniert genauso<br \/>\n<strong>extensionAppOID<\/strong> : OID &#8211; <strong><em>Object ID !<\/em><\/strong> der App-Registration, mit der ich meine Directory Extension verkn\u00fcpfen m\u00f6chte <\/p>\n<\/blockquote>\n<p><br class=\"\"><\/p>\n<p>Verbindungsaufbau:<\/p>\n<pre><code class=\"language-powershell\">$graphConfig = ConvertFrom-Json $(Get-Content -Raw $hiddenValuesPath)\nConnect-MgGraph -TenantId $graphConfig.tenantID -ClientId $graphConfig.clientID -CertificateThumbprint $graphConfig.thumb<\/code><\/pre>\n<p><br class=\"\"><\/p>\n<h2>Directory Extensions finden und lesen<\/h2>\n<blockquote>\n<ul>\n<li>Find-MgGraphCommand &quot;CmdLet&quot; liefert die API Uri, die vom CmdLet genutzt wird, so bekommt man <a href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/api\/overview?view=graph-rest-1.0\">bessere Dokumentation<\/a> sowie die notwendigen Rechte<\/li>\n<li>Get-MgUser \/ Die Graph API wird nur die Properties liefern nach denen man Fragt \ud83d\ude09<\/li>\n<\/ul>\n<\/blockquote>\n<p><br class=\"\"><\/p>\n<pre><code class=\"language-powershell\"># Speichern der Directory Extension die ich ansehen will, da der volle Name etwas unhandlich ist\n$extension  = Get-MgDirectoryObjectAvailableExtensionProperty  | where Name -match &quot;exampleExtension&quot;\n\n# &quot;Ich versuche so wenig Microsoft.Graph Module wie m\u00f6glich zu nutzen weil Updates sonst ewig dauern&quot;\n# $extension = (Invoke-MgGraphRequest POST &quot;\/v1.0\/directoryObjects\/getAvailableExtensionProperties&quot; -OutputType PSObject).Value | Where-Object Name -match &quot;exampleExtension&quot;\n\n# Wert f\u00fcr einen einzelnen Benutzer auslesen\n$user = Get-MgUser -UserId $graphConfig.exampleUser -Property Displayname, Id, UserPrincipalName, $extension.Name \n\n# Definieren einer Calculated Property f\u00fcr Select-Object, um die Verschachtelung in AdditionalProperties aufzul\u00f6sen\n$extensionValueExpr = @{Name = &quot;$($extension.Name)&quot;; Expression = {$_.AdditionalProperties.$($extension.Name)}}\n$user | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table<\/code><\/pre>\n<p>Ergebnis:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrowte.ch\/wp-content\/uploads\/2023\/12\/image-1701948539195.png\" alt=\"Exemple Result 1\" \/><\/p>\n<hr \/>\n<p>Alternativ k\u00f6nnen wir nach den Extension Attributen Filtern:<\/p>\n<pre><code class=\"language-powershell\">$extension  = Get-MgDirectoryObjectAvailableExtensionProperty  | where Name -match &quot;nickName&quot;\n$users = Get-MgUser -Filter &quot;startswith($($extension.Name),&#039;I&#039;)&quot; -Property Displayname, Id, UserPrincipalName, $extension.Name \n\n# Definieren einer Calculated Property f\u00fcr Select-Object, um die Verschachtelung in AdditionalProperties aufzul\u00f6sen\n$extensionValueExpr = @{Name = &quot;nickName&quot;; Expression = {$_.AdditionalProperties.$($extension.Name)}}\n$users | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table<\/code><\/pre>\n<p>Ergebnis:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrowte.ch\/wp-content\/uploads\/2023\/12\/image-1701948756759.png\" alt=\"Example Result 2\" \/><\/p>\n<p><br class=\"\"><\/p>\n<h2>Eine eigene Extension erstellen<\/h2>\n<blockquote>\n<p>! Dies sollte nicht f\u00fcr die von Entra ID Connect Sync verwendeten Extensions versucht werden &#8211; die Quelle f\u00fcr diese Attribute sollte On-Premises bleiben<br \/>\nVerwenden Sie den Entra ID Connect Sync-Assistenten, um Erweiterungen hinzuzuf\u00fcgen<\/p>\n<\/blockquote>\n<p><br class=\"\"><\/p>\n<pre><code class=\"language-powershell\"># Es muss immer eine zugewiesene Applikation geben, ansonsten k\u00f6nnen die Attribute aber sehr breit genutzt werden\n$oid = $graphConfig.extensionAppOID\n$params = @{\n    name = &quot;freshExtension&quot;\n    dataType = &quot;String&quot;\n    targetObjects = @(&quot;User&quot;)\n}\nNew-MgApplicationExtensionProperty -ApplicationId $oid @params\n\n# Wenn ich die App ID meiner Registration nutzen will m\u00fcsste ich die Objekt ID von der Graph IP holen:\n# $oid = (Get-MgApplication -Filter $(&quot;AppId eq &#039;{0}&#039;&quot; -f $graphConfig.clientID)).Id\n\n# Weniger Module, gleiche Params:\n# Invoke-MgGraphRequest POST &quot;\/v1.0\/applications\/$oid\/extensionProperties&quot; -Body $params<\/code><\/pre>\n<p>Ergebnis:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrowte.ch\/wp-content\/uploads\/2023\/12\/image-1701948782039.png\" alt=\"Example Result 3\" \/><\/p>\n<p><br class=\"\"><\/p>\n<h2>Extension auf einem Benutzer setzen<\/h2>\n<blockquote>\n<p>! Dies sollte nicht f\u00fcr die von Entra ID Connect Sync verwendeten Extensions versucht werden &#8211; die Quelle f\u00fcr diese Attribute sollte On-Premises bleiben<br \/>\nNutzen sie OnPrem Management l\u00f6sungen f\u00fcr diese Eigenschaften (Set-ADUser, IAM, etc.)<\/p>\n<\/blockquote>\n<pre><code class=\"language-powershell\">$extension  = Get-MgDirectoryObjectAvailableExtensionProperty  | where Name -match &quot;freshExtension&quot;\n\n# Wir k\u00f6nnten mehrere Eigenschaften mit einem Request setzen, dann m\u00fcsste man sie aber alle benennen :)\n$params = @{\n    &quot;$($extension.Name)&quot; = &quot;Hello, this is a fresh value&quot;\n}\nUpdate-MgUser -UserId $graphConfig.exampleUser -BodyParameter $params\n\n# User Updates liefern kein Output bei Erfolg, schauen wir also nach\n$user = Get-MgUser -UserId $graphConfig.exampleUser -Property Displayname, Id, UserPrincipalName, $extension.Name \n\n# Definieren einer Calculated Property f\u00fcr Select-Object, um die Verschachtelung in AdditionalProperties aufzul\u00f6sen\n$extensionValueExpr = @{Name = &quot;freshExtension&quot;; Expression = {$_.AdditionalProperties.$($extension.Name)}}\n$user | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table\n<\/code><\/pre>\n<p>Result:<br \/>\n<img decoding=\"async\" src=\"https:\/\/sparrowte.ch\/wp-content\/uploads\/2023\/12\/image-1701948805997.png\" alt=\"Example Result 4\" \/><\/p>\n<p><br class=\"\"><\/p>\n<h2>Alle Extension Attribute lesen<\/h2>\n<p>Wenn wir alle Extension Attibute sehen wollen wird es etwas komplizierter &#8211; lasst es mich bitte wissen wenn ihr einen besseren Weg kennt \ud83d\ude09  <\/p>\n<pre><code class=\"language-powershell\"># Alle Extension Attribute auslesen - wird dieses Script regelm\u00e4\u00dfig ausgef\u00fchrt k\u00f6nnte man diese Cachen.\n$extensions = Get-MgDirectoryObjectAvailableExtensionProperty\n\n# Standard Benutzereigenschaften die wir auslesen m\u00f6chten\n$properties = @(&quot;Displayname&quot;, &quot;Id&quot;, &quot;UserPrincipalName&quot;) \n\n# Auslesen aller Benutzer mit allen Extension Properties\n$users = Get-MgUser -All -Property ($properties + $extensions.Name)\n\n$allUsersParsed = [System.Collections.Arraylist]::new()\n\n# Aufl\u00f6sen der Verschachtelung in der Hashtabelle\n# Filtert auch unn\u00f6tige Eigenschaften aus dem Graph Schema, die wir nicht angefragt haben\nForeach ($u in $users){\n    $userParsed = @{}\n    Foreach ($prop in $properties) {\n        $userParsed.$prop = $u.$prop\n    }\n    $userParsed += $u.AdditionalProperties\n    $allUsersParsed.Add([pscustomobject]$userParsed) | Out-Null\n}\n\n$allUsersParsed | Format-Table ($properties + $extensions.Name)<\/code><\/pre>\n<p>Ergebnis:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrowte.ch\/wp-content\/uploads\/2023\/12\/image-1701948827692.png\" alt=\"Example Result 5\" \/><\/p>\n<p><br class=\"\"><\/p>\n<h2>Abschlie\u00dfende Worte<\/h2>\n<p>Wir k\u00f6nnen also jetzt Verzeichniserweiterungen lesen und erstellen &#8211; sehr cool. Aber wie geht es weiter? <strong>Ich<\/strong> habe es zum Spa\u00df gemacht, aber lassen Sie mich wissen, wof\u00fcr Sie dieses Wissen sinnvoll eingesetzt haben \ud83d\ude01  <\/p>\n<p><br class=\"\"><\/p>\n<p>Ich werde keine Kommentare moderieren und m\u00f6chte Ihre E-Mail-Adresse nicht; bitte beteiligen Sie sich an der Diskussion \u00fcber <a href=\"https:\/\/www.linkedin.com\/posts\/julian-sperling-4bba72228_arbeiten-mit-entra-id-directory-extensions-activity-7138549730292256768-8x3h?utm_source=share&amp;utm_medium=member_desktop\">meinen Zugeh\u00f6rigen LinkedIn Post<\/a>. <\/p>\n<p><br class=\"\"><\/p>\n<p>Wenn sie an den Dingen interessiert sind die ich tue <a href=\"https:\/\/www.linkedin.com\/in\/julian-sperling-4bba72228\/\">folgen sie mir auf LinkedIn<\/a>.   <\/p>\n<p><br class=\"\"><\/p>\n<blockquote>\n<p>\u00dcbersetzung unterst\u00fctzt durch ChatGPT<\/p>\n<\/blockquote>\n<p><br class=\"\"><\/p>\n<h2>Mehr Informationen<\/h2>\n<ul>\n<li>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/hybrid\/connect\/how-to-connect-sync-feature-directory-extensions\">Konfigurieren von Entra ID Connect Sync Directory Extensions<\/a> <\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/extensibility-overview?tabs=http#comparison-of-extension-types\">Vergleich verschiedener Methoden eigene Daten in Entra ID hinzuzuf\u00fcgen<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/extensibility-overview?tabs=http#considerations-for-using-directory-extensions\">\u00dcberlegungen beim L\u00f6schen von Directory Extensions<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/graph\/api\/application-post-extensionproperty?view=graph-rest-1.0&amp;tabs=http#request-body\">Mehr Optionen bei der Erstellung von Directory Extensions<\/a><\/p>\n<\/li>\n<\/ul>\n<p><br class=\"\"><\/p>\n<p><br class=\"\"><\/p>\n<blockquote>\n<p>Updates: <\/p>\n<ol>\n<li>08.12.23 &#8211; Verbessern der Codeklarheit rund um Select-Object<\/li>\n<\/ol>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Entra ID Directory Extensions Haben Sie schon einmal Informationen in Entra ID speichern wollen, konnten aber kein passendes Attribut finden, um Ihre Daten zu abzulegen? Zum Beispiel, um den Spitznamen einer Person auf eine nutzbare Weise zu hinterlegen? Oder ben\u00f6tigen Sie ein spezifisches Attribut aus Ihrer Personalverwaltungssoftware f\u00fcr Single Sign-On oder Autorisierung? Oder f\u00fcr dynamische&#8230; &raquo; <a class=\"read-more-link\" href=\"https:\/\/sparrow365.de\/index.php\/2023\/12\/07\/405\/\">weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":404,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,48],"tags":[72,74,70,50,64,52,68,66,60,56,58,54,62],"class_list":["post-405","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-me-id","category-powershell","tag-aad","tag-aad-connect","tag-azure-ad","tag-directoryextensions","tag-entra","tag-entra-id","tag-entra-id-connect","tag-entra-id-connect-sync","tag-graph","tag-graph-api","tag-microsoft-graph","tag-powershell","tag-powershell-sdk"],"_links":{"self":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/comments?post=405"}],"version-history":[{"count":5,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/405\/revisions"}],"predecessor-version":[{"id":407,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/405\/revisions\/407"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media\/404"}],"wp:attachment":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media?parent=405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/categories?post=405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/tags?post=405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}