Sync Custom Attributes to Office 365 for Group-based Licensing

This feature is an "oldie but goodie" that my customers are starting to ask questions about as they start looking into ways to automate their Office 365 deployments.

Can I sync additional attributes to Office 365, and can I use them for Group-Based Licensing?

The answers are yes and yes.  You're welcome.

There are a ton of subtopics here, and we're just going to dive right in.  You can follow along from start to finish, or jump to the part where you need help.

[toc]

Creating a custom attribute to extend your Active Directory Schema

The Active Directory schema has hundreds of attributes that can be associated with and used for users, contacts, and groups.  You can see a great OID reference at ldap.com, and more than likely, most of what you need is already there.  However, you may decide that you want to include additional information in your directory in a format specific to your organization. For that, we need to extend the schema.

Requesting an OID

So you want to create a schema extension?  Great--strap in.  In order to create a schema extension and register it in Active Directory, you need an object identifier (OID).  OIDs are an identifier mechanism standardized by the International Telecommunications Union (ITU) and ISO/IEC for naming any object, concept, or "thing" with a globally unambiguous persistent name.  They're organized in a hierarchical or tree-type system, allowing you to navigate to a top level node (such as an organization) and drill down to object types, classes, or individual objects.  Once you have an OID, you can assign it to whatever you want.  Typically, OIDs are used for LDAP and SNMP configurations.

To request an OID prefix for your organization, head over to the Internet Assigned Numbers Authority Private Enterprise Number request form, fill it out, and wait about a week.  Here's how I filled out my form:

You'll receive an email requesting that you confirm your submission.  After a week or so has passed, you should receive an OID issued to your organization.

From there, you can set up a structure to define how you will assign identifiers in your organization.  LDAPWiki has some great resources that you can follow.

If you don't want to request your own private enterprise number and devise your own OID scheme, you can use the OID generator located in the TechNet Gallery.  The base OID for this is 1.2.840.113556.1.8000.2554, which has been assigned to Microsoft for custom OID creation.  The OID generator we provide leverages the [guid]::NewGuid() method to create a unique tree for your organization underneath our PEN.

We recommend creating an auxiliary attribute class, then adding the new attributes to that attribute class, and then adding that auxiliary class to the structural (think user, group, contact) class.

First, we'll create some OIDs using the Microsoft PEN for customer use:

 $guid = ([guid]::NewGuid()).Guid
$value = @()
for ($i=0; $i -lt 5; $i++) 
    { 
    $value += [uint64]::parse($guid.Substring($i,4).Replace("-",""),"AllowHexSpecifier") 
    }
$prefix = "1.2.840.113556.1.8000.2554" # Microsoft custom PEN for OID generation
$attribuiteOid = $prefix + "." + ($value -join ".")
[System.Collections.ArrayList]$parentOidArray = $oid.Split(".")
$parentOidArray.RemoveAt($parentOidArray.Count - 1)
$parentOid = $parentOidArray -join "."

Adding the attribute to your Active Directory Schema

You *could* do this through the UI, but why, when you have the ability to do it via PowerShell?

 $Name        = "Name of Attribute"
$Description       = "Description of Attribute"
$DisplayName    = "DisplayName of Attribute"
$attributes = @{
 adminDescription  = "Admin description of attribute, if different than description"
 adminDisplayName  = "HR-Record-Locator" # DisplayName without spaces
    schemaIDGuid      = [guid]::NewGuid()
   attributeID   = $attributeOID
        ldapDisplayName   = "HRRecordLocator" # can only be ASCII characters
   omSyntax      = "19" # integer as defined in https://msdn.microsoft.com/en-us/library/cc223177.aspx"
    attributeSyntax   = "2.5.5.5" # dotted value as defined in https://docs.microsoft.com/en-us/windows/desktop/ADSchema/syntaxes"
  IsSingleValued    = $true # $true to make it a single-valued attribute; $false for multivalued"
 rangeUpper    = "250" # [max value if integer or 250 if value is string that will by synced by AAD Connect]
}
New-ADObject -Name $Name -server (Get-ADForest).SchemaMaster -Path (Get-ADRootDSE).SchemaNamingContext -Type attributeSchema -Description $Description -DisplayName $DisplayName -OtherAttributes $attributes

