Exception Handling in Power Automate
Tags: powerautomate, powerplatform, nocode, rpa
Published: 2023-01-18 09:31:54

Exception handling is key to all automations, as no matter how hard we try there will always be unexpected behaviour

The run after isn't the most intuitive exception handling I've seen (Blue Prism does it better), but once you used it a few times it's not too bad.

## How To

Exception handling is configure in the run after setting. As good practice I recommend in most cases you should catch fails and timeouts.

![run after](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thbaoiftd3r6n3fljmkm.png)

Parallel branches can be used to divert and handle exceptions, with either the branch ending the flow or rejoining after handling the exception.
`Scopes` are key to exception handling, as they create a block that can be handled the same i.e if ay action fails within the `Scope` it will trigger the exception handling. Additionally wrapping all your exception actions within its own `Scope` ensures all those actions run.

![error logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6cm48kjxcvt0jm33bbv.png)

The exception detail can be found and used with the following 2 expressions

actions() - For action exception
result() - For container (e.g scope/condition/loop)

No just to makes things more interesting I have (so far) found 3 different exception schemas, so for The actions() we have::


Type 1
```
{
  "name": "Compose",
  "startTime": "2023-11-28T11:24:54.1360838Z",
  "endTime": "2023-11-28T11:24:54.1368194Z",
  "trackingId": "238f1d13-aafc-4aaf-ad64-35123f51011b",
  "clientTrackingId": "08585004363290835095277375624CU45",
  "clientKeywords": [
    "testFlow"
  ],
  "code": "BadRequest",
  "status": "Failed",
  "error": {
    "code": "InvalidTemplate",
    "message": "Unable to process template language expressions in action 'Compose' inputs at line '0' and column '0': 'The template language function 'int' was invoked with a parameter that is not valid. The value cannot be converted to the target type.'."
  }
}
```

Type 2
```
{
  "name": "HTTP",
  "inputs": {
    "host": {
      "apiId": "subscriptions/72c9792d-e7e6-4eb5-b764-fc599b9b3005/providers/Microsoft.Web/locations/westus/runtimes/unitedstates-002/apis/sharepointonline",
      "connectionReferenceName": "shared_sharepointonline",
      "operationId": "HttpRequest"
    },
    "parameters": {
      "dataset": "https://sharepoint.com/sites/",
      "parameters/method": "GET",
      "parameters/uri": "_api/test?excpetion=true"
    }
  },
  "outputs": {
    "statusCode": 404,
    "headers": {
      "Cache-Control": "no-store, no-cache",
      "Pragma": "no-cache",
      "Set-Cookie": "ARRAffinity=40237ffdc57de1390eeff374e782e979bae0af189a51754a0bc4cff0e861cdf3;Path=/;HttpOnly;Secure;Domain=sharepointonline-scus.azconn-scus-001.p.azurewebsites.net,ARRAffinitySameSite=40237ffdc57de1390eeff374e782e979bae0af189a51754a0bc4cff0e861cdf3;Path=/;HttpOnly;SameSite=None;Secure;Domain=sharepointonline-scus.azconn-scus-001.p.azurewebsites.net",
      "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
      "x-ms-request-id": "f77df2a0-30c5-4000-719f-851232ca50c8",
      "X-Content-Type-Options": "nosniff",
      "X-Frame-Options": "DENY",
      "Timing-Allow-Origin": "*",
      "x-ms-apihub-cached-response": "false",
      "x-ms-apihub-obo": "false",
      "Date": "Tue, 28 Nov 2023 11:31:17 GMT",
      "Content-Length": "383",
      "Content-Type": "application/json",
      "Expires": "-1"
    },
    "body": {
      "status": 404,
      "message": "Cannot find resource for the request test.\r\nclientRequestId: 5ff1e5-26f1-4224-99e7-cbe1c1837b1a\r\nserviceRequestId: f77df2a0-30c5-4000-719f-851232ca50c8",
      "source": "https://sharepoint.com/sites//_api/test?excpetion=true",
      "errors": [
        "-1",
        "Microsoft.SharePoint.Client.ResourceNotFoundException"
      ]
    }
  },
  "startTime": "2023-11-28T11:31:18.0648326Z",
  "endTime": "2023-11-28T11:31:18.4852347Z",
  "trackingId": "2d78395c-461e-4768-8035-5649039455a1",
  "clientTrackingId": "08585004358075242826545384959CU152",
  "clientKeywords": [
    "testFlow"
  ],
  "code": "NotFound",
  "status": "Failed"
}
```

