How to import user photos in bulk via PS and Exchange Online?

I’d like to be able to bulk import photos to user accounts via Exchange Online and so I tried this script. (The users photos are 10kb or smaller and are stored in the User images folder with their samAccountName as the file name)

We have an on prem AD environment that is synced to O365 via DirSync

After connecting to Exchange Online I tried this script

$path = ‘C:\Temp\User images’
$Images = Get-ChildItem $path
$Images |Foreach-Object{
$Identity = ($.sAMAccountName.Tostring() -split “.”)[0]
$PictureData = $path+$
.sAMAccountName
Set-UserPhoto -Identity $Identity -PictureData ([System.IO.File]::ReadAllBytes($PictureData)) -Confirm:$false }

But when I hit enter I get this error message, "You cannot call a method on a null-valued expression. Referencing the 4th line $Identiy = ($.sAMAccountName.Tostring…

I have a feeling that sAMAccountName is not an O365 attribute and so that’s why it’s saying it’s null. I tried userPrincipalName instead and got the same error message.

Last week there was a topic like that one. I think it will help you: https://powershell.org/forums/topic/daily-import-photos-into-active-directory/

Thanks for the reply Richard. I’d like to store it using the Exchange Online module if possible and not import it directly to AD. I was able to get it to work with a single user but the bulk process is proving more tricky.

Someone suggested replacing samAccountName with $_.BaseName because files don’t have a samAccountName attribute. I made that change and the null errors went away but now it’s saying it can’t find the image files in the User images folder. Even though it names them specifically.

Hi Jeremy, I’ve change the code a bit that I provided in the previous post. Can you try it?
Please try it first on a few accounts, not on all accounts. :slight_smile:

$imageFolder = 'C:\Temp\Images\'
$pictures = Get-ChildItem $imageFolder

foreach ($picture in $pictures){
    
    Try {
        $user = Get-User -Identity $($picture.BaseName) -ErrorAction Stop
        }
    
    Catch {
        Write-Warning "Warning: $_"
    }

    If ($user) {
        $user | Set-UserPhoto -PictureData ([System.IO.File]::ReadAllBytes("$imageFolder\$($picture.Name)"))
    }
}

That script worked like a charm Richard :slight_smile:

I added -Confirm:$false at the end so it wouldn’t prompt me Y or N since I’m planning on doing hundreds of these.

Thanks a ton.

Just change this line from:

$user | Set-UserPhoto -PictureData ([System.IO.File]::ReadAllBytes("$imageFolder\$($picture.Name)"))

To:

$user | Set-UserPhoto -PictureData ([System.IO.File]::ReadAllBytes("$imageFolder\$($picture.Name)")) -Confirm:$false

You beat me to it :slight_smile: I was just editing my post before to say I found the Confirm false method.

Do you mind walking me through the logic of this script?

I guess the part I’m not understanding is why is it $picture.Name in the last step instead of $picture.BaseName.

As a fun side note, once you upload a picture to Exchange 2013+/Office365 it does some additional editing and filter to the file. Basically it will take any file that doesn’t match these resolutions

  • 48x48
  • 64x64
  • 96x96
  • 120x120
  • 240x240
  • 360x360
  • 432x432
  • 504x504
  • 648x648

and perform a center crop with a bias towards the top of the file (around 15%) along with down sampling it to the nearest supported resolution. Even if you give it a file that matches the supported resolution, Exchange performs some sort of compression on it to reduce the file size.
This makes it a pain to do a byte compare or file hash of the original image since they will always be different even though they look the same and are the same resolution. At my company we rely on a separate HR system to get our employee photos, so we had to automate the process of updating employee photos. Luckily in march of 2015 Ben Vierck presented his PSimaging module on the scripting guys blog <a href="https://blogs.technet.microsoft.com/heyscriptingguy/2015/03/19/psimaging-part-1-test-image/"scripting guys blog. This allowed us to properly judge if the image was dissimilar enough to flag it as a new image.

Jeremy, I made a blog post about this. Hope it helps! https://powershell.org/2016/04/13/how-to-import-user-pictures-in-exchange-online/

Thanks a ton Richard. Your blog post was very helpful.

I’ve been using the script and the more users I test in a batch the more errors I keep getting that say "the operation couldn’t be found because object ‘abrewerwl’ couldn’t be found on 'BY1PR04…NAMPR04…prod.outlook.com

I ran the script for a group of 50 users and received that error for 11 of the 50 accounts.

Is there a way to get these errors to go away? The 11 accounts it’s referring to all do exist in our O365 and AD environment.

What I’ve also noticed is that the script keeps going down the list regardless of the errors. And so it’s assigning the picture of the account that’s displaying an error to the next user in the list. Of the 50, I had to change 7 of the accounts because they were assigned the wrong picture.

Any help would be greatly appreciated :slight_smile:

Hmm, not sure what causes that. Maybe put in a Start-Sleep after the If scriptblock. Can you try that?

Like this?

$imageFolder = ‘C:\Temp\Images’
$pictures = Get-ChildItem $imageFolder

foreach ($picture in $pictures){

Try {
    $user = Get-User -Identity $($picture.BaseName) -ErrorAction Stop
    }

Catch {
    Write-Warning "Warning: $_"
}

If ($user) {
    $user | Set-UserPhoto -PictureData ([System.IO.File]::ReadAllBytes("$imageFolder\$($picture.Name)"))
}

Start-Sleep -s 5
}

Yep. try 2 seconds first, 5 seconds is quite long. :slight_smile:

Turned out changing the image file name to the user’s full email address (including the @domain) eliminated the “object not found” errors completely.

I’m in need of some help using this method to bulk import my users. I’ve been at a loss in trying to figure out how to do it. This script and the one Richard blogged about would be perfect, however, my photos come from a third party app (our Badging Department) and the employee photo image files get stored in separate sub-directories based on employee number. They are unwilling to change this behavior to “C:\EmpPhotos”. Instead this is an example what the directory structure looks like:

C:\Images\0000\0001.jpg
C:\Images\1000\1000.jpg
C:\Images\2000\2002.jpg

Is there a way to search all sub-folders for a match with the AD attribute “employeeNumber”?