
Short blog post today with a script that exports the root contact list or a specific folder of a users Exchange Online mailbox. You need an Azure application with the app right Contact.Read. If you don’t know how to use Graph API in PowerShell and create an Azure app, take a look here at my blog post on the topic: Getting started with Graph API and PowerShell
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
#Specify users you need to extract contactlist from. | |
$users = "[email protected]" | |
#Specify contactlist folder you need to export. Have added wildcard in the request, so if you are exporting all folders thats starts with Skype, enter "Skype" as foldername. | |
#Enter "root" as foldername if you want all contacts that are not in a folder. | |
$folder = "skype" | |
function Get-MSGraphAppToken{ | |
<# .SYNOPSIS | |
Get an app based authentication token required for interacting with Microsoft Graph API | |
.PARAMETER TenantID | |
A tenant ID should be provided. | |
.PARAMETER ClientID | |
Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID. | |
.PARAMETER ClientSecret | |
Web application client secret. | |
.EXAMPLE | |
# Manually specify username and password to acquire an authentication token: | |
Get-MSGraphAppToken -TenantID $TenantID -ClientID $ClientID -ClientSecert = $ClientSecret | |
.NOTES | |
Author: Jan Ketil Skanke | |
Contact: @JankeSkanke | |
Created: 2020-15-03 | |
Updated: 2020-15-03 | |
Version history: | |
1.0.0 – (2020-03-15) Function created | |
#> | |
[CmdletBinding()] | |
param ( | |
[parameter(Mandatory = $true, HelpMessage = "Your Azure AD Directory ID should be provided")] | |
[ValidateNotNullOrEmpty()] | |
[string]$TenantID, | |
[parameter(Mandatory = $true, HelpMessage = "Application ID for an Azure AD application")] | |
[ValidateNotNullOrEmpty()] | |
[string]$ClientID, | |
[parameter(Mandatory = $true, HelpMessage = "Azure AD Application Client Secret.")] | |
[ValidateNotNullOrEmpty()] | |
[string]$ClientSecret | |
) | |
Process { | |
$ErrorActionPreference = "Stop" | |
# Construct URI | |
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" | |
# Construct Body | |
$body = @{ | |
client_id = $clientId | |
scope = "https://graph.microsoft.com/.default" | |
client_secret = $clientSecret | |
grant_type = "client_credentials" | |
} | |
try { | |
$MyTokenRequest = Invoke-WebRequest –Method Post –Uri $uri –ContentType "application/x-www-form-urlencoded" –Body $body –UseBasicParsing | |
$MyToken =($MyTokenRequest.Content | ConvertFrom-Json).access_token | |
If(!$MyToken){ | |
Write-Warning "Failed to get Graph API access token!" | |
Exit 1 | |
} | |
$MyHeader = @{"Authorization" = "Bearer $MyToken" } | |
} | |
catch [System.Exception] { | |
Write-Warning "Failed to get Access Token, Error message: $($_.Exception.Message)"; break | |
} | |
return $MyHeader | |
} | |
} | |
$tenantId = 'xxxxxxxx' | |
$ClientID = 'xxxxxxxx' | |
$ClientSecret = "xxxxxxxx" | |
$global:Header = Get-MSGraphAppToken –TenantID $tenantId –ClientID $ClientID –ClientSecret $ClientSecret | |
function RefreshToken{ | |
Process{ | |
if(!$global:stopwatch){$global:stopwatch = [system.diagnostics.stopwatch]::StartNew()} | |
if(($global:stopwatch.Elapsed).Minutes -eq 45){ | |
$global:stopwatch = [system.diagnostics.stopwatch]::StartNew() | |
$global:Header = Get-MSGraphAppToken –TenantID $tenantId –ClientID $ClientID –ClientSecret $ClientSecret | |
} | |
} | |
} | |
function Get-GraphRequest($uri){ | |
Process { | |
$ErrorActionPreference = "Stop" | |
try { | |
#Graph API request that loops through every '@odata.nextLink'. | |
$content = while (-not [string]::IsNullOrEmpty($Uri)) { | |
# API Call | |
Write-Host "`r`nQuerying $Uri…" –ForegroundColor Yellow | |
try{ | |
RefreshToken | |
$apiCall = Invoke-WebRequest –Method "GET" –Uri $Uri –ContentType "application/json" –Headers $global:Header –ErrorAction Stop –UseBasicParsing | |
} | |
catch{ | |
Start-Sleep –Seconds 30 | |
$apiCall = Invoke-WebRequest –Method "GET" –Uri $Uri –ContentType "application/json" –Headers $global:Header –ErrorAction Stop –UseBasicParsing | |
} | |
$nextLink = $null | |
$Uri = $null | |
if ($apiCall.Content) { | |
# Check if any data is left | |
$nextLink = $apiCall.Content | ConvertFrom-Json | Select-Object '@odata.nextLink' | |
$Uri = $nextLink.'@odata.nextLink' | |
$apiCall.Content | ConvertFrom-Json | |
} | |
} | |
} | |
catch [System.Exception] { | |
Write-Warning "Failed to complete request, Error message: $($_.Exception.Message)"; break | |
} | |
return $content.value | |
} | |
} | |
foreach($user in $users){ | |
if($folder -eq 'root'){ | |
$usercontactfolders = Invoke-RestMethod –Method GET –Headers $global:Header "https://graph.microsoft.com/v1.0/users/$($user)/contactfolders/contacts" | |
foreach($usercontactfolder in $usercontactfolders){ | |
$contacts = Get-GraphRequest –uri "https://graph.microsoft.com/v1.0/users/$($user)/contactfolders/$($usercontactfolder.id)/contacts" | |
$customcontacts = @() | |
foreach($contact in $contacts){ | |
$myObject = [PSCustomObject]@{ | |
Name = $contact.displayName | |
email = ($contact.emailaddresses).name | |
} | |
$customcontacts += $myObject | |
} | |
$customcontacts | Export-Csv "c:\temp\$($user)-root.csv" –NoTypeInformation | |
} | |
} | |
Else{ | |
$usercontactfolders = Get-GraphRequest –uri "https://graph.microsoft.com/v1.0/users/$($user)/contactfolders" | Where-Object{$_.displayname -like "$($folder)*"} | |
foreach($usercontactfolder in $usercontactfolders){ | |
$contacts = Get-GraphRequest –uri "https://graph.microsoft.com/v1.0/users/$($user)/contactfolders/$($usercontactfolder.id)/contacts" | |
$customcontacts = @() | |
foreach($contact in $contacts){ | |
$myObject = [PSCustomObject]@{ | |
Name = $contact.displayName | |
email = ($contact.emailaddresses).name | |
} | |
$customcontacts += $myObject | |
} | |
$customcontacts | Export-Csv "c:\temp\$($user)–$($usercontactfolder.displayname).csv" –NoTypeInformation | |
} | |
} | |
} |
Nice post. I can’t seem to get contact photos exported – do you have a solution on that?
LikeLike
No, haven’t looked at that. Probably need a different type of request as you are downloading a file and not a JSON list
LikeLike