Syncing Azure Photos to SharePoint

sync-azure-photos-to-sharepoint

Throughout my time at AMT, I have received requests and complaints as to why user(s) profile images are not displaying in SharePoint, even when they do in fact have a profile image against their account.

If you do some digging online, you will find that this is an all-too-common problem within Microsoft 365. Users profile images will display in one part of your tenancy, such as Exchange or Delve, but then not in others like SharePoint. The aim of this Blog is to provide some insight on how you can get your user images displaying consistently throughout your tenancy.

Start the process

First, you may be starting from a point where you have not uploaded any Pictures to your tenancy at all. If this is the case, you can use PowerShell to bulk upload user images, or rely on your users to upload their own pictures. Neither of these options have a large success rate and can often been laborious and time consuming. This is where I have been making use of a great free tool provided by CodeTwo for bulk uploading user images to Microsoft 365. I will not go in to too much detail on how to use the tool, as full instructions can be found on their website, but it is so easy to use and has saved me hours when working with bulk uploads.

Now you have all your users’ photos uploaded to Microsoft 365, you might still notice that they are not showing in SharePoint. This issue can be frustrating, and without proper knowledge is not the easiest of problems to fix without intervention from Microsoft. There are a few things that can be done to ‘encourage’ SharePoint to sync the user profile images.

SharePoint storage

Before tackling the issue, it is important to grasp a basic understanding of how SharePoint is storing your photos at a high level, it will allow you to understand where the issues lie. When a user is created, their profile image is supposed to sync through to SharePoint, where it is stored in in the My Site Host collection under a folder called ‘User Photos’. You can navigate to where the photos are stored by entering the URL: https://[tenant-name]-my.sharepoint.com/User Photos/Profile Pictures

where you replace [tenant-name] with the name of your SharePoint tenant. What you will discover here is that for each user that has access to and has visited SharePoint, has 3 images, 1 small, 1 medium and 1 large.

These images will be stored something like the following. Replace all special characters in your email with an underscore (_). For example, if the email address was john.smith@contoso.com, the image name would be stored like john_smith_contoso_com. Then for each size image, it would be stored as:

· john_smith_contoso_com_SThumb.jpg

· john_smith_contoso_com_MThumb.jpg

· john_smith_contoso_com_LThumb.jpg

Where S is the small thumbnail, M is Medium and L is Large.

Upon visiting this location, you may notice that a large amount of the images look like this:

blank-profile-image

This means that somewhere along the lines, the sync between Exchange and SharePoint has not worked.

In a user’s SharePoint user profile, the picture property, is a simply a URL that points to the MThumb.jpg in the My Site, site collection.

Just to add another layer of complexity, SharePoint also stores user images on a per site basis in the user information list. This is a hidden list that exists on each SharePoint site, which is populated with all the users who have ever visited that site. This hidden list can be found by navigating to the URL: https://[tenant-name].sharepoint.com/_layouts/15/people.aspx?MembershipGroupId=0

Here, you will see a list of all users who have visited that site. If you select ‘Settings’ -> ‘List Settings’, you can edit the default view to show the users picture by adding it to the view. Again, you may notice a large number of place holder images. The reason I bring this up is that not only have we observed syncing issues between Exchange and SharePoint, but also from within SharePoint, where these hidden lists are not updated with the latest version of the users profile image! This is especially annoying, when utilising a built in SharePoint API endpoint to return a user’s profile image, which will look at the hidden lists picture value and return this.

For reference, that endpoint is: https://[tenant-name].sharepoint.com/_layouts/15/userphoto.aspx?

So now you know how SharePoint is storing your user images, how can you fix the issues?

The first thing that can be done is to enable a setting in the users SharePoint user profile called ‘Picture Exchange Sync State’. By default, if a user’s profile image has not yet been triggered to sync from Exchange, this value will be set to 0. Setting it to 1 can trigger a manual sync which will in turn cause their image to show in SharePoint. Doing this process 1 by one can be a laborious task, luckily, we have written a PowerShell script that can do it for you.

Having run the script, after the next SharePoint crawl, you may begin to start to see some users profile images showing on their SharePoint profiles. However, this doesn’t always resolve the issue. Because of this, we have adapted another PowerShell script (Originally written by Brennan Aldridge) that looks to read in a user’s profile image from Exchange, and force upload it to SharePoint, essentially skipping the sync process entirely. The script can be found below, and allows you to either run it against a list of users that you provide via a CSV file, or against all users in your Active Directory.

