At a customer recently, we were asked if we could provide a non-technical way of controlling profile pictures for both Office 365 and Windows 10. So straight away I thought, time for some PowerShell. I came up with the solution of having a number of shares on a server, which can be permission’d as required…

.\Source – for adding images
.\Replace – if a user wanted to change their picture
.\Remove – if a user opted out of the profile picture setup

As this was a new setup, I requested that they name the images UPN.jpg, as that way I could grab the username during the process. The source images were going to be the ones used for ID badges, so coming from a high resolution camera. I then cracked on and uploaded to Office 365 and it looked the business, here is me thinking this is a piece of cake. However, as soon as I tried to resize and use these for Windows 10, well you guessed it, pixel horribleness. Turns out Office 365 uses some nice squaring technology on the uploaded images.

Not to be deterred, I had a play with some PowerShell functions I found on the internet to resize images and while they worked, I just wasn’t happy with the dimensions. Then, bing, the lightbulb went off, let’s use Office 365 to resize the images for me!

So basically if I resize the image using a PowerShell function to 767×767 (which worked well for my source images), I could upload this for Office 365. Then using the URL below you can download the square image at set resolutions. In my case, I was wanting 96×96 as that’s the thumbnailPhoto attribute for AD. So I plumbed in the UserPrincipalName and ImageSize into the URL using variables and grabbed the image…

https://outlook.office365.com/ews/Exchange.asmx/s/GetUserPhoto?email=”+$UserPrincipalName+”&size=HR”+$ImageSize

I’ve provided part of the script below. This essentially followed the logic of…

    • A user’s profile picture should be added to the .\Source share
    • This picture should be named by the user email address, for example [email protected]
    • The nightly schedule will then poll that directory for all pictures and obtain the username from each file
    • It will resize the Office 365 image to 767×767 and then upload this to Office 365
    • It will then download the Office 365 image in 96×96 format, taking advantage of the squaring process within Office 365, and save it in preparation of on-premises Active Directory
    • Finally, if the user does not have an Active Directory picture set, it will then write this image to the thumbnailPhoto attribute ready for the logon script to download

[code language=”powershell” firstline=”1″]
log "INFO" 100 "Grabbing all images from the Source Directory"
$OriginalImages = Get-ChildItem .\Source -Filter "*.jpg" -Recurse
$OriginalImagesCount = $OriginalImages.Count
log "INFO" 100 "Found $OriginalImagesCount images"
$ImageUserCounter = 0

foreach ($ImageUser in $OriginalImages)
{
try
{
$ImageUserCounter ++
$UserPrincipalName = $null
$UserPrincipalName = [io.path]::GetFileNameWithoutExtension($ImageUser.Name)
log "INFO" 100 "Grabbing the UserPrincipalName for $UserPrincipalName. $ImageUserCounter/$OriginalImagesCount"
}
catch
{
log "WARNING" 100 "Unable to grab the UserPrincipalName for $ImageUserCounter/$OriginalImagesCount. The value of the variable is: $UserPrincipalName. Server said: $_"
continue
}
try
{
log "INFO" 100 "Resizing $UserPrincipalName Office365 image"
$SkypeOutput = $null
$SkypeOutput = $SkypeWorkingDir+"\$UserPrincipalName 767×767.jpg"
ResizeImage $ImageUser.VersionInfo.FileName 767 767 $SkypeOutput
}
catch
{
log "WARNING" 100 "Unable to resize $UserPrincipalName Office365 image, server said: $_"
continue
}
try
{
log "INFO" 100 "Setting $UserPrincipalName Office365 picture"
Set-o365UserPhoto -Identity $UserPrincipalName -PictureData ([System.IO.File]::ReadAllBytes($SkypeOutput)) -Confirm:$false -ErrorAction stop
}
catch
{
log "WARNING" 100 "Unable to set $UserPrincipalName’s Office365 image, server said: $_"
}
try
{
log "INFO" 100 "Resizing $UserPrincipalName AD image"
$ImageSize = "96×96"
$DownloadURL = "https://outlook.office365.com/ews/Exchange.asmx/s/GetUserPhoto?email="+$UserPrincipalName+"&size=HR"+$ImageSize
$DownloadPath = $ADWorkingDir+"\"+$UserPrincipalName+" "+$ImageSize+".jpg"

$WebClient = New-Object System.Net.WebClient
$WebClient.Credentials = New-Object System.Net.NetworkCredential($adminuser,$adminpwd)
$WebClient.DownloadFile($DownloadURL,$DownloadPath)
$ADPhoto = ([System.IO.File]::ReadAllBytes($DownloadPath))
}
catch
{
log "WARNING" 100 "Unable to resize $UserPrincipalName’s AD image, server said: $_"
}

$ADUser = $null
$ADUser = Get-ADUser -Filter {UserPrincipalname -eq $UserPrincipalName} -Properties * -ErrorAction Stop
if ($ADUser.thumbnailPhoto -eq $null)
{
try
{
log "INFO" 100 "$UserPrincipalName does not currently have an AD picture, setting this now"
Set-ADUser $ADUser -Replace @{thumbnailPhoto=$ADPhoto} -ErrorAction Stop
}
catch
{
log "WARNING" 100 "Unable to set $UserPrincipalName’s AD image, server: said $_"
}
}
else
{
log "INFO" 100 "$UserPrincipalName already has a picture set in AD, skipping this section"
}
}
[/code]

A quick note on a gotcha I found as well, you need to change the PSSession string to include a proxy method which is described here ala…

[code language=”powershell” firstline=”1″]
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI https://outlook.office365.com/powershell-liveid/?proxymethod=rps -Credential $credential -Authentication Basic -AllowRedirection
[/code]

And there you go, this approach not only uploaded photo’s for Office 365, but took advantage of their image manipulation and re-used that for on-premises applications such as AD. Enjoy!

Category:
Exchange, Office 365, PowerShell, SharePoint, Skype For Business

Join the conversation! 2 Comments

  1. Hello Dave, This is exactly what I am looking for. Now if I could get it work that would be perfect. Does log refer to a function I am not seeing somewhere. I am using native PowerShell 5.x in Windows 10 with the CSOM and AD Modules installed. Is there something I am missing to get log to work?

  2. Hi David, yes Log is just a logging function that generates an output file. You could change those lines to Write-Host for development. Alternatively, you could come up with something similar, it’s based on Add-Content…

    https://technet.microsoft.com/en-us/library/ee156791.aspx

Comments are closed.