Profile Photos and Graph API

Loads of posts on this topic on the Interwebs. Let’s add one more as I spent some time today and yesterday researching on this. Got a whole bunch of tabs to close and I don’t want to forget what I learnt. :)

(Update: I’ll be updating this post as I learn more stuff and they will be marked similar to this update paragraph).

It is possible to update profile photos via Graph API. However, v1.0 of the API only works with Exchange Online and not Azure AD, so the user account you are trying to update must be present in Exchange Online. This link tells you about the profilePhoto resource:

A profile photo of a user, group or an Outlook contact accessed from Exchange Online. It’s binary data not encoded in base-64.

The supported sizes of HD photos on Exchange Online are as follows: ’48×48′, ’64×64′, ’96×96′, ‘120×120’, ‘240×240’, ‘360×360′,’432×432’, ‘504×504’, and ‘648×648’.

The beta API supports Azure AD too so it works with accounts that don’t have Exchange Online mailboxes. However, a) this is a beta API, and b) updating in Azure AD doesn’t do much as things like SharePoint Online don’t look to Azure AD for the photos… so it’s a bit of a bummer.

This link from Microsoft is a good starting point on how the photo syncing works. Go read it…

Azure AD and on-prem AD both store thumbnails, not the full hi-res photo. If you have a thumbnail in on-prem AD that is synced over to Azure AD. From Azure AD it is synced over to Exchange Online, and the latter is kind of your primary source for everything else going forward.

However (this a word that’ll keep coming up haha!) … in the case of photos set on-prem, the sync from Azure AD to Exchange Online only happens the first time. Subsequent changes are not synced over. Also, if you sync from AD to AAD to Exchange Online what you have are the measly thumbnails… so best to avoid this route perhaps, and go directly to the source.

The “source” in this case is Exchange Online, as everything looks to it for photos. Exchange Online has its own directory service (EXchange Online Directory Services – aka. EXODS) and that stores hi-res photos. (As an aside: other workloads, like SharePoint Online too have their own Directory Services.. they all sync to AAD and each other, but in the case of photos EXODS is the important one).

To update Exchange Online with photos one can use the Set-UserPhoto cmdlet from the Exchange Online PowerShell module. Or the Graph API v1.0 as I mentioned earlier. The Graph API has a limitation that REST requests can only be 4MB or less, so the photo has to be less than 4MB in size. If that is a problem for you, Set-UserPhoto is the only alternative.

The following picture is from Microsoft:

If you set a photo in Exchange Online this gets synced over to Azure AD too (not sure if it syncs back to on-prem AD). SharePoint Online then pulls photos for itself from Exchange Online (this is what is shown in SharePoint Online & OneDrive). SharePoint Online being the base for a lot of things (e.g. Teams) I imagine this final sync is what a lot of users see.

The sync of photos from Exchange Online to SharePoint Online happens each time the user “requests their own photo in SharePoint Online” (which I imagine is when a user clicks on the top right corner to see their profile; in my limited testing the photo only appeared after I clicked there). As a result of this SharePoint Online pulls the photo from Exchange Online and creates three resized photos – a large, medium, and small – which are stored in the “https://${tenantName}-my.sharepoint.com/User Photos/Profile Pictures” site. This is accessible read-only to everyone and has JPG files named based on the UPN. The filenames have the following pattern from what I see in my environment:

However, what if a user does not have an Exchange Online mailbox? In that case there is no EXODS as a source for photos as the user is not present in that directory. The beta Graph API can update photos in AAD, or they can be synced (thumbnails) from on-prem AD to AAD, but workloads like SharePoint Online do not look to AAD for photos. (I keep saying “workloads” but I am not sure what else is there apart from SharePoint Online… everything seems to be Exchange Online or SharePoint Online these days, so “workloads” is kind of the grand sounding “we” as far as I am concerned). Instead, we can manage photos directly in SharePoint Online.

If a user wants to do this they can go to their SharePoint Online or OneDrive site, click the top right corner, go to their “Office Profile”, and update their profile photo in the page they are then taken to. (During my testing the final update either happened on the Delve site I was taken to, or a SharePoint Online page I was redirected to – I am not sure why this difference. It happened with the same accounts on subsequent tests). Because SharePoint Online syncs with Exchange Online, if a user who has an Exchange Online mailbox does this the photo will update on Exchange Online as well as Azure AD. But if the user does not have Exchange Online then the photo stays with SharePoint Online and does not sync to Azure AD – so you end up with separate photos in Azure AD and SPO unless the same photo is added to both places.

