How to GRACEFULLY check if “Id” is User or SharePoint Group in Power Automate

In this blog post, we’re going to see different ways to check if an Id refers to a User or a SharePoint group. They will all work, HOWEVER, doesn’t mean they’re good! And I’ll tell you why for each case. It’s a long post, but it’s truly worth understanding.

Spoiler: Think about error handling in your flows 😉

Why checking the “Id”?

First of all, why would you check if an Id is user or SharePoint group? Well, the simplest example would be that in all approval processes, we go in sequence: Approver1, approver2, etc. It’s not difficult to imagine that each approver can not only be a user or a SharePoint group, but each field (column) could potentially be multi-select, adding even more complexity.

Finally, depending on the result, we perform different steps.

Backend list & Approvers details

For each scenario, we’ll use the same list.

  • List name: Requests
  • Fields:
    • Title (built-in column)
    • Approvers (Allow People and Groups – single select)

Now in Power Automate, we can use the Get items action, but I’ll be using an HTTP request to SharePoint and get specific results.

My Uri will be like this:

_api/web/lists/GetById('<ListID>')/items?$select=Approvers/Id,Title&$expand=Approvers

The results of this action will look like this:

{
  "d": {
    "results": [
      {
        "__metadata": {
          "id": "<guid>",
          "uri": "https://MyDomain.sharepoint.com/sites/M365ServiceRequests/_api/Web/Lists(guid'<ListID>')/Items(1)",
          "etag": "\"3\"",
          "type": "SP.Data.RequestsListItem"
        },
        "Approvers": {
          "__metadata": {
            "id": "<guid>",
            "type": "SP.Data.UserInfoItem"
          },
          "Id": 6
        },
        "Title": "Test1"
      },
      {
        "__metadata": {
          "id": "<guid>",
          "uri": "https://MyDomain.sharepoint.com/sites/M365ServiceRequests/_api/Web/Lists(guid'<ListID>')/Items(2)",
          "etag": "\"5\"",
          "type": "SP.Data.RequestsListItem"
        },
        "Approvers": {
          "__metadata": {
            "id": "<guid>",
            "type": "SP.Data.UserInfoItem"
          },
          "Id": 20
        },
        "Title": "Test2"
      },
      {
        "__metadata": {
          "id": "<guid>",
          "uri": "https://MyDomain.sharepoint.com/sites/M365ServiceRequests/_api/Web/Lists(guid'<ListID>')/Items(3)",
          "etag": "\"2\"",
          "type": "SP.Data.RequestsListItem"
        },
        "Approvers": {
          "__metadata": {
            "id": "<guid>",
            "type": "SP.Data.UserInfoItem"
          },
          "Id": 12
        },
        "Title": "Test3"
      }
    ]
  }
}

We can see that for each request (blue), we have the Id of each approver (yellow). This is the Id that we need to check!

Endpoints

We have 2 different endpoints for Users or SharePoint groups:

So let’s try different ways, and see if there’s anything else we can think about 😏

Scenario 1: Scopes

For this first scenario, we’ll use scopes. If the endpoint (see above) that we choose works, then continue. If the action using this endpoint fails, then go to the next scope. Let’s give it a try…

We loop through the outputs of the previous action:

outputs('Get_approvers_details')?['body/d/results']

The formula for the Id in the Uri is:

item()?['Approvers']?['Id']

Then, we change the Configure Run After. If “Id is User” action fails, then the scope called Approver is SPO group will run the actions inside.

Results

First loop, User succeeds, so it does not go to the SPO group scope ✅

Second loop, User fails, so it goes to the SPO group scope ✅

Then third loop (user) is the same as the first loop.

👉 Why is it bad?

Because when you add error handling (Try/Catch scopes), it will give you a false positive.

If any of the actions in the “Approver is User” scope fail, then it goes to the SPO group scope. But surely we’ll have more steps to perform in that scope?!

Let’s make the flow fail on purpose; I’ll add a Compose action with the formula:

div(1,0)

The Id was a user, but my next action failed. It went to the SPO group scope (as defined by the Configure Run After condition), and it never went to the Catch scope… 😥

💡 Another reason to avoid that scope scenario is that, depending on the complexity of your flow, you may find yourself with too many nested actions, and when you try to save the flow, “computer says no!”. You’ve reached the maximum level of 8 and you’re above the allowed limit for your nested actions…

Scenario 2: Parallel branching

