How To Handle Microsoft Graph Paging in PowerShell

Even though Microsoft Graph has been around for some years now, I’m going out on a limb here and say that it’s still quite new to a lot of…

Even though Microsoft Graph has been around for some years now, I’m going out on a limb here and say that it’s still quite new to a lot of engineers. This article won’t cover basic topics like what Graph is or how to get started. It focuses on a rather specific topic with which I struggled with at first: pagination (also known as paging).

As the name suggests, we’re talking about multiple pages of results, if a response is too large for one request/response or if the page size is “hardcoded” by Microsoft. Microsoft also offers a dedicated article on Microsoft Learn about that.

For example, if we want to query all the users in an Azure AD tenant and that tenant has 10'000 users, it wouldn’t make sense to return all these objects in a single request.

Let’s continue with the example of querying an organization’s Azure AD users. The official documentation, which also includes a note about the default page size can be found here.

The default and maximum page sizes are 100 and 999 user objects respectively.

This means that if we make a request like this:

1
$aadUsers2 = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" -Headers $Header -Method Get -ContentType "application/json")

Graph will only return the first 100 users, even though we didn’t include any filter in the request URL.

If there are less than 100 users, all users will be returned of course. We can see that this is the case because the variable $aadUsers2 does not contain an @odata.nextLink property.

Because Microsoft Graph uses the OData (Open Data Protocol) for their REST API, we can also specify the page size by including the $top filter. In fact, I need to use this to simulate and demonstrate paging because I don’t have more than 100 users in my test tenant.

In this example, I’m using ?`$top=5 to make Graph only return 5 objects per page. The ` before the $ sign is needed to escape the character.

1
$aadUsers = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users?\`$top=5" -Headers $Header -Method Get -ContentType "application/json")  

Here we can see that the result now includes an @odata.nextLink property. If a Get request is made to this URL, we will receive the next 5 users.

In a more realistic scenario, a tenant may have tens of thousands of Azure AD users though. So, setting the top filter to 999 is more realistic but still wouldn’t return all the users. But what if we really need to get all the users?

How can we keep requesting the next page until we have received all the pages/objects?

I’ve come up with a very simple solution for this. We’ll just use a do-until loop in PowerShell which will keep requesting the next page via the URL returned by the @odata.nextLink until there’s no @odata.nextLink property anymore. Once it’s done, we’ll have all the users in the $allPages variable. And finally, we set the initial variable $aadUsers to the value of $allPages.

Please be aware that you will need to include your own Authorization Header before you are able to make any requests towards Microsoft Graph. A very comprehensive guide about that can be found here for example.

For most of these commands, like getting users, there are also PowerShell Modules and Cmdlets available. However, I like to use PowerShell web requests (Invoke-RestMethod ) to reduce dependencies. This way, I don’t need to install any modules if I ever need to run this code from another system or a serverless Azure Function, for example.

Let me know what you think. Do you think that’s useful, or have you found a better way to handle paging in Microsoft Graph?

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Hosted on GitHub Pages
Built with Hugo
Theme Stack designed by Jimmy