How to Update User Photos for Microsoft 365 Accounts

Photos Enhance the Tenant Directory

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.

User-Photo Uploads

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).

Image 1 Expand
Teams Upload Photo
Figure 1: Updating a user photo through Teams settings (image credit: Tony Redmond)


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).

Image 2 Expand
Delve Photo
Figure 2: Updating a user photo through Delve (image credit: Tony Redmond)


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.

PowerShell Cmdlets for User Account Photo Updates

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.

  • Get-AzureADUserThumbnailPhoto checks if an image is present in an account.
  • Set-AzureADUserThumbnailPhoto adds a new image to an account.

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.

Organization-Driven Photo Updates

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:

  • Resolution: 648 x 648 pixels. This is the largest resolution supported. Behind the scenes, Exchange Online generates smaller 64 x 64 and 96 x 96 pixel thumbnails for apps to use when small thumbnails are appropriate. Most digital photos are much larger (in pixels) so some resizing is needed. Square photos are best as they won’t be cropped. Usually, best results are obtained when the user faces directly into the camera.
  • Size: Less than 500 KB.
Image 3 Expand
User Photo Library
Figure 3: A simple user photo library (image credit: Tony Redmond)


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:\Temp\UserPhotos\"
$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.

Completing the Picture with Groups and Teams

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:\temp\privacy.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:\temp\Privacy.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.