Monitor Assignments of Container Management Labels to Groups with the Graph SDK
Recently, I was challenged to create a version of the script to monitor the assignment of sensitivity labels to Microsoft 365 groups (teams, groups, or sites, otherwise called “containers”) to use the Microsoft Graph instead of the Exchange Online management module. Groups that are assigned labels inherit a range of settings that control how the groups work, which is why this form of sensitivity labels are called container management labels. They are an excellent way of enforcing standard behavior for groups that store different kinds of information within an organization. Figure 1 shows details of a container management label as viewed through the Purview portal.
The big issue with container management labels is that a group owner can assign a different label at any time. Figure 2 shows how a team owner can change the assigned container management label. Owners being able to update the assigned label doesn’t sound serious, but it is because choosing a different label usually affects the controls applied to a group. For instance, changing a label might open up a group to guest accounts, and any guests added to the group have access to the files stored in the group’s SharePoint Online site. In some cases, this might be considered a form of security compromise. It’s therefore important to monitor groups to ensure that the container management label assigned to the group by administrators is not changed by a group owner.
As I describe elsewhere, some elements of the conversion, like finding groups without container management labels, are easy. Using the Microsoft Graph PowerShell SDK Get-MgGroup cmdlet to find groups is much faster than Exchange’s Get-UnifiedGroup cmdlet, mostly because the Exchange cmdlet fetches many more properties.
Graph Extensibility Options
However, the conversion faces a fundamental problem in that the Microsoft Graph only supports the Exchange custom attributes, otherwise known as on-premises extension attributes, for user and device objects. The script uses a custom attribute to store details about the container management label assigned to the group and checks that information against the currently-assigned label to know if the label is changed. To move away from the Exchange Online management module, we therefore need to use a different method to store details of the organization-approved sensitivity label assigned to Microsoft 365 groups.
This brings us to a discussion about extending the information that can be stored and accessed in the Microsoft Graph for group objects. Microsoft publishes a useful comparison of extensibility options for Graph resources to help developers to make the right choice.
Last year, I wrote about how to use directory extensions with user objects. Directory extensions would be a good choice to store details of container management labels. The extensions are strongly typed (we only need to store text), filterable, and can be used in membership rules for dynamic Microsoft 365 groups. You can even synchronize directory extensions to on-premises Active Directory through the AD Connect utility.
Schema extensions are similar to directory extensions and will also serve our purpose. The storage requirement is relatively simple. The only real need is for filter support because it will help to find groups that don’t have an assigned container management label. Besides, we haven’t yet written about using schema extensions in practice, so going down this route sounds like a good idea.
Defining a Schema Extension for Groups
Like directory extensions, schema extensions are anchored or tied to a registered Entra ID app. The app serves as a convenient holder to publish the extensions to the Microsoft Graph and is created without any permissions. All you need is the application identifier for the app, which is used as the owner of the schema extension when it is defined. Here’s how to use the New-MgApplication cmdlet to create a suitable app:
New-MgApplication -DisplayName 'Labels Extension App' -Description 'App to hold schema extensions for container management labels'
In this example, the application identifier for the owner app is 2166459d-0ec5-49be-bf41-a78565f5ff51 and the schema extension includes three properties to store the name and identifier of the container management label allocated to the group and the date when the last assignment occurred. The target type is defined as “Group,” meaning any kind of group supported by Entra ID. After defining the schema extension in a hash table, the New-MgSchemaExtension cmdlet adds the extension to the Graph:
$LabelExtensionSettings = @{ id = "office365itpros_labels" description = "Office 365 IT Pros Container Label Extensions" TargetTypes = @( "Group" ) owner = "2166459d-0ec5-49be-bf41-a78565f5ff51" properties = @( @{ name = "LabelId" type = "String" } @{ name = "LabelDisplayName" type = "String" } @{ name = "DateLabelUpdated" type = "DateTime" } ) } # Update the Graph schema New-MgSchemaExtension -BodyParameter $LabelExtensionSettings
The identifier (id) for the extension is office365itpros_labels. The first part is the domain prefix and the second is the extension name. The domain prefix must refer to a vanity domain owned by the tenant (in this case, office365itpros.com). If your tenant doesn’t have a vanity domain, you must omit the domain prefix and just name the extension. For instance, in a trial tenant that only has an onmicrosoft.com domain, Entra ID created the identifier as extv15cqd52_labels. If you attempt to use a domain that the tenant doesn’t own, Entra ID returns an error like this:
Your organization must own the namespace Labelsarefun as a part of one of the verified domains
Upgrading the Schema Extension
After adding the schema extension, it’s in a development status. This means that the owner app can access the extension to develop and test code. To make the extension available to other apps, like the Microsoft Graph PowerShell SDK, amend the definition to add the line status = “Available” (casing is important) immediately under the owner definition and then run the Update-MgSchemaExtension cmdlet to change the status.
Update-MgSchemaExtension -SchemaExtensionId "office365itpros_labels" -BodyParameter $LabelExtensionSettings
Running the Get-MgSchemaExtension cmdlet afterwards should report that the status of the schema extension is available. You can also see that the extension has three properties.
Get-MgSchemaExtension -Filter "id eq 'office365itpros_labels'" | Format-List Description : Office 365 IT Pros Container Label Extensions Id : office365itpros_labels Owner : 2166459d-0ec5-49be-bf41-a78565f5ff51 Properties : {LabelId, LabelDisplayName, DateLabelUpdated} Status : Available TargetTypes : {Group} AdditionalProperties : {}
To test that the extension works, let’s update a group. This code fetches the assigned label information for a group and writes it into the schema extension properties. The date property is in ISO 8601 format. After fetching the information, the code populates a hash table with the values for the schema extension properties. A second hash table holds the parameters for the group update, which in this case is just for the schema extension:
$Group = Get-MgGroup -Filter "displayName eq 'Industry News" -Property Id, DisplayName, assignedLabels $CurrentLabelName = $Group.assignedLabels.DisplayName $CurrentLabelId = $Group.assignedLabels.LabelId $DateLabelUpdated = Get-Date -format 'yyyy-MM-ddTHH:mm:ssZ' $LabelSchemaSettings = @{} $LabelSchemaSettings.Add("LabelId", $CurrentLabelId) $LabelSchemaSettings.Add("LabelDisplayName", $CurrentLabelName) $LabelSchemaSettings.Add("DateLabelUpdated", $DateLabelUpdated) $Parameters = @{} $Parameters.Add("office365itpros_labels", $LabelSchemaSettings) Update-MgGroup -GroupId $Group.Id -BodyParameter $Parameters
After the update, we should be able to fetch the schema extension properties for the group. The schema extension must be explicitly fetched. Casing is very important, and you must reference properties exactly as they are defined in the extension. This example fetches the last date for a label update:
$Group = Get-MgGroup -Filter "displayName eq 'Industry News'" -Property Id, DisplayName, AssignedLabels, office365itpros_labels $Group.additionalProperties.office365itpros_labels['DateLabelUpdated'] 2024-10-23T19:00:04Z
Schema extension properties are filterable, so they can be used to find groups with specific labels. For example:
Get-MgGroup -Filter "(groupTypes/any(c:c eq 'unified')) and (office365itpros_labels/labelid eq 'e42fd42e-7240-4df0-9d8f-d14658bcf7ce')"
Updating the Schema Extension Properties for Microsoft 365 Groups
To prepare for monitoring, we need to update the schema extension data for each group so that the current label name, label identifier, and date and time of the last update are stored. This code finds the current set of Microsoft 365 groups and loops through each group to update the schema extension properties. As you can see, much the same code is used as above.
[array]$Groups = Get-MgGroup -All -PageSize 500 -Filter "(groupTypes/any(c:c eq 'unified'))" -Property DisplayName, Id, assignedLabels, office365itpros_labels [int]$i = 0 ForEach ($Group in $Groups) { $i++ Write-Host ("Updating label schema for group {0} ({1}/{2})" -f $Group.DisplayName, $i, $Groups.count) $CurrentLabelName = $Group.assignedLabels.DisplayName $CurrentLabelId = $Group.assignedLabels.LabelId $DateLabelUpdated = Get-Date -format 'yyyy-MM-ddTHH:mm:ssZ' $LabelSchemaSettings = @{} $LabelSchemaSettings.Add("LabelId", $CurrentLabelId) $LabelSchemaSettings.Add("LabelDisplayName", $CurrentLabelName) $LabelSchemaSettings.Add("DateLabelUpdated", $DateLabelUpdated) $Parameters = @{} $Parameters.Add("office365itpros_labels", $LabelSchemaSettings) Try { Update-MgGroup -GroupId $Group.Id -BodyParameter $Parameters } Catch { Write-Host ("Failed to update group {0} with label {1}" -f $Group.DisplayName, $CurrentLabelName) } }
Before running the code above, it’s a good idea to find groups without an assigned label and update them with a default container management label. In other words, establish a good baseline by ensuring that every Microsoft 365 group has a container management label.
Monitoring Container Management Changes
With a functioning schema extension and groups populated with properties to hold details of assigned container management labels, we can write a script to check for changes made to labels that will revert to the “official” label if a change is detected. You can download the script I wrote to monitor container management changes for Microsoft 365 groups from GitHub.
The remaining question is how to monitor changes. My preferred approach is to use an Azure automation runbook operating on a scheduled basis, say once monthly. You can add some reporting to the script to email details of any label reassignments that are found. In fact, because it’s PowerShell, you can amend the code in any way you like.
Directory extensions or schema extensions are both good choices for storing extra data for groups. Before making your choice, be sure to read the Microsoft documentation and do some testing. In particular, understand what happens to extension data if someone removes the owning app.