Type 3
```
{
  "name": "HTTP",
  "inputs": {
    "host": {
      "apiId": "subscriptions/72c9792d-e7e6-4eb5-b764-fc599b9b3005/providers/Microsoft.Web/locations/westus/runtimes/unitedstates-002/apis/sharepointonline",
      "connectionReferenceName": "shared_sharepointonline",
      "operationId": "HttpRequest"
    },
    "parameters": {
      "dataset": "https://sharepoint.com/sites/",
      "parameters/method": "GET",
      "parameters/uri": "_api/test?excpetion=true"
    }
  },
  "outputs": {
    "statusCode": 404,
    "headers": {
      "Cache-Control": "no-store, no-cache",
      "Pragma": "no-cache",
      "Set-Cookie": "ARRAffinity=40237ffdc57de1390eeff374e782e979bae0af189a51754a0bc4cff0e861cdf3;Path=/;HttpOnly;Secure;Domain=sharepointonline-scus.azconn-scus-001.p.azurewebsites.net,ARRAffinitySameSite=40237ffdc57de1390eeff374e782e979bae0af189a51754a0bc4cff0e861cdf3;Path=/;HttpOnly;SameSite=None;Secure;Domain=sharepointonline-scus.azconn-scus-001.p.azurewebsites.net",
      "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
      "x-ms-request-id": "f77df2a0-30c5-4000-719f-851232ca50c8",
      "X-Content-Type-Options": "nosniff",
      "X-Frame-Options": "DENY",
      "Timing-Allow-Origin": "*",
      "x-ms-apihub-cached-response": "false",
      "x-ms-apihub-obo": "false",
      "Date": "Tue, 28 Nov 2023 11:31:17 GMT",
      "Content-Length": "383",
      "Content-Type": "application/json",
      "Expires": "-1"
    },
    "body": {
      "status": 404,
      "message": "Cannot find resource for the request test.\r\nclientRequestId: 5ff1e5-26f1-4224-99e7-cbe1c1837b1a\r\nserviceRequestId: f77df2a0-30c5-4000-719f-851232ca50c8",
      "source": "https://sharepoint.com/sites//_api/test?excpetion=true",
      "errors": [
        "-1",
        "Microsoft.SharePoint.Client.ResourceNotFoundException"
      ]
    }
  },
  "startTime": "2023-11-28T11:31:18.0648326Z",
  "endTime": "2023-11-28T11:31:18.4852347Z",
  "trackingId": "2d78395c-461e-4768-8035-5649039455a1",
  "clientTrackingId": "08585004358075242826545384959CU152",
  "clientKeywords": [
    "testFlow"
  ],
  "code": "NotFound",
  "status": "Failed"
}
```

- Type 1: error.message (e.g compose, Outlook Send Email)
- Type 2: outputs.body.message (e.g SharePoint HTTP, Get Items)
- Type 3: outputs.body.error.innerError.message (e.g Create Item)

So you will need to target the expression to the right action type

```
actions('nameOfActionString')?['error']?['message']
actions('nameOfActionString')?['outputs']?['body']?['message']
actions('nameOfActionString')?['outputs']?['body']?['error']?['innerError']?['message']

```

But as I said recommend result() as catching exceptions from `Scopes `is easier. It returns the exact same objects but in an array instead. But with two caveats, this only works on the Scope/Condition level (so a condition within a scope wont work), Loops are a little different.


So Type 1 would now look like this (wrapped in an array):
```
[
  {
    "name": "Compose",
    "startTime": "2023-11-28T11:35:25.4082007Z",
    "endTime": "2023-11-28T11:35:25.4090563Z",
    "trackingId": "3ed89fb2-63f7-45c1-9954-60c2bc9b17de",
    "clientTrackingId": "08585004358075242826545384959CU152",
    "clientKeywords": [
      "testFlow"
    ],
    "code": "BadRequest",
    "status": "Failed",
    "error": {
      "code": "InvalidTemplate",
      "message": "Unable to process template language expressions in action 'Compose' inputs at line '0' and column '0': 'The template language function 'int' was invoked with a parameter that is not valid. The value cannot be converted to the target type.'."
    }
  }
]
```

Because loops add anther dimension there is an additional 'outputs' array, which is for each iteration of the loop, so Type 1 now looks like:

