-
-
Save darrenjrobinson/fdec4ef8721161574d866ef9941fdbf5 to your computer and use it in GitHub Desktop.
Exchange Online User Profile Photos PSMA Import
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param ( | |
$Username, | |
$Password, | |
$Credentials, | |
$OperationType, | |
[bool] $usepagedimport, | |
$pagesize | |
) | |
$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 | |
} | |
else | |
{ | |
$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 | |
} | |
else | |
{ | |
$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 https://outlook.office365.com/powershell-liveid/ -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 = "https://graph.microsoft.com" | |
# 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("https://graph.microsoft.com/User.Read.All") | |
# 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 | |
$web.Add_DocumentCompleted($DocComp) | |
$form.Controls.Add($web) | |
$form.Add_Shown({$form.Activate()}) | |
$form.ShowDialog() | Out-Null | |
$queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query) | |
$output = @{} | |
foreach($key in $queryOutput.Keys){ | |
$output["$key"] = $queryOutput[$key] | |
} | |
$output | |
} | |
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 https://login.microsoftonline.com/common/oauth2/token ` | |
-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 | |
} | |
else | |
{ | |
# Get Permissions (if the first time), get an AuthCode and Get a Bearer and Refresh Token | |
# Get AuthCode | |
$url = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&redirect_uri=$redirectUri&client_id=$clientID&resource=$resourceEncoded&scope=$scopeEncoded" | |
Get-AuthCode | |
# 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 https://login.microsoftonline.com/common/oauth2/token ` | |
-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 | |
break | |
} | |
# 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 "https://graph.microsoft.com/v1.0/users/$($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 = "https://graph.microsoft.com/v1.0/users/$($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 | |
"$($mbxphoto.name) 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") | |
$obj.Add("EXOUserPrincipalName",$mbxphoto.userPrincipalName) | |
$obj.Add("EXODisplayName",$mbxphoto.displayName) | |
$obj.Add("EXOAlias",$mbxphoto.Alias) | |
$obj.Add("EXOonPremiseSID",$mbxphoto.ExternalDirectoryObjectId.tobyte()) | |
$obj.Add("EXOMail",$mbxphoto.PrimarySmtpAddress) | |
$obj.Add("EXOPhoto",$byteImg) | |
$obj.Add("EXOPhotoChecksum",$imgchecksum) | |
# Pass the User Object to the MA | |
$obj | |
# Increase the object count | |
$objectpagecount++ | |
# for logging how many we've processed | |
$global:objectsImported++ | |
# 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 | |
break | |
} | |
} | |
else { | |
"$($mbxphoto.DisplayName) hasn't uploaded a photo to EXO" | Out-File $DebugFile -Append | |
# Increase the object count | |
$objectpagecount++ | |
# for logging how many we've processed | |
$global:objectsImported++ | |
} | |
} | |
# *********************************************************** | |
"Finished Import as: " + $OperationType + " " + (Get-Date) | Out-File $DebugFile -Append | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment