How to use PowerShell to call Graph API endpoints – Engineerer

How to use PowerShell to call Graph API endpoints

It’s just fantastic how hard it is to call the Graph API REST endpoints through PowerShell. I couldn’t let loose from this – from my point of view – big issue and went from problem to solution.


The script: O365-Azure-Scripts/Get-StaleUsersReport.ps1 at master · engineererr/O365-Azure-Scripts (

Quickly I stumblet upon…

… a PowerShell module that could really do the trick. It’s a wrapper around MSAL.NET and is found here: GitHub – AzureAD/MSAL.PS.

Perfect! Only there is not that much info about how to use the module. This info is to be acquired through the MSAL.NET documentation. I definitely will make a pull request to the author to add an file 🤞

Pull request 👉 Added a confidential client usage example to by engineererr · Pull Request #35 · AzureAD/MSAL.PS (

The example

This customer wanted to know which users haven’t signed in for a while through AAD. With this indicator, it’s possible to identify inactive user account that should probably be locked or deleted. Microsoft covers this scenario in their docs How to manage inactive user accounts in Azure AD | Microsoft Docs as well.

Note that to use this specific endpoint an AAD P1 license is required for the one that is reading from this endpoint.

What we have

  • The Graph Endpoint: le 2021-06-21T00:00:00Z
  • The method: GET
  • The permissions needed:
    • User.Read.All
    • AuditLogs.Read.All
    • Organization.Read.All

What to prepare

Create an Azure AD App principal like Patrick Lamber described in his blog post How do I authenticate towards Graph API with PowerShell? | by Patrick Lamber | Medium. Don’t forget to add the required API permissions.

Side note: Patrick’s blog post inspired me to go deeper into this topic. Unfortunately, the DLL Patrick used is already ancient (the DLL was replaced by the MSAL.NET that we use in this blog post) by IT standards😅

The script

It’s not necessary to use the $ConfidentialClient but it’s the recommended approach. The reason is the authentication flow suits the respective scenario and token caching is baked in as well. More is described in the article Public and confidential client apps (MSAL) – Microsoft identity platform | Microsoft Docs.

Install-Module -Name MSAL.PS -RequiredVersion
Import-Module MSAL.PS
$appId = "YOURAPPID"
$appSecret = "YOURAPPSECRET"

$ConfidentialClientOptions = New-Object Microsoft.Identity.Client.ConfidentialClientApplicationOptions -Property @{ ClientId = $appId; TenantId = $tenantID; ClientSecret = $appSecret }
$ConfidentialClient = $ConfidentialClientOptions | New-MsalClientApplication
$tokenObj = Get-MsalToken -Scope '' -ConfidentialClientApplication $ConfidentialClient
$apiUrl = " le 2021-06-21T00:00:00Z&`$select=userPrincipalName,displayName,mail,signInActivity"
$res = Invoke-RestMethod -Headers @{Authorization = "Bearer $($tokenObj.AccessToken)"} -Uri $apiUrl -Method Get
$res.value | select userPrincipalName, displayName, mail, @{L="LastSignInDateTime";E={$_.signInActivity.lastSignInDateTime}} | Sort-Object -Property LastSignInDateTime

Finishing line

The hardest part was to understand that to use the client credential flow we need to create a ConfidentialClient that is then used to acquire the token.

And then, I completely forgot to escape the $-signs in the $apiUrl variable. It’s done with backticks before the to-be-escaped characters.

So, now we have the toolset to authenticate and call Graph API REST endpoints. What do YOU make with that? I think great times lie ahead of us – have fun!