Profile Photo Sync SPO

Brass Contributor

As admin, I have added profile photos for everyone in my org through the O365 admin portal. The photos are seen everywhere. Except on SharePoint. I am unable to upload photos for people on SPO. I have read in other posts that the photos are synced when each user visits their Delve page at least once. Now, I cannot expect every user to go to their Delve page to sync the profile photos to SharePoint. Is there another way for the admin to do this?

 

Many thanks for any help!

12 Replies
Take a look at the following articles where you will find different approaches to achieve this. Be ready to work with some PowerShell:
- https://spbreed.wordpress.com/2015/11/06/demystifying-user-profile-picture-sync-in-office365/
- http://windowsitpro.com/blog/synchronizing-user-photos-across-office-365-workloads-isnt-easy

So, I set the user photo using Powershell. See screenshot. It ran without any errors. Now, when I go the sharepoint admin page, the photo for this particular user has not been set. See screenshot. Set-UserPhoto does not seem to set the profile photo in SharePoint. Any further help?

UserPhoto Error.png


UserPhoto Error1.png

Following PowerShell script is used to instantly process and display profile picture in Exchange Online and SharePoint Online using “Set-UserPhoto” cmdlet and CSOM.
 
Before executing the following script, you need to modify the input parameters in the script with your own values as follows,
 
Input Parameters:
$siteUrl= "https://tenantname-my.sharepoint.com/"
$username= "admin@tenantname.onmicrosoft.com"
$password= "xxxxxxxx"
$folderpath= "E:\photo"
$SPOAdminPortalUrl= “https://tenantname-admin.sharepoint.com/”

 

#Add references to SharePoint client assemblies and authenticate to Office 365 site – required for CSOM
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client").location)
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.runtime").location)
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles").location)
Add-Type -AssemblyName System.Drawing

Function UpdateUserProfile()
{
Param(
  [Parameter(Mandatory=$True)]
  [String]$targetAcc,

  [Parameter(Mandatory=$True)]
  [String]$PropertyName,

  [Parameter(Mandatory=$False)]
  [String]$Value, 

  [Parameter(Mandatory=$True)]
  [String]$SPOAdminPortalUrl,

  [Parameter(Mandatory=$True)]
  [System.Net.ICredentials]$Creds

)
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SPOAdminPortalUrl)
$ctx.Credentials = $Creds
$peopleManager = New-Object Microsoft.SharePoint.Client.UserProfiles.PeopleManager($ctx)
$targetAccount = ("i:0#.f|membership|" + $targetAcc)
$peopleManager.SetSingleValueProfileProperty($targetAccount, $PropertyName, $Value)
$ctx.ExecuteQuery()
}