When I ran it in my environment (with actual values), it looked like this:

 New-ADObject -Name "HR-Record-Locator" -server (Get-ADForest).SchemaMaster -Path (Get-ADRootDSE).SchemaNamingContext -Type attributeSchema -Description "HR Record Locator" -DisplayName "HR Record Locator" -OtherAttributes @{ attributeID = '1.2.840.113556.1.8000.2554.52153.4519.39988.20098.33204.3098875.11650513'; adminDescription = "HR Record Locator"; adminDisplayName = "HR-Record-Locator"; omSyntax = '19'; schemaIDGuid = $SchemaIDGuid; ldapDisplayName = "HRRecordLocator"; rangeUpper = '250'; isSingleValued = $true; attributeSyntax = '2.5.5.5' }

Adding an auxiliary schema class

Like I mentioned previously, while you can add a new attribute directly to the user, group, or contact object class, we recommend as a best practice that you create an auxiliary class, add the attribute to that, and then add that auxiliary class to the object.  That way, you're grouping all of your custom stuff together, and it will be easier to identify for those who follow you.

Again, I'm going to take the PowerShell road, because it's my favorite and I much prefer it to creating screenshots on a plane.

 $Name = "Undocumented Features Auxiliary Class"
$attributes = @{
     adminDescription      = "Undocumented Features Auxiliary Class"
     objectClass           = "classSchema"
     ldapDisplayName       = "UndocumentedFeaturesAuxiliaryClass"
     adminDisplayName      = "Undocumented-Features-Aux-Class"
     objectClassCategory   = "3"
     systemOnly            = $false
     subclassOf            = "2.5.6.0" # subclass of "top" special class 
     rdnAttId              = "cn"
     governsId             = $parentOID #[oid created previously; if you create additional objects under this OID, this subclass will govern those as well]
}
New-ADObject -Name $Name -Type classSchema -Path (Get-ADRootDSE).SchemaNamingContext -OtherAttributes $attributes

Adding an attribute to an auxiliary schema class

Now that you've created your attribute and your auxiliary class, it's time to add the custom attribute "HR Locator Record" to the "Undocumented Features Auxiliary Class" auxiliary class.  It's like the Redundancy Department of Redundancy up in here. Yes, I know.

Note: When adding an attribute to a class, you'll need to use the LDAP Display Name value.  If you didn't specify this value when you created the attribute, it will be the name of the attribute with the spaces removed.

 $schemaContext = (Get-ADRootDSE).SchemaNamingContext
$classPath = Get-ADObject -SearchBase $schemaContext -Filter { name -eq "Undocumented Features Auxiliary Class" }
$classPath | Set-ADObject -Add @{maycontain='HRRecordLocator'}

Adding the auxiliary attribute class to the user structural class

FINALLY. WE'RE TO THE END OF ADDING THE ATTRIBUTE.  At this point, we just need to add the newly-created auxiliary class to the class(es) that we want this attribute to available to.  Since we want this attribute to be available for user objects, we're going to add it to the User class.

The syntax is pretty similar to that above.

 $schemaPath = (Get-ADRootDSE).schemaNamingContext
$NewAuxiliaryClass = Get-ADObject -SearchBase $schemaPath -Filter { name -eq "Undocumented Features Auxiliary Class" } -Properties governsID
$ExistingClass = Get-ADObject -SearchBase $schemaPath -Filter { name -eq "User" }
$ExistingClass | Set-ADObject -Add @{ auxiliaryClass = $($NewAuxiliaryClass.governsID) }

At this point, we'll go check ADSI Edit to see what we've got.

First, we can see that the HRRecordLocator attribute has been added to the schema:

And next, we can see that the Undocumented Features Auxiliary Class has been added as well. AND it has the HRRecordLocator set as an optional ("mayContain") attribute:

And, of course, we can see that the auxiliary class has been added to the User structural class.

And, for the moment of truth, we can open up Active Directory Users and Computers, find a user, and open the attribute editor tab:

Awesome! Next!

Adding attributes to AAD Connect

