Tag Archives: EMS

Azure MFA Report Dashboard in Azure Portal–The Good, The Bad and The Ugly

If you are working with EMS and implementing Azure AD, Intune, MDM, MAM, Information Protection and more, you can build yourself some great dashboards in the Azure Portal using tiles and pin blades to your customized dashboard. This is an example from my own workplace:

image

Often when I work with projects implementing Identity & Access, Conditional Access and Azure MFA, I wish I could have a dashboard to report on MFA registration, and be able to pin that to my EMS dashboard as shown above.

It might be in the future that Azure MFA registrations and methods will be native in the portal, but for now this information have to be retreived in another way. In this blog post I will show you how you can set up a solution for showing this information. I will use the Markdown Tile from the gallery for displaying this information, and in the end it will look like this:

I referred in the title of this blog post to the good, the bad and the ugly, and by that I mean the process of setting this up, because it starts easy enough in the beginning but it will get more “ugly” in the end 😉

The Good – Setting up the Markdown Tile

I will use the Markdown Tile for the content in my customized dashboard in my Azure Portal. The first part is easy to set up, just click Edit and find the Markdown tile from the gallery, as shown below:

image

Drag the tile to a place you want it on your dashboard, and the Edit the title, subtitle and content as prompted:

image

There is a sample content provided, other than that you can write your own markdown. I will not get into details on markdown format here, there is a lot of good guides for learning the format, for example this: https://guides.github.com/features/mastering-markdown/. I will however provide you with a sample for reporting MFA registrations and default methods. This is how I set up my markdown tile:

image

And here is a link to my github repository where you can get the complete MFAReport.md file sample:

https://github.com/skillriver/AzureMFADashboard/blob/master/MFAReport.md

Now we need to fill that markdown tile with some real Azure AD MFA report data to report on.

The Bad – PowerShell Script for getting MFA registration and methods to Markdown

So the “bad” news is that we are reliant on running some Azure AD PowerShell commands for getting user details for MFA registration and methods. For now we are also reliant on the Azure AD v1 PowerShell (MSOnline) Module, as the new v2 AzureAD Module does not yet have any methods to get MFA authentication data. We cannot use the Microsoft Graph API either to get MFA user data, but I expect that to change in the future.

So lets look at the script I use, and after authenticating and connecting to Azure AD in my tenant with Connect-MSOLService, I will run the following commands to get details from each user where there has been configured one or more StrongAuthenticationMethods, and Group on those methods and save the results to a hash table. The results are stored in the $authMethodsRegistered object variable. Similarly I run the command once more, filtering on only showing the methods that are set to default for each user, and save to the $authMethodsDefault variable.

# Connect to MSOnline PowerShell (Azure AD v1)
Connect-MsolService

# Get MFA Methods Registered as Hash Table
$authMethodsRegistered = Get-MsolUser -All | Where-Object {$_.StrongAuthenticationMethods -ne $null} | Select-Object -Property UserPrincipalName -ExpandProperty StrongAuthenticationMethods `
| Group-Object MethodType -AsHashTable -AsString

# Get Default MFA Methods as Hash Table
$authMethodsDefault = Get-MsolUser -All | Where-Object {$_.StrongAuthenticationMethods -ne $null} | Select-Object -Property UserPrincipalName -ExpandProperty StrongAuthenticationMethods `
| Where-Object {$_.IsDefault -eq $true} | Group-Object MethodType -AsHashTable -AsString

# Create a Custom Object for MFA Data
$authMethodsData = New-Object PSObject
$authMethodsData | Add-Member -MemberType NoteProperty -Name AuthPhoneRegistered -Value $authMethodsRegistered.TwoWayVoiceMobile.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name AuthPhoneAppRegistered -Value $authMethodsRegistered.PhoneAppOTP.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name OfficePhoneRegistered -Value $authMethodsRegistered.TwoWayVoiceOffice.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name AlternatePhoneRegistered -Value $authMethodsRegistered.TwoWayVoiceAlternateMobile.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name OneWaySMSDefault –Value $authMethodsDefault.OneWaySMS.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name PhoneAppNotificationDefault –Value $authMethodsDefault.PhoneAppNotification.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name PhoneAppOTPDefault –Value $authMethodsDefault.PhoneAppOTP.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name TwoWayVoiceMobileDefault –Value $authMethodsDefault.TwoWayVoiceMobile.Count
$authMethodsData | Add-Member -MemberType NoteProperty -Name TwoWayVoiceOfficeDefault –Value $authMethodsDefault.TwoWayVoiceOffice.Count

