Use Microsoft 365 Group Photos to Give Groups and Teams a Unique Identity
In the past, I have written about the value of assigning photos to Azure AD user accounts so that the photos can surface in places like Outlook, Teams, and the Microsoft 365 user profile card. The inevitable question arose if the same approach should be taken to assigning photos to Microsoft 365 groups.
I’ve always thought that giving a group or team a photo is a great way to enforce its identity and provide some additional visual interest when browsing lists of groups. This article covers the basics of how to assign photos to groups and teams through clients, which is the simplest way to do the job. However, the downside is that the group or team owner is responsible for finding an appropriate image and updating the group via OWA or Teams. Not every owner exercises great judgment about the image given to a group or team, which brings us to contemplate how to manage the central management of group photos.
Central Image Management 101
Let’s consider the pieces that might be needed to manage group images centrally. Among the questions that should be answered are:
- What groups come under central control? The easy answer is “all groups and teams,” but that might create a huge management problem. It might be better to concentrate on groups and teams with a specific classification or sensitivity label. For instance, it could be left to owners of groups intended for open discussion to decide what image to use, while any group or team used for a business purpose might come under central control.
- Are group owners allowed to override a centrally-applied image? Teams or OWA don’t know anything about central control and will cheerfully allow group owners to assign whatever image they want to the group. You can’t change the way the clients work, so if you don’t want owners to change group images, a different mechanism is needed. For example, an Azure Automation runbook to check that each group has the right image and, if not, to update the group to comply with whatever is the officially designated image.
Other guidelines, especially for owner-applied photos, should describe the kind of images acceptable to the organization to avoid situations where groups receive objectionable photos for one reason or another.
Group Mailboxes are the Key
Every Microsoft 365 group has a group mailbox used for purposes like the group calendar. Because it’s a mailbox, the Exchange Online management Set-UserPhoto and Get-UserPhoto cmdlets work against groups if you use the GroupMailbox parameter. For example, these commands set a new photo for a group and then retrieve the image metadata.
Set-UserPhoto -GroupMailbox -Identity "Board Member Discussions" -PictureData ([System.IO.File]::ReadAllBytes("C:\Temp\BoardMembers.png")) -Confirm:$False Get-UserPhoto -GroupMailbox -Identity "Board Member Discussions" Identity : BoardMembers_6c3405c6-9556-4c04-8c36-ca46eb6a44d8 PictureData : {255, 216, 255, 224...} Thumbprint : 1522057433 IsValid : True ObjectState : New
The Set-TeamPicture cmdlet is also available to update a group photo. This cmdlet only works with team-enabled groups, and a Get-TeamPicture cmdlet is unavailable. The assumption might be that administrators are only interested in updating team photos and never need to check if a team has a picture.
Default Images
This brings me to the default images displayed by Microsoft 365 applications if a team or group doesn’t have a photo. Figure 1 shows a list of teams. The two at the bottom of the list don’t have photos, so Teams shows two initials taken from the team display name on a colored shape (the color and shape used differs from application to application). Collaboration Central becomes (a shared channel hosted by another tenant) becomes CC, while System and Engineering Architects uses SE.
The interesting thing is that if you run Get-UserPhoto against a group that doesn’t have a team photo, the cmdlet reports that some image metadata exists:
Get-UserPhoto -GroupMailbox -Identity "System and Engineering Architects" Identity : AllArchitectsM365_bc00fcfc-5534-4298-9051-c0c1f5fa7775 PictureData : {255, 216, 255, 224...} Thumbprint : -1055175947 IsValid : True ObjectState : New
My theory is that the creation of a new group mailbox generates a default image composed of initials taken from the display name, which would account for the presence of the metadata. To test the theory, I removed the metadata with the Remove-UserPhoto cmdlet:
Remove-UserPhoto -GroupMailbox -Identity "System and Engineering Architects " -Confirm:$False
Running Get-UserPhoto against the group afterward causes the cmdlet to fail:
Get-UserPhoto -GroupMailbox -Identity "System and Engineering Architects" Write-ErrorMessage : |Microsoft.Exchange.Configuration.CmdletProxyException|Error on proxy command 'Get-UserPhoto -GroupMailbox:$True -Identity:'CN=AllArchitectsM365_bc00fcfc-5534-4298-9051-c0c1f5fa7775,OU=Office365itpros.onmicrosoft.com,OU...'' to server AM5PR0402MB2915.eurprd04.prod.outlook.com: Server version 15.20.6565.0000, Proxy method PSWS: InternalServerError: Error executing cmdlet : { "code": "InternalServerError", "message": "Error executing cmdlet", "details": [ { "code": "Client", "target": "EURPR04A002.prod.outlook.com/Microsoft Exchange Hosted Organizations/office365itpros.onmicrosoft.com/AllArchitectsM365_bc00fcfc-5534-4298-9051-c0c1f5fa7775", "message": "|Microsoft.Exchange.Data.Storage.UserPhotoNotFoundException|There is no photo stored here."
Looking at group properties with the Microsoft Entra admin center using the Graph X-Ray tool revealed that the Get-MgGroupPhotoContent cmdlet from the Microsoft Graph PowerShell SDK might give some insight. Here’s an example:
Get-MgGroupPhotoContent -GroupId "5c011293-7cc7-41c4-a0fc-3e3bb98db834" -OutFile "xxx.jpg"
Running the cmdlet against the group where I had removed the image returned an image, but when I ran the cmdlet against another group, the cmdlet exported a 12.8 KB image. Figure 2 shows the result of opening the image with Paint.
My interpretation is that Azure AD creates and stores default images for Microsoft 365 Groups. This approach makes sense because it’s easier and faster for applications to fetch the default image. Two other points come to mind. First, applications probably cache group images to avoid the need to fetch the data each time a user references a group. Second, if a default image isn’t available for a group, applications can generate a default image without storing it in the group mailbox. This action allows applications like the Entra ID admin center to support groups without mailboxes (like distribution lists and security groups). Pausing to generate an image is slower, but it keeps the show on the road.
Interestingly, user accounts don’t seem to have default images created for them. At least, neither the Get-UserPhoto nor the Get-MgUserPhotoContent cmdlets reported any image content for a newly-created mailbox.
Updating Group Photos Centrally
Interesting as it is to understand how Microsoft 365 manages default group photos, the original question remains: how to exert central control over group photos. A PowerShell script might do the following:
- Find all Microsoft 365 Groups subject to central management (all, those with specific labels, etc.).
- For each group, check if an approved photo is available and what its thumbprint is.
- Check the thumbprint against the picture metadata for the group. If it’s different, update the group with the approved photo.
So much for theory… Let’s plunge into some code. First, let’s define what groups to control and find them. The script defines two variables to hold the identifiers for sensitivity labels (run the Get-Label cmdlet from the compliance module to know what identifiers to use). The script then fetches all Microsoft 365 groups with the Get-UnifiedGroup cmdlet before applying a client-side filter to extract the groups assigned the two sensitivity labels. Unfortunately, Get-UnifiedGroup doesn’t support server-side filtering against label identifiers.
$LimitedAccessId = "d6cfd185-f31c-4508-ae40-229ff18a9919" $ConfidentialAccessId = "c99e52c6-f5ff-4050-9313-ca6a3a35710f" [array]$Groups = Get-UnifiedGroup -ResultSize Unlimited $Groups = $Groups | Where-Object {$_.SensitivityLabel -eq $LimitedAccessId -or $_.SensitivityLabel -eq $ConfidentialAccessId} | Sort-Object DisplayName Write-Host ("Scanning {0} groups to check photos..." -f $Groups.count)
To define the approved photos for groups, I use a CSV file. Any repository will do if you can read from it to create an array.
# Read in data about approved photos [array]$GroupPhotos = Import-csv c:\temp\GroupPhotos.csv
Figure 3 shows the data used to define the groups and their photos. You can see that in the thumbprint value for each photo. Keeping this allows the script to compare the current photo for a group with the expected value. If a mismatch occurs, it means that someone updated the official photo.
Next, the script loops through the set of groups. For each group, it checks to see if an entry exists in the control data for the group (meaning that the organization has assigned an official photo for the group). If an entry is available and a photo file is present, the script checks the thumbprint of the group’s image. If the thumbprint matches the stored value, the check passes. If not, the script updates the group with the approved photo. Here’s the code:
ForEach ($Group in $Groups) { $ExistingPhotoData = $Null # Do we have some photo data? $Photo = $GroupPhotos | Where-Object {$_.ExternalDirectoryObjectId -eq $Group.ExternalDirectoryObjectId} If ($Photo) { # We do! $PhotoThumbPrint = $Photo.Thumbprint If ($Photo.Photo) { $PhotoFile = $PhotoDirectory + $Photo.Photo } Else { $PhotoFile = $Null Write-Host ("Group {0} requires an approved photo but no entry is available in the photo list" -f $Group.DisplayName) } # Check if a photo file is where we expect it to be If ((Test-Path $PhotoFile) -eq $False) { Write-Host ("Group {0} requires an approved photo but the expected file is not available in {1}" -f $Group.DisplayName, $PhotoFile) $PhotoFile = $Null } Write-Host ("Checking photo for group {0}" -f $Group.DisplayName) $ExistingPhotoData = Get-UserPhoto -Identity $Group.ExternalDirectoryObjectId -GroupMailbox -ErrorAction SilentlyContinue If ($ExistingPhotoData.Thumbprint -eq $PhotoThumbprint) { Write-Host ("Group {0} has the approved photo" -f $Group.DisplayName) } If ($ExistingPhotoData.Thumbprint -ne $PhotoThumbprint -and $Null -ne $PhotoFile) { # Thumbprints don't match, so update with approved image Write-Host ("Thumbprint mismatch: Updating photo for {0} with {1}" -f $Group.DisplayName, $PhotoFile) -foregroundcolor Red Set-UserPhoto -GroupMailbox -Identity $Group.ExternalDirectoryObjectId -ErrorAction SilentlyContinue -PictureData ([System.IO.File]::ReadAllBytes($PhotoFile)) -Confirm:$False $ExistingPhotoData = Get-UserPhoto -Identity $Group.ExternalDirectoryObjectId -GroupMailbox -ErrorAction SilentlyContinue $Photo | Add-Member -NotePropertyName Thumbprint -NotePropertyValue $ExistingPhotoData.Thumbprint -force } ElseIf ($ExistingPhotoData.Thumbprint -ne $PhotoThumbprint -and $Null -eq $PhotoFile) { Write-Host ("Thumbprint mismatch detected for group {0} but no photo file available" -f $Group.displayName) -foregroundcolor Yellow } # End if for thumbprint check } # End if photo } # End Foreach group
After processing all groups, the script saves the control file to ensure updated thumbprints are used in the next run.
Moving to a Solution
The example script is available from GitHub. Its code is intended to illustrate the principles of a solution. It is not a fully-fledged production-level script and can do with better error handling, logging, etc. This is a good example of a process that works well as a scheduled Azure Automation runbook to check group photos regularly. The biggest changes required for Azure Automation are to use a managed identity for authentication and to download the control data from a network location. Overall, I hope that the article helps you understand how group photos work and the potential for central management, if that’s what the organization decides to do.
Microsoft Platform Migration Planning and Consolidation
Simplify migration planning, overcome migration challenges, and finish projects faster while minimizing the costs, risks and disruptions to users.