Function UploadImage ()
{
Param(
  [Parameter(Mandatory=$True)]
  [String]$SiteURL,

  [Parameter(Mandatory=$True)]
  [String]$User,

  [Parameter(Mandatory=$False)]
  [String]$Password, 

  [Parameter(Mandatory=$True)]
  [String]$Folder,

  [Parameter(Mandatory=$True)]
  [String]$SPOAdminPortalUrl

)

#Defualt Image library and Folder value 
$DocLibName ="User Photos"
$foldername="Profile Pictures"

#Connect Exchange online 

$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $User, $secstr
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/?proxyMethod=RPS -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession $Session

$Securepass = ConvertTo-SecureString $Password -AsPlainText -Force
$Creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($User,$Securepass)

#Bind to site collection
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
$Context.Credentials = $Creds

#Retrieve list
$List = $Context.Web.Lists.GetByTitle($DocLibName)
$Context.Load($List)
$Context.Load($List.RootFolder)
$Context.ExecuteQuery()
$ServerRelativeUrlOfRootFolder = $List.RootFolder.ServerRelativeUrl

$uploadFolderUrl=  $ServerRelativeUrlOfRootFolder+"/"+$foldername
$spoimagename = @{"_SThumb" = "48"; "_MThumb" = "72"; "_LThumb" = "200"}
#Upload file
Foreach ($File in (dir $Folder -File))
{

#Upload image into Exchange online 
$PictureData = $File.FullName
$Identity=$File.BaseName
Set-UserPhoto -Identity $Identity -PictureData ([System.IO.File]::ReadAllBytes($PictureData)) -Confirm:$false
Write-Host "Exchange online image uploaded successful for" $Identity  

$username=$File.BaseName.Replace("@", "_").Replace(".", "_")
$Extension = $File.Extension
Foreach($imagename in $spoimagename.GetEnumerator())
{
#Covert image into different size of image
$img = [System.Drawing.Image]::FromFile((Get-Item $PictureData))
[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
$ImageRelativeURL="/"+$DocLibName+"/"+$foldername+"/"+$FullFilename
$ModifiedRelativeURL=$ImageRelativeURL.Replace(" ","%20")
[Microsoft.SharePoint.Client.File]::SaveBinaryDirect($Context,$ModifiedRelativeURL, $stream, $true)

}
Write-Host "SharePoint online image uploaded successful for" $Identity 
#Change user Profile Property in Sharepoint onlne 
$PictureURL=$SiteURL+$DocLibName+"/"+$foldername+"/"+$username+"_MThumb"+$Extension

UpdateUserProfile -targetAcc $Identity  -PropertyName PictureURL  -Value $PictureURL -SPOAdminPortalUrl $SPOAdminPortalUrl -Creds $Creds

UpdateUserProfile -targetAcc $Identity  -PropertyName SPS-PicturePlaceholderState -Value 0 -SPOAdminPortalUrl $SPOAdminPortalUrl -Creds $Creds

UpdateUserProfile -targetAcc $Identity  -PropertyName SPS-PictureExchangeSyncState -Value 0 -SPOAdminPortalUrl $SPOAdminPortalUrl -Creds $Creds

UpdateUserProfile -targetAcc $Identity  -PropertyName SPS-PictureTimestamp  -Value 63605901091 -SPOAdminPortalUrl $SPOAdminPortalUrl -Creds $Creds

Write-Host "Image processed successfully and ready to display for" $Identity
}

}
#Input parameter
$siteUrl="https://tenantname-my.sharepoint.com/"
$username= "admin@tenantname.onmicrosoft.com" 
$password= "pass@word1"
$folderpath= "E:\photo"
$SPOAdminPortalUrl = "https://tenantname-admin.sharepoint.com/"

uploadimage -SiteURL $siteUrl -User $username -Password $password -Folder $folderpath -SPOAdminPortalUrl $SPOAdminPortalUrl 

This looks exactly what I'm looking for - but not being a big powershell user, what is required for me to run this powershell script? What do I need to connect to first and how?

I finally got it working. Both through Powershell and the UI. 

 

For the UI option (easier compared to PS) , go to the sharepoint online admin center --> User Profiles (left nav) --> Manage User Profiles. Edit a users profile. If you can't upload a photo, then change the 

Picture Exchange Sync State to 0. Then save. Edit the profle again, and upload a photo for the user (10KB or less in size). 

For Powershell option, I followed this article. Make sure you have Microsoft Azure Active Directory Module for Windows Powershell installed. And module MSOnline should also be installed. Follow the steps in the screenshot from my previous reply in this post. 

For the powershell option, it takes upto 72 hours to reflect the change. The UI option is almost instant. Just a few minutes delay. 

You need to run 'Set-ExecutionPolicy RemoteSigned' first and you need to install CSOM https://www.microsoft.com/en-in/download/details.aspx?id=42038

 

Also please note that the user photos should be named as userprincipalname (email address) of the user to whom you need to upload photo. 

Now you need to change the imput parameters in the script as mentioned and save as .ps1 file and run it. This will update the user photos immediately.

@Santhosh Balakrishnan 

 

Thank you for this code example.

My image file was continually locked after the first edit. I was able to fix it by changing the following lines of code in case you want to include this.

 

$graph = [System.Drawing.Graphics]::FromImage($img2)
$graph.DrawImage($img, 0, 0, $new_width, $new_height)

 

to include

 

$graph = [System.Drawing.Graphics]::FromImage($img2)
$graph.DrawImage($img, 0, 0, $new_width, $new_height)
$img.Dispose();
$graph.Dispose();

  

I got the script working, but I only updates for the current user.  I need it to update ALL users.

 

I thought this might help others.

Based on a previous post I created this script to work with O365 with MFA enabled.

 

This PowerShell script will allow you to sync a user profile photo from Exchange Online to Sharepoint Online.

You can select a single email address or choose "All"

 

EDIT: Added Try-Catch block around section for some error handling.

 

#Add references to SharePoint client assemblies and authenticate to Office 365 site – required for CSOM
Add-Type -AssemblyName System.Drawing

Function UploadImage ()
{
	Param(
	  [Parameter(Mandatory=$True)]
	  [String]$SiteURL,

	  [Parameter(Mandatory=$True)]
	  [String]$SPOAdminPortalUrl,

	  [Parameter(Mandatory=$True, HelpMessage="Enter user email address or All for all mailboxes")]
	  [String]$UserEmail

	)

	#Defualt Image library and Folder value 
	$DocLibName ="User Photos"
	$foldername="Profile Pictures"

    Connect-ExchangeOnline
    $siteConnection = Connect-PnPOnline -Url $SiteURL –Interactive -ReturnConnection

    #NOTE: there is a bug in Set-PnPUserProfileProperty in which the ConnectPnPOnline must connect with the UseWebLogin option 
    # https://github.com/pnp/powershell/issues/277
    $adminConnection = Connect-PnPOnline -Url $SPOAdminPortalUrl –UseWebLogin -ReturnConnection

	$spoimagename = @{"_SThumb" = "48"; "_MThumb" = "72"; "_LThumb" = "200"}


    $allUserMailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox

    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

		        $username = $mailbox.PrimarySmtpAddress.Replace("@", "_").Replace(".", "_")

		        $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 image uploaded successful for" $mailbox.PrimarySmtpAddress
		        #Change user Profile Property in Sharepoint onlne 
		        $PictureURL=$SiteURL+$DocLibName+"/"+$foldername+"/"+$username+"_MThumb"+$Extension

                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

		        Write-Host "Image processed successfully and ready to display for" $mailbox.PrimarySmtpAddress
            }catch{
                $ErrorMessage = $_.Exception.Message
                Write-Host "Failed to process " $mailbox.PrimarySmtpAddress -ForegroundColor red
                Write-Host $ErrorMessage -ForegroundColor red
            }
	    }        

    }

}

#Input parameter
$siteUrl="https://company-my.sharepoint.com/"
$SPOAdminPortalUrl = "https://company-admin.sharepoint.com/"

uploadimage -SiteURL $siteUrl -SPOAdminPortalUrl $SPOAdminPortalUrl

 

 

Perfect, I was just looking for this solution. Thanks for sharing!
Be careful when you do the All option, as I haven't put error handling in the code.
So exceptions will occur if the Exchange Online mailbox does not have a profile picture.
As I don't reset any of the variables in the for loop, you might accidentally have the previous person photo applied to the user without the profile picture in Exchange Online.

I see the script landed up here: Office 365 Profile Picture Not Displayed Inside SharePoint Online Webparts (mrsharepoint.guru)

 

Both script are suspiciously identical...