# Write to Markdown file
"## MFA Authentication Methods`n" | Set-Content .\MFAReport.md -Force -Encoding UTF8
"### Registered`n" | Add-Content .\MFAReport.md -Encoding UTF8
"The following methods has been registered by users:`n" | Add-Content .\MFAReport.md -Encoding UTF8
"| Method | Count |" | Add-Content .\MFAReport.md -Encoding UTF8
"|:-----------|:-----------|" | Add-Content .\MFAReport.md -Encoding UTF8
"| Authentication Phone | " + [string]$authMethodsData.AuthPhoneRegistered + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| Phone App | " + [string]$authMethodsData.AuthPhoneAppRegistered + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| Alternate Phone | " + [string]$authMethodsData.AlternatePhoneRegistered + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| Office Phone | " + [string]$authMethodsData.OfficePhoneRegistered + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"" | Add-Content .\MFAReport.md -Encoding UTF8
"### Default Method" | Add-Content .\MFAReport.md -Encoding UTF8
"The following methods has been configured as default by users:" | Add-Content .\MFAReport.md -Encoding UTF8
"" | Add-Content .\MFAReport.md -Encoding UTF8
"| Method | Count |" | Add-Content .\MFAReport.md -Encoding UTF8
"|:-----------|:-----------|" | Add-Content .\MFAReport.md -Encoding UTF8
"| OneWay SMS | " + [string]$authMethodsData.OneWaySMSDefault + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| Phone App Notification | " + [string]$authMethodsData.PhoneAppNotificationDefault + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| Phone App OTP | " + [string]$authMethodsData.PhoneAppOTPDefault + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| TwoWay Voice Mobile | " + [string]$authMethodsData.TwoWayVoiceMobileDefault + " |" | Add-Content .\MFAReport.md -Encoding UTF8
"| TwoWay Voice Office Phone | " + [string]$authMethodsData.TwoWayVoiceOfficeDefault + " |`n" | Add-Content .\MFAReport.md -Encoding UTF8
"Last reported " + [string](Get-Date) | Add-Content .\MFAReport.md -Encoding UTF8

"" | Add-Content .\MFAReport.md

The complete PowerShell script can be found at my GitHub repository here:

https://github.com/skillriver/AzureMFADashboard/blob/master/MFAStrongAuthenticationUserReport.ps1

So now we have a script where we can get MFA authentication details for each user and create a markdown file that we can use in the tile in the Azure Portal custom dashboard. But it is all a manual process now, and it works fine for an ad hoc update. If we want to automate however, we have to get into the “ugly” stuff 😉

The Ugly – Automating Markdown creation and update Dashboard

This part requires multiple steps. First we need to schedule and run the PowerShell commands from above. Then we need to find a way to update the customized dashboard tile with the updated markdown file. To summary, this is what we need now:

  • Schedule the PowerShell script to run automatically. We can use Azure Automation for that.
  • Programmatically change the markdown tile in the customized dashboard. We can use Azure Resource Manager Rest API for that.

Lets get into the Azure Automation solution first. To run a PowerShell script I will need to create a Runbook, and in that Runbook I need to authenticate to Azure AD. I can define a Credential Asset with a username and password for a global admin user, but I like to use the least privilege possible, and besides that all my global admins are either protected by Azure AD PIM and/or MFA, so that won’t work. I prefer to use a service principal whereever possible, but after testing extensively with Connect-MSOLService that is not supported either.

So I tested with a dedicated Azure AD credential account, first by only adding the user to the Directory Readers role. I was able to list all users with Get-MSOLUser, but not any StrongAuthentication info. Neither did it work with Security Readers. In the end I added the user account to User Administrator role in Azure AD, and I was successful getting StrongAuthentication methods.

So, in my automation accont I will add or reuse my credentials:

image

