Last Update: Sep 04, 2024 | Published: Dec 16, 2020
In the past I have written about the desirability of assigning photos to guest users and how to use PowerShell to assign a default photo to guest users. Nice as it is to have photos for guest users, it’s even more important to have photos for tenant user accounts, especially in the pursuit of an accurate tenant directory. Photos make it easier for users to identify other people as they recognize faces more easily rather than the default two initials used when accounts are photo-less. Having a photo available makes features like the Microsoft 365 People card or the Teams organization view more visually attractive and in a sense, pleasing.
If you decide to have pictures for all accounts, the next question is what’s the best way to ensure that every user account gets a nice photo.
The obvious way is to ask people to upload their own photo. If the organization doesn’t block people from updating their photos through OWA mailbox policies, users can update photos in several ways, including Teams (upload photo through settings – Figure 1).
Users can also update their photo through their Office 365 profile, accessed by clicking their name in the right-hand side of any Office 365 browser app. This launches Delve, where they can change their photo (Figure 2).
The downside is that replying on users is likely to produce inconsistent outcomes. Some will comply and upload appropriate photos. Others will upload less than appropriate photos. And some will ignore the request on the basis that it wastes their precious time. The net result is that the tenant directory will include a mismatch of photos.
Which brings us to consider how organizations can perform centralized photo updates for user accounts. My previous articles (covering guest accounts) use cmdlets from the AzureAD PowerShell module.
These cmdlets work well for Azure AD objects like guest accounts. They exist because Azure AD is used by organizations who don’t have Office 365, but they’re not designed to manage user photos used by Office 365.
Instead, the Get-UserPhoto and Set-UserPhoto cmdlets are the right way to process photos for Office 365 users. Photo data is stored in the Exchange directory (EXODS) and synchronized with SharePoint Online, which keeps its own set of photos.
User pictures updated via Set-UserPhoto cmdlet are also synchronized to Azure AD. However, for some reason, the Get-AzureADUserThumbnailPhoto cmdlet doesn’t like these photos and returns an error if run against an account whose photo was updated with Set-UserPhoto. For example:
Get-AzureADUserThumbnailPhoto -ObjectId b8eef43d-6854-4d77-9e03-745cf2e11e11 Get-AzureADUserThumbnailPhoto : Error occurred while executing GetAzureADUserThumbnailPhoto Code: Request_ResourceNotFound Message: Resource 'thumbnailPhoto' does not exist or one of its queried reference-property objects are not present. RequestId: c8762999-c4aa-4512-991f-562792915b0d DateTimeStamp: Sat, 28 Nov 2020 12:42:50 GMT HttpStatusCode: NotFound
Whereas if a photo is updated with Set-AzureADThumbnailPhoto, the results from Get-AzureADThumbNailPhoto look like this:
Get-AzureADUserThumbnailPhoto -ObjectId $User.ObjectId Tag : PhysicalDimension : {Width=200, Height=120} Size : {Width=200, Height=120} Width : 200 Height : 120 HorizontalResolution : 96 VerticalResolution : 96 Flags : 77840 RawFormat : [ImageFormat: b96b3cae-0728-11d3-9d7b-0000f81ef32e] PixelFormat : Format24bppRgb Palette : System.Drawing.Imaging.ColorPalette FrameDimensionsList : {7462dc86-6180-4c7e-8e3f-ee7333a7a483} PropertyIdList : {274, 20625, 20624} PropertyItems : {274, 20625, 20624}
It would be nice if better photo synchronization existed between Office 365 and Azure AD, but at least photos work in the apps.
Different arrangements will suit different organizations. Some synchronize images from HR records. Some use commercially created apps, like Code Two Software’s (free) Photos for Office 365. Others create their own solution, such as writing some PowerShell to add pictures to accounts after they are created.
But let’s assume that a tenant directory has been allowed to degrade and many accounts are missing pictures. Coding a script to look for and fix the problem is straightforward. The first requirement is a repository of user photos. For testing, I created a simple folder of images, each with the same name as a mailbox alias (Figure 3). The ideal image characteristics are:
Next, the code must figure out if an account has a photo. This is easily done by running the Get-UserPhoto cmdlet. It’s possible to ignore this check if you decide that the account should be updated if a photo is available.
After the script figures out if a photo is available for an account, the last step is to run Set-UserPhoto to update the account.
The bare bones of a PowerShell script are below (you can download a more complete version of the script from GitHub). It’s worth noting that the two cmdlets are not fast. Get-UserPhoto takes a few seconds to check for an existing photo while Set-UserPhoto can take up to 45 seconds to update an account. Thankfully, this isn’t a script that should be needed very often.
$PhotoLocation = "c:TempUserPhotos" $Users = Get-ExoMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited ForEach ($User in $Users) { $PhotoExists = $Null # Is EXODS happy with the user photo information for the account? Write-Host "Checking photo for" $User.DisplayName $CheckPhoto = Get-UserPhoto -Identity $User.Alias -ErrorAction SilentlyContinue If (!$CheckPhoto) { # No photo found in mailbox $UserPhoto = $PhotoLocation + $User.UserPrincipalName.Split("@")[0]+".jpg" If (Test-Path $UserPhoto) { # Update the photo because we have a file Write-Host "Updating photo for" $User.DisplayName -Foregroundcolor Red Set-UserPhoto -Identity $User.Alias -PictureData ([System.IO.File]::ReadAllBytes($UserPhoto)) -Confirm:$False } Else { # No photo file available Write-Host "No photo file available for" $User.DisplayName } } }
If you need to remove an existing photo, run the Remove-UserPhoto cmdlet.
Remove-UserPhoto -Identity [email protected] -Confirm:$False
Following updates, it takes a little while for images to show up in all apps. Caching will eventually expire to allow the new photos replace the old. As you’d expect, browser apps are faster to display new photos than desktop apps. The Teams desktop client is very slow to pick up new photos.
Microsoft 365 Groups and teams can be assigned pictures too. Two cmdlets are available: Set-UserPhoto can update a group mailbox just like it can a user mailbox if you remember to add the GroupMailbox parameter. For example:
Set-UserPhoto -Identity "Privacy Advocates" -PictureData ([System.IO.File]::ReadAllBytes("c:tempprivacy.jpg")) -GroupMailbox
The new picture will be synchronized to other workloads like SharePoint Online, Yammer, and Planner.
Alternatively, a team owner (but oddly, not a global administrator) can run Set-TeamPicture. For example:
Set-TeamPicture -GroupId $ObjectId -ImagePath c:tempPrivacy.jpg
Until Microsoft upgrades Set-TeamPicture, use Set-UserPhoto because this allows a tenant administrator account to update any Microsoft 365 group in a tenant. And if the group gets a photo, its associated team will pick up the picture too. Get-UserPhoto also supports group mailboxes, so you could adapt the script shown above to look for groups missing photos and update them.