Skip to content

Instantly share code, notes, and snippets.

@darrenjrobinson darrenjrobinson/import.ps1 Secret
Last active May 23, 2017

What would you like to do?
Exchange Online User Profile Photos PSMA Import
param (
[bool] $usepagedimport,
$DebugFilePath = "C:\PROGRA~1\MICROS~4\2010\SYNCHR~1\EXTENS~2\EXOPho~1\Debug\EXOImport.txt"
if(!(Test-Path $DebugFilePath))
$DebugFile = New-Item -Path $DebugFilePath -ItemType File
$DebugFile = Get-Item -Path $DebugFilePath
"Starting Import as: " + $OperationType + " " + (Get-Date) | Out-File $DebugFile -Append
$DebugPhotoDir = "C:\PROGRA~1\MICROS~4\2010\SYNCHR~1\EXTENS~2\EXOPho~1\Debug\Photos"
if(!(Test-Path $DebugPhotoDir))
$DebugPhotoPath = New-Item -Path $DebugPhotoDir -ItemType Directory
$DebugPhotoPath = Get-Item -Path $DebugPhotoDir
# Powershell Module required for Get-Hash (to calculate Image checksum)
Import-Module Pscx
# Created PS Creds
$UserCredential = New-Object System.Management.Automation.PSCredential $Username,$Password
$tempImagePhotoPath = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\EXOPhotos\profileImage.jpg"
$oAuthRefreshTokenPath = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\EXOPhotos\refresh.token"
# Path for oAuth Refresh Token
"oAuth Token Refresh Path $oAuthRefreshTokenPath" | Out-File $DebugFile -Append
# Get a list of mailboxes with profile photos
# ONLY the first time as we are using Paged Imports
if (!$Global:mbxwithpictures) {
# Create Remote PowerShell Session to Exchange Online
try {
if ($global:EXOSession){Remove-PSSession $global:EXOSession}
"Opening a new EXO RPS Session." | out-file $DebugFile -Append
$Global:EXOSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $Credentials -Authentication Basic -AllowRedirection -ErrorVariable $EXOError
Import-PSSession $Global:EXOSession
"Opened a new RPS Session" | out-file $DebugFile -Append
"EXO Session $($EXOSession.Id) $($EXOSession.ConfigurationName) $($EXOSession.State)" | Out-File $DebugFile -Append
catch {
"Failed creating a new RPS Session" | out-file $DebugFile -Append
$EXOError | out-file $DebugFile -Append
$Global:mbxwithpictures = Get-Mailbox -ResultSize Unlimited | where-object {$_.haspicture -eq $true}
"Found $($mbxwithpictures.count) mailboxes with photos" | Out-File $DebugFile -Append
[int]$Global:intMBXwithpictures = $mbxwithpictures.count
# Counter to know where we are up to processing the Import
# Starting at minus 1 as our first object is 0 and I'm incrementing at the start of the loop.
[int]$global:objectsImported = -1
# Now we have a list of mailboxes with pictures
# Use GraphAPI to go in and get them.
# The resource URI
$resource = ""
# The Client ID and Client Secret obainted when registering the WebApp
$clientid = "b7ea7880-5d17-4e3e-ab76-034567890"
$clientSecret = "jhZn8GsMear3VXO3XxMgUngPeeItusaljdsfljIEOOW="
$redirectUri = 'https://localhost/'
# Library required for System.Web.HttpUtility
Add-Type -AssemblyName System.Web
# UrlEncode the ClientID and ClientSecret and URL's for special characters
$clientIDEncoded = [System.Web.HttpUtility]::UrlEncode($clientid)
$clientSecretEncoded = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$resourceEncoded = [System.Web.HttpUtility]::UrlEncode($resource)
$scopeEncoded = [System.Web.HttpUtility]::UrlEncode("")
# Function to popup Auth Dialog Windows Form for getting an AuthCode
# So this will never work as part of the MA. Left here for manual run if Refresh.Token is lost and needs to be regenerated
Function Get-AuthCode {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=440;Height=640}
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=420;Height=600;Url=($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
$web.ScriptErrorsSuppressed = $true
$form.ShowDialog() | Out-Null
$queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
$output = @{}
foreach($key in $queryOutput.Keys){
$output["$key"] = $queryOutput[$key]
if (Test-Path $oAuthRefreshTokenPath ){
# We have a previous refresh token.
# use it to get a new token
$refreshtoken = Get-Content $oAuthRefreshTokenPath
"Previous Refresh Token Loaded $refreshtoken"| Out-File $DebugFile -Append
# Refresh the token
$body = "grant_type=refresh_token&refresh_token=$refreshtoken&redirect_uri=$redirectUri&client_id=$clientId&client_secret=$clientSecretEncoded&resource=$resource"
$Authorization = Invoke-RestMethod `
-Method Post -ContentType "application/x-www-form-urlencoded" `
-Body $body `
-ErrorAction STOP
$accesstoken = $Authorization.access_token
$refreshtoken = $Authorization.refresh_token | Out-File $oAuthRefreshTokenPath
"New Refresh Token Loaded $refreshtoken"| Out-File $DebugFile -Append
$refreshtoken | Out-File $DebugFile -Append
# Get Permissions (if the first time), get an AuthCode and Get a Bearer and Refresh Token
# Get AuthCode
$url = "$redirectUri&client_id=$clientID&resource=$resourceEncoded&scope=$scopeEncoded"
# Extract Access token from the returned URI
$regex = '(?<=code=)(.*)(?=&)'
$authCode = ($uri | Select-string -pattern $regex).Matches[0].Value
Write-output "Received an authCode, $authCode"
#get Access Token
$body = "grant_type=authorization_code&redirect_uri=$redirectUri&client_id=$clientId&client_secret=$clientSecretEncoded&code=$authCode&resource=$resource"
$Authorization = Invoke-RestMethod `
-Method Post -ContentType "application/x-www-form-urlencoded" `
-Body $body `
-ErrorAction STOP
Write-output $Authorization.access_token
$accesstoken = $Authorization.access_token
$refreshtoken = $Authorization.refresh_token | out-file $oAuthRefreshTokenPath
# ********************* Process objects with profile photos into the MA *******************
# Know where we are at in relation to the pagesize from the Run Profile
[int]$objectpagecount = 0
foreach ($Global:mbxphoto in $Global:mbxwithpictures){
# continue from where we go to from the previous page of objects processed
$Global:mbxphoto = $Global:mbxwithpictures[$global:objectsImported + 1]
# if we are at the end then set MoreToImport to False and quit
if (!$Global:mbxphoto -or ($global:objectsImported +1 -eq $Global:intMBXwithpictures)) {
# nothing left to process
$global:MoreToImport = $false
# Double check they have a photo
# Get all photos sizes stored for the user
try {
$images = Invoke-RestMethod -Method Get -Headers @{Authorization = "Bearer $accesstoken"
'Content-Type' = 'application/json'} `
-Uri "$($mbxphoto.UserPrincipalName)/photos"
catch {
"User not located in EXO" | Out-File $DebugFile -Append
$images = $null
if ($images.value.Count -gt 0){
# Get the Largest Image Size
[int]$numberofimages = $images.value.Count
$largestsize = $images.value[$numberofimages - 1].id
# Get the profile photo using the WebClient over Invoke-RestMethod so it is returned as Byte and not String
$url = "$($mbxphoto.UserPrincipalName)/photos/$($largestsize)/`$value"
$wc = New-Object System.Net.WebClient
$wc.Headers["Authorization"] = "Bearer " + $accesstoken;
$byteImg = $wc.DownloadData($url)
# Get a checksum for the profile image
[string]$imgchecksum = $byteImg | Get-Hash -Algorithm SHA256
"$($ has a $($largestsize) photo with checksum: $($imgchecksum)" | Out-File $DebugFile -Append
# Create Object in the MA CS
$obj = @{}
$obj.Add("ID", $mbxphoto.ExternalDirectoryObjectId)
$obj.Add("objectID", $mbxphoto.ExternalDirectoryObjectId)
$obj.Add("objectClass", "EXOUser")
# Pass the User Object to the MA
# Increase the object count
# for logging how many we've processed
# Debug Logging where we are up to
"Page count: " + $objectpagecount | Out-File $DebugFile -Append
"Objects Imported count: " +$global:objectsImported | Out-File $DebugFile -Append
"Objects Remaining count: "+($Global:intMBXwithpictures - $global:objectsImported -1) | Out-File $DebugFile -Append
# if number of processed objects equals the pagesize set MoreToImport as true and break
if ($objectpagecount -eq $pagesize)
$global:MoreToImport = $true
"More to Import: " + $objectpagecount | Out-File $DebugFile -Append
else {
"$($mbxphoto.DisplayName) hasn't uploaded a photo to EXO" | Out-File $DebugFile -Append
# Increase the object count
# for logging how many we've processed
# ***********************************************************
"Finished Import as: " + $OperationType + " " + (Get-Date) | Out-File $DebugFile -Append
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.