SharePoint Online User Photo Sync

Steel Contributor

We are running Ex2010 Hybrid scenario with Office 365, using SharePoint Online only. No hybrid for SP. We are on the latest version of AAD Sync. When setting user photos we use the commands below. 

 

$user = 'blah.blah@blah.com'
$userphoto = "\\someFileShare\blah_blah.jpg"
Set-UserPhoto -Identity $user -PictureData ([System.IO.File]::ReadAllBytes($userphoto)) -Confirm:$false

Photos show up in AD/EXOL No Problem. Photos do not sync up to delve or SharePoint.

 

Are we using the right commands for this? Is there another command to run to ensure the same photo is uploaded to Delve/SharePoint??

 

Any help would be appreciated. 

11 Replies

@Adam Harmetz Could you get the appropriate person to help with this envolved? It seems this is a big issue for others as well after doing further research on it. 

 

@Tony Redmond Does a great job outlining the issue further in his blog post

We had to come up with a script to manually push them through to SharePoint because of this bug, as it was inconsistent.

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 
Same here. This is one of those things you'd assume works, but doesn't. Ultimately the script works, but we shouldn't have to depend on it.

I have an internal thread with the owners of this component and have asked them to weigh in on this thread.

 

Thanks,

Adam

@Adam Harmetz , Could you please follow up with the owners of this component? They never weighed in.

Agreed. It would be great if we could get rid of our band aid in favor of a formal fix.
I had a customer who required a similar solution. They have an investment in GApps and (for now) are only using SPO, so no hybrid in this case. The customer needed a solution for populating custom UPSA props from Active Directory as well as upload user photos. Ended up using the UPA PnP sample and extending it heavily to support various custom props, work with how they structured their photos today on-prem, and other requirements like make it a Windows Service leveraging the Event Log. It's a bit convoluted compared to just being able to use something like AAD Sync to grab photos from thumbnailPhoto or a chosen attribute and get them to AAD for eventual consumption by the service. That said, since Exchange [Online] is the source of truth for high quality pictures, there's not a great story here.

Hello Everyone,

 

There are several obstacles to setting the SharePoint Online user photo. We acknowledge that this is a scenario that stands to be improved, but there is no planned work in this space at this time. Please see https://support.microsoft.com/en-us/kb/3185286 for the official documentation on the process.

 

At present, to update a user’s profile in SharePoint one of the following flows must happen:

 

1) The user has an Exchange license/mailbox. In this case the picture can be set in Exchange. From there it will sync from Exchange Online to SharePoint Online. The user must navigate to their Delve Profile Page to trigger the sync from Exchange Online to SharePoint Online. This can only happen every 72 hours.

 

2) The user doesn’t have an Exchange mailbox. In this case the picture needs to be uploaded to SharePoint directly. The only way to do this is via the UI.


The script refenced at the start of this thread effectively simulates what happens during the Exchange-to-SharePoint sync operation. There is no other way to achieve this at present. We will continue to evaluate this area and appreciate how complex the scenario can be.


Call to action! Please upvote the following topics on the UserVoice Customer Feedback site related to this. This feedback helps us prioritize.


https://office365.uservoice.com/forums/273493-office-365-admin/suggestions/10078710-need-a-way-to-fo...

 

https://office365.uservoice.com/forums/264636-general/suggestions/15826552-show-pictures-without-exc...

 

Thanks,
Roberto Taboada

We've actually started using a solution called Hyperfish that handles the profile photos in Exchange, SharePoint, and Azure AD. I blogged about it here: http://thecloudmouth.com/2016/11/29/simplifying-profile-pictures-in-office-365-with-hyperfish/