Whether you extended Active Directory to include your own attributes or just want to take advantage of unused attributes that already exist in your directory, you'll need to configure AAD Connect to import, synchronize, and export those attributes to Azure AD.  This process will also extend your Azure Active Directory schema.

Refreshing the schema

If you created one or more new attributes and you had previously installed AAD Connect, you'll need to refresh the AAD Connect schema cache.  When you install AAD Connect initially, it takes an inventory of your existing schema configuration and caches it.  When you update the AD Schema, you need to trigger AAD Connect to refresh its cache.

  1. Launch the AA Connect Synchronization Service.
  2. Select the Connectors tab.
  3. Select the Active Directory Domain Services connector representing the directory where you modified the schema.
  4. Right-click and select Refresh Schema... or click Refresh Schema from the Actions pane.
  5. Confirm the refresh action by clicking OK.
  6. If prompted, provide credentials.  You don't need to provide the credentials for the connector and can provide any admin account that can read the newly added schema classes.
  7. Verify that the schema has refreshed successfully and click Close.
  8. Exit the Synchronization Service Manager.

AAD Connect Setup

Now, it's time to reconfigure AAD Connect to use additional attributes.  Whether you added new attributes to your schema or are just wanting to use additional attributes in your current directory that aren't part of the default sync, the process is the same.

  1. From the desktop, launch the AAD Connect Setup.
  2. Click Configure.
  3. Select Customize synchronization options and click Next.
  4. Enter your credentials and click Next.
  5. Confirm your directory (as in, you shouldn't need to make any changes here) and click Next.
  6. On the Domain/OU Filtering page, click Next.
  7. Select the checkbox for Directory extension attribute sync and click Next.
  8. In the attribute list, select the new attributes you want to sync to Azure AD from the Available Attributes column, click the green arrow to move them to the Selected Attributes column, and then click Next. Note: The attribute list is cAsE-sEnSiTivE, so be sure you are entering the attribute name in the Search bar the same way you entered them into the directory.
  9. Click Next to continue through the end of the configuration screens without making any additional changes.
  10. On the Ready to Configure page, confirm the change and click Configure.
  11. Wait while the AAD Connect installer completes the configuration.
  12. Click Exit to close the wizard.

Check Synchronization Rules Editor for new rules

At this point, AAD Connect setup has made several changes to your configuration. You can view the synchronization rules that control the import, synchronization, and export of attributes to Office 365.  To locate the rules governing your new attribute:

Inbound Rule

  1. Launch the Synchronization Rules Editor.
  2. Under Direction, select Inbound. Under MV Object Type, select person. Under Connector, select the entry associated with the on-premises Active Directory. Under Connector Object Type, select user. Under Connector Attribute, select the attribute that was configured in AAD Connect setup.
  3. Select the rule and click Edit.
  4. Click No to view/edit the existing rule.
  5. Click on the Transformations tab to view the inbound synchronization rule.
  6. Click Cancel to close the rule.

Outbound Rule

  1. If it's not already open, launch the Synchronization Rules Editor.
  2. Under Direction, select Outbound. Under MV Object Type, select person. Under Connector, select the entry associated with Azure AD (typically, tenant.onmicrosoft.com - AAD). Under Connector Object Type, select user. Under Connector Attribute, locate the attribute that was configured in AAD Connect setup. It will be identified as extension_guid_attributeName.  Note: this is also how you will locate the attribute in the Azure AD portal when you create the rules for your dynamic group.
  3. Select the rule and click Edit.
  4. Click No to view/edit the existing rule.
  5. Click on the Transformations tab to view the outbound synchronization rule.
  6. Click Cancel to close the rule.

Test for the attribute value

I'm a big fan of the Russian proverb, "trust, but verify."  We've configured our system correctly, but it's important to verify that everything is working correctly.

Modify a user

  1. Locate a user in synchronization scope.  I located test user "Phebe B Valenti" in my on-premises AD environment and set the value of my test attribute.

     Set-ADUser -Identity Phebe.B.Valenti -Replace @{hRRecordLocator = "UF-CR2XJ1"}
    
  2. Trigger an AAD Connect Delta Synchronization Cycle.

     Start-ADSyncSyncCycle -PolicyType Delta
    

Confirm the results in AAD Connect

  1. Launch the Synchronization Service Manager.
  2. Select Connectors.
  3. Select the Azure AD Connector.
  4. Click the Updates link in the Export Statistics window pane.
  5. Select the object and click Properties.
  6. Notice that the attribute has been populated and exported to Azure Active Directory.
  7. Click Close and exit the Synchronization Service Manager.

Confirm the results in Azure AD

  1. Launch the Azure AD PowerShell and connect to Azure AD.

     $cred = Get-Credential
    Connect-AzureAD -Credential $cred
    
  2. Locate the Azure AD object ID for your user.  My test user is Phebe.B.Valenti.

     (Get-AzureADUser -SearchString phebe.b.valenti).ExtensionProperty
    

Configure a Dynamic Group in Azure AD with custom attributes

Now that you've configured AAD Connect to sync your additional attributes, it's time to actually get to the fun part.

And if you believe this is fun, I have a bridge to sell you .... ;-)