Next, I will create a new PowerShell script based Runbook, basically I will use the PowerShell script from earlier in the blog, but with a couple of added parameter and getting the credential using the Get-PSAutomationCredential method. This is how it looks, you will get link to the complete script later:

image

And after testing, I can see that I successfully will get the MFAReport.md content (added a Get-Content .\MFAReport.md at the end to display the output):

image

Now that we have a solution for running the PowerShell script and generating the markdown file, the next part is how to update that data in the custom dashboard. And for that we need to look into programatically changing Azure Portal dashboards. There is a good resource and starting point for that in this article: https://docs.microsoft.com/en-us/azure/azure-portal/azure-portal-dashboards-create-programmatically.

First you need to share the custom dashboard, remember to include the markdown tile we set up in the first part of this blog post. At the top in the portal dashboard, select the Share button:

image

By sharing the dashboard will published as an Azure resource. Specify a name, select the subscription and either use the default dashboard resource group or select an existing one:

image

Go to Resource Explorer in the Portal:

image

Navigate to Subscriptions, Resource Groups, and find the resource group and resource containing the custom dashboard. From there you will be able to see the JSON definition of the dashboard and specifically the markdown tile containing the content we want to update:

image

So for next process now we need to copy this complete JSON definition containing all your tiles including the markdown tile. Locally on your computer, create a .json file in your favorite JSON editor, I use Visual Studio Code for this, and paste in the content. I have named my file DeploymentTemplateMFAReport.json.

Now we need to change this template to be ready for deployment, and for that we need to add or change a couple of things. First, in the start of the JSON file, add schema and versioning, and parameters, variables and resources section like I have shown below in line 1-17:

image

I have chosen to use 3 parameters, the markdown content itself, and name of the dashboard and the title of the dashboard.

Next, find the tile for the markdown content, and change the content value to the value of the parameter, like I have done at line 113 here:

image

And last, at the end of the json template, add and change the following settings, I have used my parameters for the dashboard name and the dashboard title here in the lines 401-411:

image

My deployment template for the customized dashboard is now completely general and can be used in every environment. In fact you are welcome to borrow and use my template from above her, I have included it in my github repository:

https://github.com/skillriver/AzureMFADashboard/blob/master/DeploymentTemplateMFAReport.json

Working locally in my Visual Studio Code editor, I can now test the deployment using Azure PowerShell, as shown below and described with these simple steps:

  1. Connect to Azure Resource Manager and select the Subscription
  2. Specify a variable for the Resource Group you want to deploy to
  3. The MFAreport.md file (which we created earlier) need some converting to JSON format, I’m removing all espace characters and any uneeded special characters
  4. Specify variable names your environment for name and title for the dashboard
  5. Deploy the custom dashboard to the resource group

image

However, now that we can test the deployment, I want to schedule a deployment using Azure Automation, and I will continue on my previous runbook from before. But first we need to set up some connections for authenticating to Azure and some variables.

I Azure Automation we can create an Azure Run As Account, this will also create a service principal. If you navigate to your Automation Account in the Azure Portal, and go to the section called Run as accounts, you can create an Azure Run As Account automatically, as I have done here:

image

If I look more closely at this generated Run As Account, I can see details for Azure AD App Registration, Service Principal, Certificate and more. This account will also automatically be assigned Contributor role for my Azure Subscription. If you want more control over Azure Run As Accounts, you can create your own as described in the following article: https://docs.microsoft.com/en-us/azure/automation/automation-create-runas-account

image

I will use this Azure Run As account in my environment to deploy the dashboard resource, I’ll just need to make sure the account has contributor access to the resource group. Next I will set ut a few variables under the Variables section for my Automation Account, I will use these variables when I deploy the resource:

image

Now we are ready to finally put together the complete Runbook and test it. You will have the complete link later in the blog post, but I will share some screenshots first:

