Graph API adds support for $count and $search query parameters

This year’s Build conference seems to be run under the “back to basics” moto, and we’ve seen some monumental improvements being announced, such as CTRL+F support for Teams (!) and similarly, a basic search query support for the Graph API. Sarcasm aside, we’re finally seeing (the beginning of) some meaningful improvements in that space, so let’s cover the new operators.

To recap the situation until now: searching and filtering capabilities in the Graph leave a lot to be desired. Surely, they’ve received some improvements over the years, and some of the most annoying issues are due to poor implementation in clients (*cough* case-sensitivity for parameter and property names *cough*), but we were still stuck with tons of limitations. Now, there’s light in the end of the tunnel, but still a long road ahead. Without further ado, meet the $search query parameter.

A query containing the $search parameter will act like a filter and in turn is combined with any filter query in a logical AND configuration. What’s new is the fact that $search query can match more than just the beginning of a string (as in the startwith parameter), but sadly only two properties currently support this: displayName and description. For all others, $search query will act exactly like $startswith one, in other words kinda useless. Let’s take a look at some examples:

$uri = 'https://graph.microsoft.com/beta/users?$count=true&$search="displayName:US"'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).Value | select displayName,UserPrincipalName

displayName userPrincipalName
----------- -----------------
SfBMailUser SfBMailUser@michev.info
Vasil US    vasilUS@michev.info

In this example, we’ve used the $search query parameter to return just objects whose displayName property contains the value “US”. The results include one object where the match is a substring of the whole value and another one, where it matched a ‘word’ at the end of the property value. Both of these wouldn’t be possible until now.

Before we move to other examples, a few words about the syntax. The $search query parameter requires you to specify the property against you are performing the search, and as mentioned above the only two properties currently supported are displayName and description. You need to separate the property and value to look for via colon (:). Multiple values can be provided, in either logical AND or logical OR configuration, with parenthesis used to specify precedence. And, the funky part, you can also use a tokenized string, delimited either by space, casing, special characters or numbers. The search will then be performed against all the individual “tokens”, in a logical AND configuration. Here’s an example:

$uri = 'https://graph.microsoft.com/beta/servicePrincipals?$count=true&$search="displayName:MicrosoftTeams"'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).Value | select displayName

displayName
-----------
Microsoft Teams Task Service
Microsoft Teams
Microsoft Teams Mailhook
MicrosoftTeamsCortanaSkills
Microsoft Teams RetentionHook Service
App Studio for Microsoft Teams

In the above, we are performing the query against the /servicePrincipals endpoint, to illustrate the fact that all directory object types are supported. We used the string value of “MicrosoftTeams”, which when tokenized is split to “Microsoft” and “Teams”. Matches against the individual tokens are not case-sensitive, supposedly, thus the query will search for every service principal that contains both the tokens in its display name. I’ve trimmed the output a bit, but you can still see several types of matches.

Now, according to the official documentation, matches should be case insensitive, meaning “Teams” should match “teams”, “TEams” and so on. In reality, things are a bit different, as tokenization seems to also be applied against the string we are examining for matches. Thus, if we add say the “Hook” string to our search query, we now have three different substrings against which matches will occur (as “MicrosoftTeamsHook” is tokenized to “Microsoft”, “Teams” and “Hook”). Given no case sensitivity, one would expect that the “hook” bit will match both “Mainhook” and “RetentionHook” in the above. The actual result however is different:

$uri = 'https://graph.microsoft.com/beta/servicePrincipals?$count=true&$search="displayName:MicrosoftTeams" AND "displayName:hook"'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).Value | select displayName

displayName
-----------
Microsoft Teams RetentionHook Service

Turns out, only the entry containing “RetentionHook” is returned, most likely because only it matches the “hook” part (after being tokenized to “Retention” and “hook”). The “Mainhook” entry on the other hand is not tokenized and thus fails to match “hook”. Or at least I think so, but the below proves me wrong:

$uri = 'https://graph.microsoft.com/beta/users?$search="displayName:new"'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).Value | select displayName

displayName
-----------
newMEU

$uri = 'https://graph.microsoft.com/beta/users'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).Value | select displayName | ? {$_ -match "new"}

displayName
-----------
newMEU
sharednew
WCnew

So a simple query looking for matches for the “new” token. The first entry, “newMEU” being broken down to “new” and “MEU” matches OK. The second one we can accept as not being tokenized, so not matching. The third one though should be tokenized to “WC” and “new” and should match?! I think at this point I’m back to proclaiming filtering in Graph is crap…

Anyway, before I end on this note, I shouldn’t forget to mention that in order for any of the above examples to work, you need to add an additional header to your requests. Namely, the “ConsistencyLevel” with value set to “eventual”, as detailed in this blog article.

To end on a positive note, the $count parameter seems to do its job right and gives you a quick way to get a total number of entries returned by a given query. Granted, you can do this yourself, but it’s valuable addition for scenarios where you want to just get the object count, without actually fetching any data. For example, the below query will get me the count of all users within my tenant:

$uri = 'https://graph.microsoft.com/beta/users/$count'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
$gr.Content

Be careful where you put the parameter though, for example the following will instead return the full set of objects, without giving the count:

$uri = 'https://graph.microsoft.com/beta/users?$count'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
$gr.Content

and this one will give you both the count and list of objects:

$uri = 'https://graph.microsoft.com/beta/users?$count=true'
$Gr = Invoke-WebRequest -Headers $AuthHeader -Uri $uri
($gr.Content | ConvertFrom-Json).'@odata.count'

Some other improvements are also available, like being able to use both $filter and $orderby in the same query. Refer to the aforementioned blog article or the official documentation for details on those.

This entry was posted in Azure AD, Graph API, Office 365. Bookmark the permalink.

One Response to Graph API adds support for $count and $search query parameters

  1. Luca Spolidoro says:

    Thank you Vasil, your comments are on point!

    I updated the original post in the Microsoft Blogs to clarify few things.
    We know that search needs improvements, but for now we cannot offer a true “contains” search for performance reasons.
    Rest assured we are working on it, and thanks again for this post; we need feedback like this.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.