Create a Dynamic Group with PowerShell

While we can create a dynamic group in the browser, I'm going to do it via PowerShell because I can. :-)

  1. Launch the Azure AD PowerShell and connect to Azure AD.  Note: In order to use the New-AzureADMSGroup cmdlet, you must have the AzureADPreview module installed.

     $cred = Get-Credential
    Import-Module AzureADPreview -Force
    Connect-AzureAD -Credential $cred
    
  2. Create the group using the New-AzureADMSGroup cmdlet.  Note: MailEnabled is a required parameter, and it must be set to $False. Similarly, MailNickname is a required parameter. Put a value in that reflects the name of the group.

     New-AzureADMSGroup -DisplayName "HRRecordIsPopulated" -Description "HR Record Locator attribute is populated" -SecurityEnabled $true -GroupTypes "DynamicMembership" -MembershipRule "(user.extension_3325f5ea6fbc4e1c95b8373de1f840c7_hRRecordLocator -contains ""UF"")" -MembershipRuleProcessingState "On" -MailEnabled $false -MailNickname HRRecordLocator
    

  3. Check the group membership.  You can use the ID returned from the New-AzureADMSGroup cmdlet.

     Get-AzureADGroupMember -ObjectId 8a0e2da4-db90-4fb1-83be-0811413fc9b4 | select UserPrincipalName
    

Create a Dynamic Group with the Azure AD Portal

Ok, you can also do it this way via the Azure AD Portal, too. :-)

  1. Launch a browser, navigate to the Azure portal (https://portal.azure.com) and log in with a global admin credential.

  2. Select Active Directory | Groups.

  3. Click +New group.

  4. Under Group Type, select Security.  Under Group Name, enter a name for the group. Under Group Description, enter a description for the group. Under Membership Type, select Dynamic User.  Click Add dynamic query.

  5. Select Advanced Rule and enter a query.  In my case, we have already identified the name of the attribute that was being mapped to (See Outbound Rule or Confirm the attribute in Azure AD to see what cloud attribute the on-premises attribute is mapped to).

     (user.extension_<guid>_<attribute> -contains "UF")
    

  6. Click Add query.

  7. Click Create.

There you have it! Woot!

Summary

As you've seen, we can create custom attributes, link them to objects in your on-premises Active Directory, sync those values to Office 365, and then create dynamic groups based on them for whatever purposes you like (such as assigning Licenses using Group-Based Licensing).

Further reading

There are a number of fantastic resources out on the interwebs that I've bookmarked over the years to really dig down and learn more about this process.  I'd thoroughly encourage reading up on them if you're interested in pursuing this even further.

Active Directory Schema

Active Directory: How to add custom attribute to schema Active Directory Schema Active Directory Technical Specification ADSchema Characteristics of Attributes Creating Custom Active Directory Attributes Extending the Active Directory Schema Extending the Schema How the Active Directory Schema Works King of Identity: Adding and Updating ADS/LDS schema with PowerShell LDAP Representations Ldapwiki: How to get your own OID Obtaining an Object Identifier from Microsoft PowerShell and the Active Directory Schema RFC 2252 Show me how to do it: Create custom Active Directory Attributes for User Properties

Azure AD Connect

Azure AD Connect Directory Extensions Darryl Kegg's blog