Using Microsoft Graph API with Powershell

Microsoft Graph API gives you the ability to interact with the continually evolving Azure services through a single endpoint: https://graph.microsoft.com. We are going to connect to Graph with Powershell, OAuth 2.0 and REST.


Registering your App

To interact with Graph we need to register our app. Go to https://apps.dev.microsoft.com/portal/register-app to get started. Login with your account that is associated with your tenant. Add a new App and enter the following details:

  • Name > just give it a name
  • Application Type > Web-app/-API
  • Sign on Url > Doesn’t matter so we go for: http://localhost:8000

Then select you newly created App and goto Required permissions, here we are going to add the Graph API permission. You will need to select different permission depending on what you want to access. You can change this later, so for now we click Add on the top, select Microsoft Graph and in step 2 we just select Read and write access to user profile. 

The last step is to create a key, go to Keys and enter a Key Description and set the duration to Never Expires. When you click on Save the value (ClientSecret) will only be displayed once, so write it done when it’s displayed.

 

icon
icon

Learn Ethical Hacking From Scratch

At udemy

Become an ethical hacker that can hack computer systems like black hat hackers and secure them like security experts.

Authentication and Authorization

To access the Graph API we need to get an Access Code. If you are new to working with Rest and Graph API then there are a few things to keep in mind:

  1. To get an Access Token you need an Authorization Code. The code is only 1 hour valid, but as long as your refresh token is valid, you only need to renew this every 90 days.
  2. The Access Token, which is used in every request is only valid for 1 hour. You can renew it with the Refresh Token.
  3. The Refresh Token is valid for 14 days.

So the first step is to get the Authentication code. We use the script below for this. Run the scripts with :

GraphAPIGetAuthCode.ps1 -ClientId <clientId> -ClientSecret <clientSecret> -RedirectUrl <redirectUrl>

It will store the Authentication code in a text file at the script root location. This way we can easily access it later.

[CmdletBinding()]
PARAM(
	[parameter(ValueFromPipeline=$true,
				ValueFromPipelineByPropertyName=$true,
				Mandatory=$true)]
	[string]$ClientId,

	[parameter(ValueFromPipeline=$true,
				ValueFromPipelineByPropertyName=$true,
				Mandatory=$true)]
	[string]$ClientSecret,

	[parameter(ValueFromPipeline=$true,
				ValueFromPipelineByPropertyName=$true,
				Mandatory=$true)]
	[string]$RedirectUrl
)
BEGIN
{
	$ResourceUrl = "https://graph.microsoft.com"
}
PROCESS
{
	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
	}

	# UrlEncode the ClientID and ClientSecret and URL's for special characters 
	$clientIDEncoded = [System.Web.HttpUtility]::UrlEncode($ClientId)
	$clientSecretEncoded = [System.Web.HttpUtility]::UrlEncode($ClientSecret)
	$redirectUrlEncoded =  [System.Web.HttpUtility]::UrlEncode($RedirectUrl)
	$resourceUrlEncoded = [System.Web.HttpUtility]::UrlEncode($ResourceUrl)
	$scopeEncoded = [System.Web.HttpUtility]::UrlEncode("https://outlook.office.com/user.readwrite.all")

	# Get AuthCode
	$url = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&redirect_uri=$redirectUrlEncoded&client_id=$clientID&resource=$resourceUrlEncoded&prompt=admin_consent&scope=$scopeEncoded"

	Get-AuthCode
	
	# Extract Access token from the returned URI
	$regex = '(?<=code=)(.*)(?=&)'
	$authCode = ($uri | Select-string -pattern $regex).Matches[0].Value

	# Store AuthCode
	Set-Content "$PSScriptRoot\AuthCode.txt" $authCode
}

Thanks to Darren Robinson: https://gist.github.com/darrenjrobinson/b74211f98c507c4acb3cdd81ce205b4f#file-ps2graphapi-ps1

Get the Access and Refresh Token

With the Authentication code we can get the all important Access token and the much needed refresh token. Because the Access token is only 1 hour valid, we need to check it’s age and request a new one with the refresh token if it’s expired.