(Update: When I tried to change the photo for an account which did not have Exchange Online mailbox, and it took me to the Delve page where I uploaded a photo, it did not update the photo with SharePoint Online. It didn’t update Azure AD either which was expected, but surprising that it didn’t update SharePoint Online. (Side note: At this point I am starting to lose it!) Here is the SharePoint Online Url as that seems to be more reliable than Delve and it doesn’t always appear: “https://${tenantName}-my.sharepoint.com//_layouts/15/editprofile.aspx” For accounts without Exchange Online mailboxes updating the photo via this Url always updates SharePoint Online, whereas the one via Delve is a hit and miss. Also, updating in SharePoint Online updates Delve too).

The following picture is from Microsoft:

I was hoping that in a hybrid scenario if my user mailbox was on-prem and I used the Set-UserPhoto to set a photo on-prem it might sync over to Azure AD and into SharePoint Online… but it does not. It didn’t even sync to Azure AD in my case (but that could just be me being impatient and the on-prem DC sync not completing for Azure AD connect). Even if it did sync to Azure AD that would be pointless as SharePoint Online does not look to Azure AD (there’s even a UserVoice ticket on this with no reponses from MS).

(Update: Useful info from this tool page: If you have Exchange Online available in your Office 365 tenant, but user mailboxes are on-premises, the thumbnail attribute does end up in Exchange Online for a user, it just doesn’t sync to SharePoint Online. If you would like to use the Exchange Online picture as a source for SPO, you can get the source image from Exchange Online for any user using their Rest API, which is a simple URL. E.g. https://outlook.office365.com/ews/Exchange.asmx/s/GetUserPhoto?email=user1@contoso.com&size=HR648x648).

==

So ignoring on-prem AD for now, what is the best way to update profile photos? If a user has Exchange Online then do so via the Graph API or Set-UserPhoto. If the user does not have Exchange Online then do so via the beta Graph API and some SharePoint scriptery to update the photo there.

If one tries to use the Graph API to update the photo of a user who does not have an Exchange Online mailbox it returns an error:

As an aside, there’s a different error if the account is not licensed at all:
So something like this would be the way to go:

For completeness I should add that there’s also a Set-AzureADUserThumbnailPhoto cmdlet that can update Azure AD. That could be an alternative to the Graph API call above; however it only sets a small thumbnail whereas Graph can set a larger picture (< 4MB).

If you want to go the Set-UserPhoto here’s the syntax:

==

Is there a way to set a user’s profile photo via SharePoint Online as an admin? You know, for the second case above where the use does not have an Exchange Online mailbox… I dug around on this today and have a better understanding.

(I am using PnP.PowerShell in the examples below, but the other SharePoint module too might work… I haven’t tried).

First let’s see how the SharePoint Online user profile points to the profile photo:

From this I can see there is a Url pointer to the medium sized thumbnail. So it sounds like the first step is to upload the photos to the “User Photos/Profile Pictures” location and then update the Url.

Googling on this brought me to this blog post that does exactly that. The associated repo in turn has two scripts – one for uploading photos, the other for setting the profile property. I wanted to fiddle a bit more to get familiar with these cmdlets…

First things first, I realized that to upload to or see the “User Photos/Profile Pictures” location I need to connect to the -my SharePoint Url. So not “https://${tenantName}.sharepoint.com” but “https://${tenantName}-my.sharepoint.com“. After that I can use cmdlets like Get-PnPFolder to view a folder, Get-PnPFile to get a file, and so on…

This doesn’t show me the contents actually. I thought I could view the contents via Find-PnPFile but that gives me an error:

Found others too with similar errors. Could just be a wrong syntax on my part too… not sure.

If I want to see whether a specific filename exists though, that’s possible. Say I want to see if the medium thumbnail photo for my UPN exists:

If it exists the cmdlet succeeds (and shows the filename as output). The docs say this cmdlet downloads the file, but that didn’t seem to be the case.

If the file does not exist, Get-PnPFile does not treat the error as terminating. So to check whether the photo file exists, and if it does not then update you can do something like this:

To upload a photo file do the following:

And to remove a photo file:

Another interesting thing I found while Googling on this (via this script) is the SPS-PicturePlaceholderState property. If (Get-PnPUserProfileProperty -Account $UPN).UserProfileProperties."SPS-PicturePlaceholderState" is 0 it means a profile picture has been set, but if it is 1 or empty then no picture is set. So that’s one more thing to update if one were to set SharePoint Online photos manually.

(Update: I don’t know much about this but there’s also a property called SPS-PictureExchangeSyncState which if set to 0 means the photo is being synced from EXO, while if it is 1 or empty then the photo is manual. I couldn’t find much info on the property except that many scripts set it to 0, and there are blog posts that suggest setting it to 1 if you SPO photos aren’t syncing from EXO and you want to manually set a photo but are unable to. Apparently setting it to 1 and then setting the photo helps. See this and this blog posts. Also).

Another useful script (thanks to this script I mentioned earlier) is the Resize-Image gist you can find here. This is super-handy to resize pictures to the three sizes you want.

Putting all this together I now have the following:

This tries to set the profile photo via Graph API (v1.0). If it fails, it checks if the error is to do with the mailbox being on-prem. If that’s the error, it sets the AAD photo via Graph API (beta) and resizes the photo into the three sizes, uploads to SharePoint Online, sets it for the user profile, and changes the placeholder state property in the SharePoint Online property to reflect that it is no longer a placeholder image. Be sure to include the Resize-Image gist as a function; alternatively, ensure the source files are already resized and skip that step. (Update: The Set-PnPUserProfileProperty does not work as of this writing. See this GitHub issue. Bummer! Workaround is to grant permissions via SharePoint APIs in addition to the Azure AD ones).

That’s all then!

Oh, one last thing. By default users can update their photos themselves. If that’s not desired, be sure to turn it off.

Updates:

  • Shout-out to this post that goes into the various ways to get the SharePoint Online picture. Good to know.
  • Wouldn’t be complete if I didn’t include a post from Tony Redmond :). That post is only about ExchangeOnline and Set-UserPhoto but it’s always fun to read a post from him as he explains things well.