Problem Description
If you are in a hybrid environment where users are synced from on-prem AD to Azure AD using AD Connect, you may find that your user photos were never populated in SharePoint Online. In this case, the user photo may be displayed in Outlook (OWA), and Teams but not SharePoint.
Here is an example:
Outlook:
Teams:
SharePoint
Cause
This issue is occurring because there is currently a 10 KB limit for UserPhotos to be stored in Exchange Online. In many cases, the userPhotos are populated by updating the “thumbnailPhoto” attribute in Active Directory.
The thumbnailPhoto attribute from the on-prem AD is synchronizationed AzureAD, but never pulled into Exchange due to the 10 KB limitation.
Since SharePoint Online only pulls from Exchange Online to populate the user photo, if the photo is missing in Exchange Online, it will never be displayed in SharePoint Online.
You can determine if this issue occurring in your environment by inspecting your Exchange Online mailboxes to see of the user attribute “HasPicture” equals false.
Example:
get-mailbox -ResultSize Unlimited | Where {($_.IsDirSynced -eq $true -and $_.HasPicture -eq $false)}
Solution
To resolve this issue, you can use this script to execute the following actions.
Part 1:
Find all users that are missing photos in Exchange Online and update the photo from AzureAD.
1. Loop through all users, locate the photo in Azure AD
2. Download the photo to a temp location on the PC.
3. Publish the photo in Exchange Online
4. Delete the downloaded photos.
Part 2:
Copy the photos from Exchange Online to SharePoint.
1. Loop through all users that have photos in Exchange Online.
2. Locate their photo in Exchange Online.
3. Publish the photos from Exchange Online to SharePoint Online.
4. Update the SharePoint User Properties to reflect the new user photo.
Please Note: Part 2 is only required if you want the photo to show up right away in SharePoint Online. The photo will eventually make its way to SharePoint Online within 3 days via backend timer jobs.
The Script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
#Title: Fix-UserPhotos #Description: Iterates through users and updates missing photos in Exchange and SharePoint. #Date: 2/1/2023 #Author: Mike Lee #Credit and Ref: https://techcommunity.microsoft.com/t5/sharepoint/profile-photo-sync-spo/m-p/78585 #Disclaimer: This PowerShell script is provided "as-is" with no warranties expressed or implied. Use it at your own risk. #Input parameters #Update Tenant with your Tenant Info $siteUrl = "https://YOURTENANT-my.sharepoint.com/" $SPOAdminPortalUrl = "https://YOURTENANT-admin.sharepoint.com/" $adminacct = 'YOUR GLOBAL ADMIN ACCOUNT' $UserEmail = $(Write-Host "Type the user e-mail address to fix a single user or 'All' for fix all users: " -ForegroundColor Green -NoNewLine; Read-Host) #Add references to SharePoint client assemblies and authenticate to Office 365 site required for CSOM Add-Type -AssemblyName System.Drawing #Modules Needed #Install-Module PnP.PowerShell -Force -Confirm:$false #Install-Module -Name ExchangeOnlineManagement -Force -Confirm:$false #Install-Module AzureAD -Force -Confirm:$false #For environments with multiple accounts, use this 'Register-PnPManagementShellAccess' to make sure PNP is using the correct account #Register-PnPManagementShellAccess #Connect to SharePoint Online (My Site) $siteConnection = Connect-PnPOnline -Url $SiteURL -Interactive -ForceAuthentication -ReturnConnection #Connect to SharePoint Online (Admin Portal) # -Weblogin is needed from within the Admin Connection to execute 'Set-PnPUserProfileProperty' due to Bug https://github.com/pnp/powershell/issues/277 #$adminConnection = Connect-PnPOnline -Url $SPOAdminPortalUrl -Interactive -ForceAuthentication -ReturnConnection $adminConnection = Connect-PnPOnline -Url $SPOAdminPortalUrl -UseWebLogin -ForceAuthentication -ReturnConnection #Connect to Exchange Online Connect-ExchangeOnline -UserPrincipalName $adminacct #Connect to Azure AD Connect-AzureAD -AccountId $adminacct #This fucntion will get the actual user Photo from Azure Ad and Publish to Exchange Online. Function updateexo() { Param( [Parameter(Mandatory=$True)] [String]$UserEmail ) #Only Find Dirsync users that do not have a Photo in Exchange Online $users = get-mailbox -ResultSize Unlimited | Where {($_.IsDirSynced -eq $true -and $_.HasPicture -eq $false)} foreach ($user in $users){ if(($user.PrimarySmtpAddress -eq $UserEmail) -or ($UserEmail -eq "All")){ try{ Write-Host "Getting user Photo from AzureAD - For:" $user.Alias -ForegroundColor Yellow Get-AzureADUserThumbnailPhoto -ObjectId $user.ExternalDirectoryObjectId -FileName ($env:USERPROFILE + "\" + $user.DisplayName) Write-Host "Uploading UserPhoto to Exchange Online - For:" $user.Alias -ForegroundColor Green Set-UserPhoto -Identity $user -PictureData ([System.IO.File]::ReadAllBytes($env:USERPROFILE + "\" + $user.DisplayName + '.' + 'jpeg')) -Confirm:$false Write-Host "Deleting UserPhoto" ($env:USERPROFILE + "\" + $user.DisplayName + '.' + 'jpeg') "from Drive - For:" $user.Alias -ForegroundColor Red Remove-Item -Path ($env:USERPROFILE + "\" + $user.DisplayName + '.' + 'jpeg') -Force -Confirm:$false Write-Host "Successfully Updated User Photo:" $user.Alias -ForegroundColor Green } catch{ $ErrorMessage = $_.Exception.Message Write-Host "Failed to process " $user.Alias -ForegroundColor red Write-Host $ErrorMessage -ForegroundColor red } } } } #This function will copy the user photo from Exchange Online and Publish to SharePoint User Photos. Function updateSPO() { Param( [Parameter(Mandatory=$True)] [String]$SiteURL, [Parameter(Mandatory=$True)] [String]$SPOAdminPortalUrl, [Parameter(Mandatory=$True)] [String]$UserEmail ) #Defualt Image library and Folder value $DocLibName ="User Photos" $foldername="Profile Pictures" $spoimagename = @{"_SThumb" = "48"; "_MThumb" = "72"; "_LThumb" = "200"} $allUserMailboxes = get-mailbox -ResultSize Unlimited | Where {($_.IsDirSynced -eq $true -and $_.HasPicture -eq $true)} Foreach($mailbox in $allUserMailboxes) { if(($mailbox.PrimarySmtpAddress -eq $UserEmail) -or ($UserEmail -eq "All")){ Write-Host "Processing " $mailbox.PrimarySmtpAddress try{ #Download Image from Exchange online $photo = Get-UserPhoto -Identity $mailbox.Identity #Write-Host "Exchange online image downloaded successful for" $mailbox.PrimarySmtpAddress Write-Host "Getting Exchange online User Photo for downloaded - User:" $mailbox.PrimarySmtpAddress -ForegroundColor Cyan #$username = $mailbox.PrimarySmtpAddress.Replace("@", "_").Replace(".", "_") #Fixed this to set photo with the correct name (GUID) $username = $mailbox.ExternalDirectoryObjectId $Extension = ".jpg" Foreach($imagename in $spoimagename.GetEnumerator()) { #Covert image into different size of image $ms = new-object System.IO.MemoryStream(,$photo.PictureData) $img = [System.Drawing.Image]::FromStream($ms) [int32]$new_width = $imagename.Value [int32]$new_height = $imagename.Value $img2 = New-Object System.Drawing.Bitmap($new_width, $new_height) $graph = [System.Drawing.Graphics]::FromImage($img2) $graph.DrawImage($img, 0, 0, $new_width, $new_height) #Covert image into memory stream $stream = New-Object -TypeName System.IO.MemoryStream $format = [System.Drawing.Imaging.ImageFormat]::Jpeg $img2.Save($stream, $format) $streamseek=$stream.Seek(0, [System.IO.SeekOrigin]::Begin) #Upload image into sharepoint online $FullFilename=$username+$imagename.Name+$Extension Add-PnPFile -Connection $siteConnection -Folder "User Photos/Profile Pictures" -FileName $FullFilename -Stream $stream } Write-Host "SharePoint online user photo was uploaded - User:" $mailbox.PrimarySmtpAddress -ForegroundColor Yellow #Change user Profile Property in Sharepoint onlne $PictureURL=$SiteURL+$DocLibName+"/"+$foldername+"/"+$username+"_MThumb"+$Extension Write-Host "Modifying User Profile Properties for with new User Photo - User:" $mailbox.PrimarySmtpAddress -ForegroundColor Magenta Set-PnPUserProfileProperty -Connection $adminConnection -Account $mailbox.PrimarySmtpAddress -PropertyName PictureURL -Value $PictureURL Set-PnPUserProfileProperty -Connection $adminConnection -Account $mailbox.PrimarySmtpAddress -PropertyName SPS-PicturePlaceholderState -Value 0 Set-PnPUserProfileProperty -Connection $adminConnection -Account $mailbox.PrimarySmtpAddress -PropertyName SPS-PictureExchangeSyncState -Value 0 Set-PnPUserProfileProperty -Connection $adminConnection -Account $mailbox.PrimarySmtpAddress -PropertyName SPS-PictureTimestamp -Value 63605901091 # Note if the ODB URL does not match the SMTP Address, replace the "$mailbox.PrimarySmtpAddress" with "$mailbox.UserPrincipalName" in the "Set-PnPUserProfileProperty" commands above. Write-Host "Image processed successfully and ready to display - User:" $mailbox.PrimarySmtpAddress -ForegroundColor Green } catch{ $ErrorMessage = $_.Exception.Message Write-Host "Failed to process " $mailbox.PrimarySmtpAddress -ForegroundColor red Write-Host $ErrorMessage -ForegroundColor red } } } } #Run Functions if ($option -le 3){ $option = (Read-Host "Type '1' to Update missing Photos in Exchange Online: Type '2' to update missing photos in SharePoint Online") if ($option -eq "1"){ updateexo -UserEmail $UserEmail } if ($option -eq "2"){ updateSPO -SiteURL $siteUrl -SPOAdminPortalUrl $SPOAdminPortalUrl -UserEmail $UserEmail } } |
What the script looks like in action:
Here is an example of running “Part 1” the script for a single user.
Now you can see that “JSMITH” no longer shows up in the query to show “HasPicture = False”
Now the user photo has been updated in Exchange Online, but it still missing in SharePoint, until you wait for the UPA to sync via backend timer jobs.
If you want to update the photo in SharePoint right away, run “Part 2” of the script:
Now when sharing content via SharePoint, the UserPhoto will be displayed.
Conclusion
I hope you found this information useful, and provided a better understanding of why this issue is occurring and how to resolve it. If you have any comments, please let me know.
More Information
User photos aren’t synced to Exchange Online – Exchange | Microsoft Learn
Information about profile picture synchronization in Microsoft 365 – Microsoft Support