While parallel branching may reduce the nested actions issues with a flat structure, it’s also not a recommended solution.

Let’s keep the same actions as before, and add parallel branching, where if the “Id is User” action fails, we configure the Run after, and we’ll go to the SharePoint group potential steps.

👉 Why is it bad?

Adding error handling with Try/Catch scopes, this one is even more painful to watch 😅

The action failed (expected for the group), then it went to the correct branch, but the flow failed (as it should with error handling practices)…

Kind of a false negative this time? No. Power Automate is doing what it’s supposed to be doing!

Scenario 3: Using variables

Still on a flat structure, we’re going to use Variables. If the “Id is User” action is successful, we’ll set the variable (string) to “User“, otherwise to “Group“. We need to play around with the Configure Run After, and also a condition.

Initialize the variable at the top of the flow (as a String – no value), and then it’ll look like that:

  • Set varUserOrGroup to User action: Will run if previous step is successful – No changes to make
  • Set varUserOrGroup to Group action: Change the Configure Run After to (if Set varUserOrGroup to User) is skipped
  • Condition: Change the Configure Run After to run (if Set varUserOrGroup to Group) is successful or is skipped

Results

Works great! Oh wait… We need error handling! 😁

Well? It still works… Sure does. BUT…

👉 Why is it bad?

  1. We’re once again into nested actions (long and complex processes will have issues)
  2. What if your process requires other actions in-between the variables?
  3. What if those in-between actions fail?
  4. What if Power Automate “has a moment” (😅) and the variable has a value of User instead of Group?
  5. This is another potential false positive
  6. What if…………………..

No, no, no… There are just too many what-ifs. Let’s not take the risk to build something that we know is not good. We need a robust flow that can have error handling, and report true problems!

The MAGIC endpoint!

Here we are! The 3rd endpoint that will save the bacon 😁

In SharePoint, there’s a list (/SiteUserInfoList) that will give you details about a user or a SharePoint group with an Id. And that endpoint is the one we should be using:

_api/web/SiteUserInfoList/items(<User_or_SPOGroup_Id>)

So, changing the endpoint in the Apply to each loop, here’s the most useful part of the JSON output.

If Id is USER:


    "FileSystemObjectType": 0,
    "Id": 6,
    "ServerRedirectedEmbedUri": null,
    "ServerRedirectedEmbedUrl": "",
    "ContentTypeId": "0x010A00EE6C93D60........",
    "Title": "Veronique Lengelle",
    "OData__ColorTag": null,
    "ComplianceAssetId": null,
    "Name": "i:0#.f|membership|vero@MyDomain.onmicrosoft.com",
    "EMail": "vero@MyDomain.onmicrosoft.com",
    "OtherMail": null,
    "UserExpiration": null,
    "UserLastDeletionTime": null,
    "MobilePhone": null,
    "Notes": null,
    "SipAddress": "vero@MyDomain.onmicrosoft.com",
    "IsSiteAdmin": true,
    "Deleted": false,
    "UserInfoHidden": false
      

If Id is GROUP:


    "FileSystemObjectType": 0,
    "Id": 20,
    "ServerRedirectedEmbedUri": null,
    "ServerRedirectedEmbedUrl": "",
    "ContentTypeId": "0x010B0063846A403.........",
    "Title": "Group1Test",
    "OData__ColorTag": null,
    "ComplianceAssetId": null,
    "Name": "Group1Test",
    "EMail": null,
    "OtherMail": null,
    "UserExpiration": null,
    "UserLastDeletionTime": null,
    "MobilePhone": null,
    "Notes": null,
    "SipAddress": null,
    "IsSiteAdmin": null,
    "Deleted": false,
    "UserInfoHidden": false,
    "Picture": null,
    "Department": null,
    "JobTitle": null,
    "FirstName": null,
    "LastName": null,
    "WorkPhone": null,
    "UserName": null,
    "WebSite": null,
    "SPSResponsibility": null,
    "Office": null,
    
  

The ContentTypeId will give you information about User, SharePointGroup, and others. But think about your ALM and create environment variables if you decide the ContentTypeId to be your “anchor” in the condition.

Let’s go with the simplest choice of the EMail which is null if it’s a SharePoint group.

Results

The action will not fail unless there’s a real problem of course. And in that case, your Try/Catch will not give you a false positive! 🥳 The properly handled green checkmarks are also better looking don’t you think?

Thanks for reading! 🙂

Discover more from Veronique's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading