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.
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.
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.
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.
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.
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.
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'])
Top comments (2)
Hi @wyattdave,
What is your recommendation for Exception handling when exception happens inside Do-Until loop (this is the only exception in Power Automate where Exceptions are not bubbling outside of the loop).
I think of these as generally business exceptions as the item being processed is generating the exception. In these cases I recommend handling each individually so that the flow can continue with the successful items. I would then log the failed items in a list/array and handle them separately.