After I’ve connected with Connect-MSOLService I’m creating a variable for the markdown content, so I’ve changed from earlier when I saved a .md file temporarily, now I just adding lines using the newline special character (`n):

image

The next part is for logging in to Azure (using the Azure Run As Account mentioned above), and then getting my variables ready for deployment:

image

Then I convert the markdown content to Json format, and removing any escape characters that I don’t need:

image

And then deploy the dashboard resource with parameters for markdown content and dashboard name & title. Note that I’m using my deployment template as a source from my github repository via the TemplateUri property:

image

You can use any TemplateUri you want, for example from a more private source like a storage account blob etc.

Testing the Runbook gives the following output, which shows it was successful:

image

When I now go and refresh the dasboard in the portal, I can see that the markdown tile has been updated:

image

That leaves me with just publishing and scheduling the runbook:

image

When creating a new schedule specify a name and recurrence:

image

Link the schedule to the runbook and any needed parameters, I have to specify my credential that are allowed to Connect-MSOLService:

image

That concludes this lengthy blog post. The script will now run regularly and update my custom markdown tile with MFA report data.

Here is the link to the PowerShell script used in my Azure Automation runbook, enjoy your MFA Reporting!

https://github.com/skillriver/AzureMFADashboard/blob/master/MFAStrongAuthenticationUserReportAutomation.ps1

 

Assign EMS License with Azure AD v2 PowerShell and Dynamic Groups

While we are waiting for support for group based licensing in the Azure AD Portal I have created this Azure AD v2 PowerShell solution for assigning EMS (Enterprise Mobility + Security) license plans using Azure AD v2 PowerShell module and Dynamic Groups.

The PowerShell CmdLets used here requires the Azure AD v2 PowerShell Module, which you can read about how to install or update here: https://gist.github.com/skillriver/35fba9647fbfbe3e99718f0ad734b241

Source of Authority, Attributes, Sync and Dynamic Groups

In my scenario I want to use extension attributes to automatically calculate membership using Dynamic Groups in Azure AD. The members of these groups will be assigned the EMS licenses.

Most organizations will have an on-premises Active Directory synchronizing to Azure AD, so the source of authority is important for where I set the value of the extension attributes, as I want my Dynamic Groups to calculate membership for both On-premise and Cloud based users (I have some Cloud based admin account I want to license as well).

So, lets take a look at my local Active Directory environment. If you have Exchange installed in your organization, you will have extended the schema with extensionAttribute1..15.

But in my case, I never have installed any versions of Exchange in my current environment, and only used Exhange Online, so I don’t have those attributes. Instead I have msDS-cloudExtensionAttribute1..20.

So I decided on using the following attributes locally in AD:

image

I have previously used ENTERPRISEPACK (SkuPartNumber for Office 365 E3) for licensing Office 365 E3 plans. In this scenario I will use the msDS-cloudExtensionAttribute2 for either EMS (SkuPartNumber for EMS E3) or EMSPREMIUM (SkuPartNumber for EMS E5).

You can also use Active Directory PowerShell to set these values on-premises:

image

Note that if I had Exchange installed, I could just have used extensionAttribute1 and extensionAttribute2, and these would be automatically synchronized to Azure AD in an Exchange Hybrid deployment. However, in my case I need to manually specify the option for Directory extension attribute sync in Azure AD Connect:

image

And then selecting to synchronize those two selected attributes:

image

After these Directory extensions are configured and synchronized to Azure AD, I can check these attributes with the following AAD v2 command:

Get-AzureADUser –ObjectId <youruser> | Select -ExpandProperty ExtensionProperty

In my environment I will find these attributes:

image

Note that the msDS_cloudExtensionAttribute1..2 has now been created in Azure AD for me, and been prefixed with extension_<GUID>_, where the GUID represent the Tenant Schema Extension App:

image

So now I know that my on-premises users with values for msDS_cloudExtensionAttribute1..2 will be synchronized to the extension attributes in Azure AD. But what about users that are source from Cloud? There are no graphical way to set these extension attributes, so we will have to do that with Azure AD v2 PowerShell. In my example I have a Cloud admin account I want to set this attribute extension for (scripts are linked later in the blog):

image

With that, I now have configured the users I want with the extension attribute values, and are ready to create the Dynamic Groups.

Creating Dynamic Groups for Assigning EMS Licenses

Earlier in the blog post I mentioned that I wanted to use the msDS_cloudExtensionAttribute2 for assigning either EMS E3 or EMS E5 licenses. If I run the following command, I get my Subscriptions, here listed by SkuId an SkuPartNumber. EMSPREMIUM refers to EMS E5, while EMS refers to the original EMS which is now E3.

image

On that basis I will create 2 Dynamic Groups, one that looks for EMSPREMIUM and one that looks for EMS in the extension attribute. You can create Dynamic Groups in the new Azure AD Portal, or by running these PowerShell commands:

image

After a while memberships in these dynamic groups will be processed, and I can check members with the following commands:

image

In my environment I will have this returned, showing users with membership in the EMS E3 and EMS E5 group respectively:

image

Before I proceed I will save these memberships to objects variables:

image

Assigning the EMS licenses based on group membership

With users, attributes and dynamic groups membership prepared, I can run the actual PowerShell commands for assigning the licenses. I also want to make sure that any users previously assigned to another EMS license will be changed to reflect the new, so that they are not double licensed. Meaning, if a user already has an EMS E3 license, and the script adds EMS E5, I will remove the EMS E3 and vice versa.

The full script is linked below, but I will go through the main parts here first. First I will save the SkuId for the EMS subscriptions:

image

Then I will loop through the membership objects saved earlier:

image

Next, create License Object for adding and removing license:

image

Then create a AssignedLicenses object, adding the AssignedLicense object from above. In addition, I check if the user has an existing EMS license to be removed, and if so add that SkuId to RemoveLicenses. If there are no license to remove, I still need to specify an empty array for RemoveLicenses.

image

And then, update the user at the end of the loop:

image

After looping through the EMS E3 members, a similar loop through EMS E5 members:

image

So to summarize, with this script commands you can assign either EMS E3 or E5 licenses based on user membership in Dynamic Groups controlled by extension attributes. In a later blog post I will show how we can consistenly apply these licenses, stay tuned!

Link to the full script is below:


# PowerShell CmdLets for Assigning EMS Licenses with Azure AD v2 PowerShell Module
# Read blog post for details: https://gotoguy.blog/2017/02/17/assign-ems-license-with-azure-ad-v2-powershell-and-dynamic-groups/
# Connect to Azure AD with Global Administrator
Connect-AzureAD
# List Subscriptions
Get-AzureADSubscribedSku | Select SkuId, SkuPartNumber
# EMS E3 license Service Plans
$EMSlicense = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq 'EMS'}
# EMS E5 license Service Plans
$EMSpremiumlicense = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq 'EMSPREMIUM'}
# Create a Dynamic Group for EMS E3 Users to be Licensed
New-AzureADMSGroup -DisplayName "EMS E3 Licensed Users" -Description "Dynamic group for EMS E3 Users" `
-SecurityEnabled $true -MailEnabled $false -MailNickname "EMSE3Users" -GroupTypes "DynamicMembership" `
-MembershipRule "(user.extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2 -eq ""EMS"")" `
-MembershipRuleProcessingState "On"
# Create a Dynamic Group for EMS E5 Users to be Licensed
New-AzureADMSGroup -DisplayName "EMS E5 Licensed Users" -Description "Dynamic group for EMS E5 Users" `
-SecurityEnabled $true -MailEnabled $false -MailNickname "EMSE5Users" -GroupTypes "DynamicMembership" `
-MembershipRule "(user.extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2 -eq ""EMSPREMIUM"")" `
-MembershipRuleProcessingState "On"
# Get Group and members
$EMSE3Group = Get-AzureADMSGroup -SearchString "EMS E3 Licensed Users"
# Check if membership has been processed, wait and try again if not yet
Get-AzureADGroupMember -ObjectId $EMSE3Group.Id
$EMSE5Group = Get-AzureADMSGroup -SearchString "EMS E5 Licensed Users"
# Check if membership has been processed, wait and try again if not yet
Get-AzureADGroupMember -ObjectId $EMSE5Group.Id
# Save members to object variable
$membersEMSE3 = Get-AzureADGroupMember -ObjectId $EMSE3Group.Id
$membersEMSE5 = Get-AzureADGroupMember -ObjectId $EMSE5Group.Id
#region EMS License Management for Dynamic Group Membership
# Get SkuId for EMS E5 (EMSPREMIUM) and EMS
$EmsE3SkuId = (Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq 'EMS'}).SkuId
$EmsE5SkuId = (Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq 'EMSPREMIUM'}).SkuId
# Loop through EMS E3 Members
ForEach ($member in $membersEMSE3) {
# Get the user
$User = Get-AzureADUser -ObjectId $member.ObjectId
# Create a License Object for assigning the EMS E3 SkuId
$AddLicense = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$AddLicense.SkuId = $EmsE3SkuId
# Create a License Object for removing the EMS E5 SkuId
$RemoveLicense = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$RemoveLicense.SkuId = $EmsE5SkuId
# Create a Licenses Object for Adding and Removing the Licenses
$Licenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$Licenses.AddLicenses = $AddLicense
# Check if the User has license to be removed
If ($user.AssignedLicenses | Where-Object {$_.SkuId -eq $EmsE5SkuId}) {
$Licenses.RemoveLicenses = $RemoveLicense.SkuId
}
Else { $Licenses.RemoveLicenses = @() }
# And lastly, update User license with added and removed licenses
Set-AzureADUserLicense -ObjectId $User.ObjectId -AssignedLicenses $Licenses
}
# Loop through EMS E5 Members
ForEach ($member in $membersEMSE5) {
# Get the user
$User = Get-AzureADUser -ObjectId $member.ObjectId
# Create a License Object for assigning the EMS E5 SkuId
$AddLicense = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$AddLicense.SkuId = $EmsE5SkuId
# Create a License Object for removing the EMS E3 SkuId
$RemoveLicense = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$RemoveLicense.SkuId = $EmsE3SkuId
# Create a Licenses Object for Adding and Removing the Licenses
$Licenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$Licenses.AddLicenses = $AddLicense
# Check if the User has license to be removed
If ($user.AssignedLicenses | Where-Object {$_.SkuId -eq $EmsE3SkuId}) {
$Licenses.RemoveLicenses = $RemoveLicense.SkuId
}
Else { $Licenses.RemoveLicenses = @() }
# And lastly, update User license with added and removed licenses
Set-AzureADUserLicense -ObjectId $User.ObjectId -AssignedLicenses $Licenses
}
#endregion

Link to script for managing and listing extension attribute properties for your users:


# Azure AD v2 PowerShell Module CmdLets for working with Extension Attribute Properties
# Connect to Azure AD with Global Administrator
Connect-AzureAD
# Get a User and Read Extension Properties
$aadUser = Get-AzureADUser -ObjectId <youruser>
$aadUser | Select -ExpandProperty ExtensionProperty
# Serialize User Object to JSON
$aadUser.ToJson()
# Explore Object Properties
$aadUser | Get-Member
# How to: Add Extension Properties
# PS! Can only write to Cloud homed users
$aadUser = Get-AzureADUser -ObjectId <yourclouduser>@elven.onmicrosoft.com
$extensionProp = New-Object "System.Collections.Generic.Dictionary“2[System.String,System.String]"
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1','ENTERPRISEPACK')
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2','EMSPREMIUM')
Set-AzureADUser -ObjectId $aadUser.ObjectId -ExtensionProperty $extensionProp
# Check added Extension Properties
Get-AzureADUser -ObjectId <yourclouduser>@elven.onmicrosoft.com | Select -ExpandProperty ExtensionProperty
#region List all users with Extension Properties
$aadUsers = Get-AzureADUser | Select DisplayName, ObjectId
$aadUsersExt = @()
ForEach ($aadUser in $aadUsers) {
$user = Get-AzureADUser -ObjectId $aadUser.ObjectId | Select ObjectId, DisplayName
$userDetail = Get-AzureADUser -ObjectId $aadUser.ObjectId | Select -ExpandProperty ExtensionProperty
        foreach ($key in $userDetail.Keys)
        {
            if($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1")
            {
                $ext1 = $userDetail."$key"
            }
            elseif($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2")
            {
                $ext2 = $userDetail."$key"
            }
else { $ext1 = ""; $ext2 = "" }
        }
$obj = [pscustomobject]@{"DisplayName"=$user.DisplayName; "ObjectId"=$user.ObjectId; "Ext1"=$ext1; "Ext2"=$ext2}
$aadUsersExt += $obj
}
# List only users with values for extension attributes
$aadUsersExt | Where {$_.Ext1 -or $_.Ext2} | FT
#endregion
# List all users
$aadUsersExt
# Serialize users and extension attributes to JSON
$aadUsersExt | ConvertTo-Json