```
[
  {
    "name": "Compose",
    "outputs": [
      {
        "name": "Compose",
        "startTime": "2023-11-28T11:58:57.5417291Z",
        "endTime": "2023-11-28T11:58:57.5431434Z",
        "trackingId": "17760050-59f1-46ae-b9c7-cabfb4edcb46",
        "clientTrackingId": "08585004358075242826545384959CU152",
        "clientKeywords": [
          "testFlow"
        ],
        "code": "BadRequest",
        "status": "Failed",
        "error": {
          "code": "InvalidTemplate",
          "message": "Unable to process template language expressions in action 'Compose' inputs at line '0' and column '0': 'The template language function 'int' was invoked with a parameter that is not valid. The value cannot be converted to the target type.'."
        }
      },
      {
        "name": "Compose",
        "startTime": "2023-11-28T11:58:57.752149Z",
        "endTime": "2023-11-28T11:58:57.7531706Z",
        "trackingId": "e870f823-3e34-4d4a-9df8-f97ad69f5b2d",
        "clientTrackingId": "08585004358075242826545384959CU152",
        "clientKeywords": [
          "testFlow"
        ],
        "code": "BadRequest",
        "status": "Failed",
        "error": {
          "code": "InvalidTemplate",
          "message": "Unable to process template language expressions in action 'Compose' inputs at line '0' and column '0': 'The template language function 'int' was invoked with a parameter that is not valid. The value cannot be converted to the target type.'."
        }
      }
    ],
    "startTime": "2023-11-28T11:58:58.1446105Z",
    "endTime": "2023-11-28T11:58:58.1452165Z",
    "trackingId": "4f95aaa2-a00f-4748-bce0-3b6ba566b138",
    "clientTrackingId": "08585004358075242826545384959CU152",
    "clientKeywords": [
      "testFlow"
    ],
    "code": "NotSpecified",
    "status": "Failed",
    "repetitionCount": 2
  }
]
```

You have 2 options with result(), either filter and return all failed as a table (HTML/Markdown table to display it nicely), or you can just grab the first from the filter if you flow is likely to only fail in one place. 

So no matter what this is going to be a little challenging, but heres my recommendations:

Use a top level scope and catch the first error from the result. I do this because I can use one expression (will show you below) to catch all, and because I build my flows with minimum nesting. If I have a loop inside my scoop I handle the excpetion inside (as dont want 1000's of messages). If there is a switch/condition that needs it I will add another exception catch just for that container.

So whats the clever expression, well its 

```
coalesce(
    sort(result('Main'),'status')[0]?['error']?['message']
,
     sort(result('Main'),'status')[0]?['outputs']?['body']?['message']
,
     sort(result('Main'),'status')[0]?['outputs']?['body']?['error']?['innerError']?['message']
)
```

It uses the `coalesce` expression which uses the first non null value (so ignores the unused results) and I sort the result array, ensuring the first item is the 'failed' action.


![exception handling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c0cnpo0tr86ryr1mie6i.png)

---

There are 3 different types/categories for exception handling

- Process
- Business
- System

---

## Process

Process exceptions are exception handling used as logic. So it's almost used as a condition, with the expectation the process will continue i.e. its not a real exception.

![process flow except](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/faxncb1xvfk04tx8dw6r.png)

These are generally called try, catch, continue, and although very useful I'm personally not a fan. I would rather try standard logic to check and then not try, rather then try and deal with the error.


## Business

Business exceptions are exceptions generated by input data, and are generally in loops. What's key about them is we don't want to end the process, we just want to flag that item as exception, and continue with the other items.

![business flow except](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdflyhw2sbn3w7mjv4l3.png)

These generally only have one action, and may not need a Scope to contain them. Though to ensure the process continues the action after the loop would have to have run after Success and Failed enabled. A good example would be a to add a row to a SharePoint list, so that those items can be manually processed/corrected.

## System

System exceptions are when something unexpected in the flow happens. Either a connector is failing due to the api or a key file can't be found. In these cases the process should carry out any clean up actions (e.g delete temp files) and escalate. This could be like a business exception with entry in a SharePoint list, but generally as its more serious I would recommend a teams message or email.

A good tip for system exceptions are:

**Wrap the entire process in a Exception**
Having a 'Main' `Scope` that contains every action means that a system exception after can catch any error in process. Although we mostly get failed emails from Power Automate, this is no good if running under a Service Account.

![system flow except](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3rfyv0yrbk98dnq4dtcg.png)

**Terminate after handling**
Use the `Terminate` action set to fail, this allows you to still identify failed runs in the logs.

**Run link**
The below expression creates a link to the failed run, so if added to the message/email, the user can click the link and  go straight to the faile run. An added bonus is these links work longer then the normal retention (last 100 runs,  I've got a link over a 1000 runs previous) and it can be accessed by environment admins without sharing the flow.

`concat('https://make.powerautomate.com/manage/environments/', workflow()?['tags']?['environmentName'], '/flows/', workflow()?['name'], '/runs/', workflow()?['run']['name'])
`

![flow failed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvbalmoqe3ljhu5pdbjo.png)