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.
TL;DR
The script: O365-Azure-Scripts/Get-StaleUsersReport.ps1 at master · engineererr/O365-Azure-Scripts (github.com).
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 examles.md file 🤞
Pull request 👉 Added a confidential client usage example to readme.me by engineererr · Pull Request #35 · AzureAD/MSAL.PS (github.com)
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: https://graph.microsoft.com/beta/users?filter=signInActivity/lastSignInDateTime 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 4.2.1.3
Import-Module MSAL.PS
$tenantID = "YOURTENANTID"
$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 'https://graph.microsoft.com/.default' -ConfidentialClientApplication $ConfidentialClient
$apiUrl = "https://graph.microsoft.com/beta/users?filter=signInActivity/lastSignInDateTime 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!