In my previous blog, I talked about how to use PowerShell with Microsoft Graph Reporting API. In that blog, I used the Client Credentials grant flow to acquire an access token for Microsoft Graph against the V1 endpoint. Also the code sample in that blog only works if all the reporting data result set is small. In this blog I’ll discuss how to get a Microsoft Graph access token using Client Credentials grant flow against the V2 endpoint and also add paging support to handle large result set which can span multiple pages.

As a pre-requisite, you will have to create an Application Registration in Azure Active Directory and configure the application to have Microsoft Graph Application Permission “AuditLog.Read.All” as laid out in my last blog. Remember to grant admin consent to the Microsoft Graph permissions as well.

Getting an Access Token

There are a couple of ways you can get an access token.

  1. Build a raw HTTP POST request for the v2 token endpoint (code below)
$ClientID = "<client id>"
$ClientSecret = "<client secret>"
$loginURL       = ""
$tenantdomain   = "<tenant name>" 
$scope1 = ""

$body = @{grant_type="client_credentials";scope=$scope1;client_id=$ClientID;client_secret=$ClientSecret}
$oauth = Invoke-RestMethod -Method Post -Uri $("$loginURL/$tenantdomain/oauth2/v2.0/token") -Body $body
Write-Host "Access Token: " $oauth.access_token
  1. Use MSAL.Net to acquire a token

I actually like this approach better than the first approach mainly due to MSAL’s capability of always getting a valid access token. By default, an access token is only valid for 1 hour. This can be problematic (especially with the first approach) if an application takes longer than 1 hour to page through a large result set with the same token. Once the token expires, MS Graph will return an error with regards to expired token. To be able to handle this scenario more effectively the application will have to take care of checking for token validity and sending out new request to get a fresh access token should the current one becomes expired. With MSAL, you don’t have to worry about token expiration. MSAL maintains its own token cache. With every AcquireTokenxxx API call, MSAL returns the token from its cache only if the token is still valid. If the existing cached token is about to expire or has expired, MSAL will automatically send out a new request to get a fresh token and return that new token to the client. Below is a sample PowerShell snippet using MSAL to acquire an access token for Microsoft Graph and then use the token for getting user sign-ins report.

Note: This PowerShell script has been tested with MSAL.Net v4.11.0 and Windows PowerShell v5.1. It might not be compatible with PowerShell Core.

$ClientID = "<client id>"
$ClientSecret = "<client secret>"
$loginURL       = ""
$tenantdomain   = "<tenant name>"
[string[]] $Scopes = ""

# Download MSAL.Net module to a local folder if it does not exist there
if ( ! (Get-ChildItem $Env:USERPROFILE/MSAL/lib/Microsoft.Identity.Client.* -erroraction ignore) ) {
    install-package -Source -ProviderName nuget -SkipDependencies Microsoft.Identity.Client -Destination $Env:USERPROFILE/MSAL/lib -force -forcebootstrap | out-null

# Load the MSAL assembly -- needed once per PowerShell session
[System.Reflection.Assembly]::LoadFrom((Get-ChildItem $Env:USERPROFILE/MSAL/lib/Microsoft.Identity.Client.*/lib/net45/Microsoft.Identity.Client.dll).fullname) | out-null

$global:app = $null

$ClientApplicationBuilder = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ClientID)
$global:app = $ClientApplicationBuilder.Build()

Function Get-GraphAccessTokenFromMSAL {
    [Microsoft.Identity.Client.AuthenticationResult] $authResult  = $null
    $AquireTokenParameters = $global:app.AcquireTokenForClient($Scopes)
    try {
        $authResult = $AquireTokenParameters.ExecuteAsync().GetAwaiter().GetResult()
    catch {
        $ErrorMessage = $_.Exception.Message
        Write-Host $ErrorMessage
    return $authResult

$myvar = Get-GraphAccessTokenFromMSAL
Write-Host "Access Token: " $myvar.AccessToken
$global:headerParams = @{}

Function Set-AuthHeader {
$global:headerParams = @{
    "Authorization" = "Bearer $((Get-GraphAccessTokenFromMSAL).AccessToken)"

$queryUrl = ""
$counter = 1
$d=(get-date -Uformat %Y%m%d).ToString()
$ReportPath = "$Env:USERPROFILE\$($d)_Report.csv"

    Write-Host "Getting Page " $counter
    $result = Invoke-RestMethod -Method Get -Uri $queryUrl -Headers $global:headerParams
    $content = $result.value
    # Write out content to csv file
    $content | Export-Csv -Path $ReportPath -Append -NoTypeInformation
    # Get the next page
    $queryUrl = $result."@odata.nextLink"

until (!($queryUrl))

Write-Host "The report can be found at " $ReportPath
Write-Host "end"

Different Types of Directory Reports

You can change the Microsoft Graph query above to get other types of Directory reports. Below are all the different report types available under the auditLogs path.

User Sign-ins reports

The query used in the above PowerShell code is for getting a list of all the user sign-ins report. Below are some other example queries using $filter:

List all sign-ins where appDisplayname starts with ‘Postman$filter=startswith(appDisplayName,'Postman')

List all user sign-ins between 2 different times$filter=createdDateTime ge 2020-04-06T11:54:00Z and createdDateTime le 2020-04-08T11:54:00Z

Note: The ‘$’ character in PowerShell has a special meaning to denote PowerShell variables. To use the above queries in PowerShell you need to escape the ‘$’ character using an Grave Accent or tick mark (`) character. An example of this is below:

$url = "`$filter=startswith(appDisplayName,'Postman')"

Directory Audit reports

Use the query below to get a list of audit logs generated by Azure Active Directory.

Provisioning reports

Use the query below to get all the provisioning events in your tenant

Query Parameters

As of the time of this writing the Reporting API only support these query clauses: $filter, $top, and $skiptoken. Depending on object types, only certain attributes support $filter query. Check the documentation on each audit log type for more info. The $select query is not supported either so if you want to limit the number of fields saved out to the Excel file you can modify line 59 in the above PowerShell snippet as followed

# only save out these fields to csv file:  appDisplayName, appId, and ipAddress
$content | select appDisplayName, appId, ipAddress | Export-Csv -Path $Env:USERPROFILE\Report.csv -Append -NoTypeInformation

Other Report Types

There are other types of reports existing under the “reports” path. These include Office 365 usage, ADFS application activity, Application sign-in summary, and Registration and usage. These endpoints will require different API Permission set. Check out the Reports documentation for more info.


Pull Azure AD Audit Report- Updated
Fetch Data from Microsoft Graph with PowerShell (Paging Support)
Adam Edwards’s PowerShellGraphDemo

Leave a Comment