The script below  stores the access and refresh token in text files in the script root location. You can always change it to store it just in variables, but keep in mind, when you close the shell it’s all gone and you need to start again with the Authorization code.

The scripts checks the last modified time of the accesstoken.txt, if it’s older than 1 hour, it will request a new access token with the refresh token. If that fails, it will request a new Authorization code and start from their again.

I use a config.json file to store my ClientId, ClientSecret and RedirectUrl, this way you can easily change it.

Config.json

{
  "AppId": {
    "ClientId": "fa9d9d34-7a2a-****-****-************",
    "ClientSecret": "J/WDSKLjE+XC9G0+ASDHUen31/***********=",
    "RedirectUrl": "https://localhost:8000",
    "ResourceUrl": "https://graph.microsoft.com"
  }
}

ConnectTo-Graph

#----------------------------------------------------------[Declarations]----------------------------------------------------------

#Get the config file
$config = Get-Content $PSScriptRoot"\config.json" -Raw | ConvertFrom-Json

# Add System Web Assembly to encode ClientSecret
Add-Type -AssemblyName System.Web

# Encode ClientSecret
$clientSecretEncoded = [System.Web.HttpUtility]::UrlEncode($config.AppId.ClientSecret) 

# Get the accessToken
If ((Test-Path -Path $PSScriptRoot"\accessToken.txt") -ne $false) {
	$accessToken = Get-Content $PSScriptRoot"\accessToken.txt"
}

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

# Check if the AccessToken is not older then 1 hour
If (($accessToken -eq $null) -or ((get-date) - (get-item $PSScriptRoot"\accessToken.txt").LastWriteTime).TotalHours -gt 1) {

	# Get the refreshToken
	$refreshToken = Get-Content $PSScriptRoot"\refreshToken.txt"

	$clientId = $config.AppId.ClientId
	$clientSecret = $config.AppId.clientSecret
	$redirectUrl = $config.AppId.RedirectUrl
	$resourceUrl = $config.AppId.ResourceUrl
	
	Try {
		$refreshBody = "grant_type=refresh_token&redirect_uri=$redirectUrl&client_id=$clientId&client_secret=$clientSecretEncoded&refresh_token=$refreshToken&resource=$resourceUrl"

		$Authorization = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token `
			-Method Post -ContentType "application/x-www-form-urlencoded" `
			-Body $refreshBody `
			-UseBasicParsing
	}
	Catch {
		$webResponse = $_.Exception.Response
	}

	If ($webResponse -ne $null) {
		# Get Authorization code
		GraphAPIGetAuthCode.ps1 -ClientId $clientId -ClientSecret $clientSecret -RedirectUrl $redirectUrl   

		$authCode = get-content $PSScriptRoot"\authCode.txt"
		$body = "grant_type=authorization_code&redirect_uri=$redirectUrl&client_id=$clientId&client_secret=$clientSecretEncoded&code=$authCode&resource=$resourceUrl"

		$Authorization = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token `
			-Method Post -ContentType "application/x-www-form-urlencoded" `
			-Body $body `
			-UseBasicParsing
	}

	# Store refreshToken
	Set-Content $PSScriptRoot"\refreshToken.txt" $Authorization.refresh_token

	# Store accessToken
	$accessToken = $Authorization.access_token
	Set-Content $PSScriptRoot"\accessToken.txt" $accessToken
} 

Getting actual date with Invoke-RestMethod from Graph

After all is done we can interact with Graph as followed:

Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} `
                  -Uri  https://graph.microsoft.com/v1.0/me `
                  -Method Get

You can download the scripts here: https://github.com/ruudmens/SysAdminScripts/tree/master/Graph

Get more stuff like this

IT, Office365, Smart Home, PowerShell and Blogging Tips

I hate spam to, so you can unsubscribe at any time.

3 thoughts on “Using Microsoft Graph API with Powershell”

  1. Hi
    I use localhost:8000 as my app adress in azure,
    Got error AADSTS90102: ‘redirect_uri’ value must be a valid absolute Uri.
    Any ideas?

  2. Good blog you have here.. It’s difficult to find high-quality writing like yours nowadays.
    I seriously appreciate people like you! Take care!!

Leave a Comment