Using PowerShell to Manage Azure AD Custom Security Attributes

Microsoft Graph PowerShell SDK Cmdlets Support Custom Security Attributes

Updated 12-Sep-2023

Introduced in preview in December 2021, I’m still looking for a good way to use Azure AD custom security attributes. Microsoft recently updated conditional access policies to support an app filter based on custom security attributes. That’s a nice example of what’s possible, but it’s probably a scenario limited to tenants that need such a capability.

In my original commentary on custom security attributes, I wondered if they might replace Exchange Online custom attributes. So far, I see little sign that this will happen, if only because the preview implementation only supports user and service principal objects. Exchange Online supports custom attributes for all mail-enabled objects, meaning that these attributes are more flexible. Marking Microsoft 365 groups so that changes to their membership can be monitored is just one recent practical example of custom attributes in use.

Marking objects is one thing. Being able to find the marked objects is equally important. The cmdlets in the Exchange Online management PowerShell support server-side filtering against custom attributes to make it easy and fast to search against the attributes. It therefore seemed like a good thing to check how to search Azure AD objects using values stored in Azure AD custom security attributes.

Assigning and Finding Custom Security Attributes

Cmdlets in the Microsoft Graph PowerShell SDK that operate against Azure AD user accounts and service principals (for enterprise and registered applications) support the custom security attributes. Microsoft’s documentation explains the basics. For example, to add custom security attributes to a user object, populate a hash table containing the attribute names and values and use it as input to the Update-MgUser cmdlet.

$Attributes = @{
	CustomSecurityAttributes = @{
	  Employees = @{
           "@odata.type" = "#Microsoft.DirectoryServices.CustomSecurityAttributeValue"
           JobCode = 'Principal'
           DateOfHire = '2-Nov-1983'
           "EmployeeNumber@odata.type" = "#Int32"
           EmployeeNumber = '150847'
           Executive = $true

Update-MgUser -Userid -BodyParameter $Attributes

In this case, the attributes come from a set called Employees (Figure 1) that contains attributes defined as string, Boolean, and integer data types. You can see in the PowerShell snippet how to define the data types for Boolean and integer attributes.

Custom Security Attributes in the Azure AD admin center
Figure 1: Custom Security Attributes in the Azure AD admin center

If you make a mistake with an attribute value, update the parameter with the correct value and run Update-MgUser again. To remove an attribute value, pass a blank value (something like ‘ ‘) rather than $Null. The lack of support for the $Null PowerShell variable is an SDK foible.

To check that the custom security attributes are in place, run the Get-MgUser cmdlet against the user account and fetch the CustomSecurityAttributes property:

$User = Get-MgUser -UserId -Property CustomSecurityAttributes

$EmployeeData = $User.customsecurityattributes.additionalProperties['Employees']

Although it’s simple to retrieve the property, complexity lurks because the attribute values are stored in a system dictionary object called additionalProperties. Multiple sets of custom security attributes can be assigned to an object, so additionalProperties holds a separate hash table for each set. In this instance, we’re only interested in attributes from the Employees set, so we extract them into a separate hash table to make it more convenient to access individual attribute values. Each attribute that we want to use is easily fetched by a keyed lookup against the hash table as shown below.


Key            Value
---            -----
@odata.type    #microsoft.graph.customSecurityAttributeValue
Executive      True
JobCode        Principal
LegalEntity    RA Ireland
DateOfHire     2-Nov-1983
EmployeeNumber 150847


I’m sure that the structure we’ve just navigated to find custom security attributes makes sense in terms of the way that Microsoft generates the cmdlets in the Microsoft Graph PowerShell SDK from the underlying Graph APIs. However, compared to the ease of access to data using other PowerShell cmdlets, it’s too complex and clunky.

Searching All User Accounts for an Attribute Value

This feeling is confirmed when trying to find users with a specific value stored in an attribute. This code finds all user member accounts in the tenant with at least one assigned license (to exclude accounts like those used for shared and room mailboxes). The code then loops through all users to find those with custom security attributes from the Employees set and checks if the job code attribute has a certain value.

[array]$Users = Get-MgUser -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" -ConsistencyLevel eventual -CountVariable Records -All -Property CustomSecurityAttributes, Id, DisplayName
ForEach ($User in $Users) {
    If ($User.customsecurityattributes.AdditionalProperties['Employees'] -ne $Null) {
        $EmployeeData = $User.customsecurityattributes.AdditionalProperties['Employees']
        If ($EmployeeData['JobCode'] -eq "Principal") {
          Write-Host $User.DisplayName "is a Principal" }

I can’t find a filter for the Get-MgUser cmdlet to search for a value in a single attribute. I’ll keep trying, but for now I’m left with the technique explained above.

Using Custom Security Attributes with Service Principals

The same approach applies to updating the service principal for an enterprise or registered app with a custom security attribute. This code searches to find a specific application and then updates two attributes. The only changes are in the cmdlets used and that the AppDepartment attribute supports the storage of multiple values. This means that we must identify that the attribute is a collection of strings and pass the strings in an array:

$App = Get-MgServicePrincipal -Filter "displayname eq 'Graph Microsoft 365 Groups Membership Report'"
$Attributes = @{
	CustomSecurityAttributes = @{
	  Applications = @{
           "@odata.type" = "#Microsoft.DirectoryServices.CustomSecurityAttributeValue"
           "AppLevel@odata.type" = "#Int32"
           AppLevel = "1"
           "AppDepartment@odata.type" = "#Collection(String)"
           AppDepartment = @( "IT" )

Finding applications marked with a certain attribute value follows the same path as explained above. Here’s what I did to find applications marked with IT in the AppDepartment attribute:

[array]$Apps = Get-MgServicePrincipal -All -Property CustomSecurityAttributes, DisplayName, Id, AppId
ForEach ($App in $Apps) {
    If ($App.customsecurityattributes.AdditionalProperties['Applications'] -ne $Null) {
       $AppAttributes = $App.customsecurityattributes.AdditionalProperties['Applications']
       If ($AppAttributes['AppDepartment'] -eq "IT") {
          Write-Host $App.DisplayName "is an IT application" }

Looking Forward to Change

I’m not writing off Azure AD custom security attributes (but if you want to store employee data, there are some standard attributes available for user objects). I’m sure that the Entra ID team has many plans to use these attributes in different ways and that the Microsoft Graph PowerShell SDK developers could make the attributes easier to work with. At least, I hope so.

Keep up to date with developments like Azure AD custom security attributes by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.

2 Replies to “Using PowerShell to Manage Azure AD Custom Security Attributes”

  1. “To remove an attribute value, pass the $Null value.”
    On removing a custom attribute via update-mguser – Oddly enough I can set an attribute to false but setting it to $null doesn’t remove it or change the attribute or it’s assignment just fyi.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.