$title = 'SharePoint Image Sync'
$question = 'Which users would you like to sync?'
$choices  = '&All Users', '&Users in CSV'

$csvPath = "C:\Users\David.Buckley\dev\amt-evolve\upss\Sync Profile Pictures\users.csv" #CSV File that contains a single column of user email addresses. Heading = Email
$tempFolder = "c:\temp\photos\" #temporarily location to store image for resizing and re-uploading.
$orgName = Read-Host "Enter your organisation name" #org prefix; e.g., contoso

$adminURL = "https://" + $orgName  + "-admin.sharepoint.com"
$mySiteURL = "https://" + $orgName + "-my.sharepoint.com"

$UserCredential = Get-Credential

$adminConn = Connect-PnPOnline -Url $adminURL -ReturnConnection -Credentials $UserCredential
$mySiteConn = Connect-PnPOnline -Url $mySiteURL -ReturnConnection -Credentials $UserCredential

#Prompt user for input
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 0)

if($decision -eq 0) {
    # Get All Users from AD
    Connect-AzureAD -Credential $UserCredential
    $data = Get-AzureADUser -All $true
    $itemCount = ($data | Measure-Object).Count
} elseif ($decision -eq 1) {
    # Import CSV and Report Users
    $data = Import-Csv $csvPath
    $itemCount = ($data | Measure-Object).Count
}

if ($itemCount -gt 0) {

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

    $data | ForEach-Object {
        if($decision -eq 0) {
            $userEmail = $_.UserPrincipalName
        } elseif ($decision -eq 1) {
            $userEmail = $_.Email
        }

        Write-Host "Attempting to sync $($userEmail)..."
        
        Try {

            $tempFilePath = $tempFolder + $userEmail + ".jpg"
            $imagePrefix = $userEmail.Replace("@", "_").Replace(".", "_");

            $Param =@{
                Uri = "https://outlook.office365.com/ews/Exchange.asmx/s/GetUserPhoto?email=" + $userEmail + "&size=HR240x240"
                Credential = $UserCredential
                OutFile = $tempFilePath
            }
            Invoke-WebRequest @Param

            $file = Get-ChildItem  -LiteralPath $tempFilePath
            $stream = $file.OpenRead()
            $img = [System.Drawing.Image]::FromStream($stream)

            Foreach($size in $pictureSizes.GetEnumerator())
            {
                [int32]$new_width = $size.Value
                [int32]$new_height = $size.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
                $fileName = $imagePrefix + $size.Name + ".jpg"

                Add-PnPFile -FileName $fileName -Stream $stream -Folder "/User Photos/Profile Pictures/" -Connection $mySiteConn
            }

            $stream.Close()

            #Set PictureURL property in SP User Profile
            $pictureURL = $mySiteURL + "/User Photos/Profile Pictures/" + $imagePrefix + "_MThumb.jpg"
            $userAccount = "i:0#.f|membership|" + $userEmail

            Set-PnPUserProfileProperty -Account $userAccount -PropertyName "PictureURL" -Value $pictureURL -Connection $adminConn

            Write-Host "Synced!" -ForegroundColor Green

        }
        Catch {
            Write-Output "$('Error |', $userEmail, $_)"
        }
    }
}
<p>

 

Simply edit the variables within the script to your preference. (Please note, you will require a basic knowledge of PowerShell in order to get the scripts to run correctly). We have tested this with MFA and the script still works, however, if you are connecting to Azure for ‘All users’ you may need to connect using online credentials by removing the credentials parameter, but to prevent multiple log ins, we just pass standard PowerShell credentials.

If the script returns an error like:

Error | [EMAIL] The remote server returned an error: (404) Not Found.

This simply means that the user does not have an Exchange license, where the script is getting the image from.

If the script returns an error like:

User Not Found: Could not load profile data from the database

This likely means that the user does not have a SharePoint license, or does not exist in the user profile service.

If you wish to run the script for only specific users, then the template CSV file has been provided. It contains one column, where on each row you specify the email for the user you wish to sync.

The script can be altered to your hearts content to work with various other image sources.

We hope that you find some of the information in this article useful, and wish you the best of luck in ensuring your images are always synced!