Export a users Exchange Online contact list/folder with Graph API!

Exchange Online 📩 er optimalisert for Microsoft Outlook epost klienten

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

#Specify users you need to extract contactlist from.
$users = "alexander@contoso.com"
#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
}
}
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s