Microsoft 365

Sync Microsoft 365 Profile Pictures with Interact

Use PowerShell to download Profile Pictures from Microsoft 365 and then upload into Interact. The scripts can be scheduled to run using Windows Task Scheduler at suitable intervals.


  1. A General Source Profile in Interact. See article General Profile Sources
  2. Download and install the ExchangeOnlineManagement PowerShell Package from:
  3. Run the following command in PowerShell
    Import-Module ExchangeOnlineManagement


You may have to set the PowerShell execution policy to RemoteSigned or Unrestricted using the following command Set-ExecutionPolicy RemoteSigned or Set-ExecutionPolicy Unrestricted

Microsoft 365 Permissions

  1. Global administrator or Exchange administrator role in Microsoft 365
  2. ApplicationImpersonation role in Exchange Online

Download Script

The following script can be used to download all users profile pictures and store them here C:\O365\AllUserProfilePictures, it will then upload them to Interact. In this example the ExternalDirectoryObjectId field will be used for the filename, for example 14f90941-8aab-49d7-963b-841962dea5e9.jpg. Interact will use this GUID to associate it with the correct user profile by mapping it against the UMIID. See the article Profile Pictures for more examples of what can be used to map the exported pictures to the interact profile, username for example.

#Input Parameters:
$folderpath = "c:\O365\AllUserProfilePictures"
$logpath = "c:\O365\log.txt"

#Connect to Exchange Online:
$UPN = "[email protected]"
$Password = ConvertTo-SecureString "password" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($UPN, $Password)
Connect-ExchangeOnline -Credential $Credential

#Interact variables
$interactUrl = ""
$profileSourceApiKey = "1234"
$profileSourceId = "1000"

#Download all user profile pictures from Microsoft 365:
New-Item -ItemType directory -Path $folderpath -Force
$allUsers = Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited | Select-Object UserPrincipalName, Alias, ExternalDirectoryObjectId

foreach ($user in $allUsers) {
    $path = Join-Path $folderpath "$($user.ExternalDirectoryObjectId).jpg"
    try {
        $photo = Get-UserPhoto -Identity $user.UserPrincipalName -ErrorAction continue
        if ($photo.PictureData -ne $null) {
            [io.file]::WriteAllBytes($path, $photo.PictureData)
            $message = "$($user.UserPrincipalName) profile picture downloaded"
            Write-Output $message
            Add-Content -Path $logpath -Value $message
        else {
            $message = "$($user.UserPrincipalName) has no profile picture"
            Write-Output $message
            Add-Content -Path $logpath -Value $message
    catch {
        $errorMessage = "Error downloading profile picture for $($user.UserPrincipalName): $($_.Exception.Message)"
        Write-Error $errorMessage
        Add-Content -Path $logpath -Value $errorMessage

Add-Type -AssemblyName System.Net.Http

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

function ProcessFiles(){
    Get-ChildItem $folderpath | 
      Foreach-Object {
        $personId = $_.BaseName

        $baseUri = $interactUrl + "/api/umi/" + $profileSourceId + "/upload/umiid/" + $personId + "/picture"

        SendFile $baseUri $_.FullName

function SendFile([string]$uri, [string]$path){
    $httpClientHandler = New-Object System.Net.Http.HttpClientHandler
    $httpClient = New-Object System.Net.Http.Httpclient $httpClientHandler

    $packageFileStream = New-Object System.IO.FileStream @($path, [System.IO.FileMode]::Open)
    $contentDispositionHeaderValue = New-Object System.Net.Http.Headers.ContentDispositionHeaderValue "form-data"
    $contentDispositionHeaderValue.Name = "fileData"
    $contentDispositionHeaderValue.FileName = (Split-Path $path -leaf)
    $streamContent = New-Object System.Net.Http.StreamContent $packageFileStream
    $streamContent.Headers.ContentDisposition = $contentDispositionHeaderValue
    $streamContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue "image/jpeg"
    $content = New-Object System.Net.Http.MultipartFormDataContent
    $httpClient.DefaultRequestHeaders.Add("X-ApiKey", $profileSourceApiKey)

        $response = $httpClient.PostAsync($Uri, $content).Result
    catch [Exception]
        # log it
        if($null -ne $httpClient)
        if($null -ne $response)