Entra ID Directory Extensions
Have you ever wanted to save information in Entra ID, but couldn’t find an appropriate attribute to store your data?
For example, storing someones nickname in a usable fashion? Or you need a specific attribute from your HR Software for Single Sign-On or authorization? Or for Dynamic Groups?
If you have synchronized Active Directory attributes not available to Entra ID by default, you might have unintentionally created Directory Extensions – and now you need to read those values.
This information is not available in the Entra ID GUI (as of December 2023), so your only option is the Graph API – my personal choice of interface is PowerShell. And since the Microsoft documentation is missing Snippets I thought it might be interesting to write this down.
I use a separate JSON File to store information that I don’t necessarily want to share on the internet:
tenantId : GUID of the Entra ID Tenant
clientID : Application ID of my App Registration / Enterprise App
thumb : Thumbprint of the certificate I use to authenticate as the App Registration
exampleUser : User UPN of my Demo user, Object ID also works
extensionAppOID : OID – Object ID ! of the App Registration I want to associate my Directory Extension with
Connecting:
$graphConfig = ConvertFrom-Json $(Get-Content -Raw $hiddenValuesPath)
Connect-MgGraph -TenantId $graphConfig.tenantID -ClientId $graphConfig.clientID -CertificateThumbprint $graphConfig.thumb
Finding and Reading Directory Extensions
- Use Find-MgGraphCommand "CmdLet" for the API Uri used by the CmdLet, this gives better documentation as well as the necessary permissions
- Get-MgUser / The Graph API will only return the Properties you ask for π
# Store the Directory Extension I want to look at, since the actual Name of the extension is rather unwieldy
$extension = Get-MgDirectoryObjectAvailableExtensionProperty | where Name -match "exampleExtension"
# "I try to use as few Microsoft.Graph modules as possible because updating takes forever"
# $extension = (Invoke-MgGraphRequest POST "/v1.0/directoryObjects/getAvailableExtensionProperties" -OutputType PSObject).Value | Where-Object Name -match "exampleExtension"
# Get the Value for a specific User
$user = Get-MgUser -UserId $graphConfig.exampleUser -Property Displayname, Id, UserPrincipalName, $extension.Name
# Define a Calculated Property for Select-Object, that resolves the nesting of AdditionalProperties
$extensionValueExpr = @{Name = "$($extension.Name)"; Expression = {$_.AdditionalProperties.$($extension.Name)}}
$user | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table
Result:
Alternatively, we can filter all Users by the Extension Attributes:
$extension = Get-MgDirectoryObjectAvailableExtensionProperty | where Name -match "nickName"
$users = Get-MgUser -Filter "startswith($($extension.Name),'I')" -Property Displayname, Id, UserPrincipalName, $extension.Name
# Define a Calculated Property for Select-Object, that resolves the nesting of AdditionalProperties
$extensionValueExpr = @{Name = "nickName"; Expression = {$_.AdditionalProperties.$($extension.Name)}}
$users | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table
Result:
Create an Extension
! This should not be done with the extensions in use by Entra ID Connect Sync – the Source of Truth for these attributes should remain OnPremises
Use the Entra ID Connect Sync Wizard to add Extensions
# There must always be an assigned app, but otherwise the Extensions can be assigned rather broadly
$oid = $graphConfig.extensionAppOID
$params = @{
name = "freshExtension"
dataType = "String"
targetObjects = @("User")
}
New-MgApplicationExtensionProperty -ApplicationId $oid @params
# If I were to use the App ID of my Registration with the attached Permissions I would have to fetch the ID from the Graph API:
# $oid = (Get-MgApplication -Filter $("AppId eq '{0}'" -f $graphConfig.clientID)).Id
# Less Modules, Params from Above are used:
# Invoke-MgGraphRequest POST "/v1.0/applications/$oid/extensionProperties" -Body $params
Result:
Set the Extension on a User
! This should not be done with the extensions in use by Entra ID Connect Sync – the Source of Truth for these attributes should remain OnPremises
Use OnPrem AD Management solutions to change Values (Set-ADUser, IAM, etc.)
$extension = Get-MgDirectoryObjectAvailableExtensionProperty | where Name -match "freshExtension"
# We could also set multiple Extensions with one Request, but then we would have to explicitly Name them all :)
$params = @{
"$($extension.Name)" = "Hello, this is a fresh value"
}
Update-MgUser -UserId $graphConfig.exampleUser -BodyParameter $params
# The Update does not return a result on Success, so lets have a look:
$user = Get-MgUser -UserId $graphConfig.exampleUser -Property Displayname, Id, UserPrincipalName, $extension.Name
# Define a Calculated Property for Select-Object, that resolves the nesting of AdditionalProperties
$extensionValueExpr = @{Name = "freshExtension"; Expression = {$_.AdditionalProperties.$($extension.Name)}}
$user | Select-Object Displayname, Id, UserPrincipalName, $extensionValueExpr | Format-Table
Result:
Read all Extension Attributes
If we want to see all extension properties at once, things get a little more complicated – do let me know if there is a better way π
# Get all Extension Properties - if run on a regular basis these could be cached
$extensions = Get-MgDirectoryObjectAvailableExtensionProperty
# Standard User Properties we want to Fetch
$properties = @("Displayname", "Id", "UserPrincipalName")
# Fetch all users and the Extension Properties
$users = Get-MgUser -All -Property ($properties + $extensions.Name)
$allUsersParsed = [System.Collections.Arraylist]::new()
# Flatten user data by merging extension properties from nested hashtables into a single-level hashtable
# Also filter out unnecessary fields from the full Graph User Schema to leave populated properties
Foreach ($u in $users){
$userParsed = @{}
Foreach ($prop in $properties) {
$userParsed.$prop = $u.$prop
}
$userParsed += $u.AdditionalProperties
$allUsersParsed.Add([pscustomobject]$userParsed) | Out-Null
}
$allUsersParsed | Format-Table ($properties + $extensions.Name)
Result:
Conclusion
So we can now read and create directory extensions – very cool. But where do we go from here? I did this for fun, but let me know what useful things you used this knowledge forπ
I won’t be moderating comments and I don’t want your email address; please join the discussion on my corresponding LinkedIn Post.
If you are interested in the things I do, please follow me on LinkedIn.
Further Reading
Updates:
- 08.12.23 – Improvements of Code Clarity around Select-Object
Comments