Reporting on Power Automate flows and some notes on the current state of the Power platform APIs

In this article, we will examine how to generate a report of Power Automate flows by leveraging the currently available tools and APIs. The same approach can be used to cover PowerApps.

Using the PowerApps admin module

One can certainly obtain some useful data out of the PowerApps admin module, for example by leveraging the code below (or similar approach used by Tony). Getting the list of flows however does not expose all the important details, so if you want to get the full picture, you need to make additional calls. Those include calls to the Get-AdminFlow cmdlet itself, Get-FlowRun to get details on flow executions, or even some of the Graph SDK cmdlets in order to get details on the user who created the flow. Another frustration is the fact that the module does not include a way to list the flow templates, so you’re stuck with displaying a GUID value.

Get-AdminFlow |
    ForEach-Object -Begin {
        $count = 1; $PercentComplete = -1;
    } -Process {
        $ActivityMessage = "Retrieving data for flow $($_.FlowName). Please wait..."
        $StatusMessage = ("Processing flow #{0}: {1}" -f $count, $_.DisplayName)
        Write-Progress -Activity $ActivityMessage -Status $StatusMessage -PercentComplete $PercentComplete -Verbose
        
        $FlowDetails = Get-AdminFlow -FlowName $_.FlowName
        $_ | Add-Member -NotePropertyName Triggers -NotePropertyValue (($FlowDetails.Internal.properties.definitionSummary.triggers.GetEnumerator().swaggerOperationId | sort -Unique) -join ",")
        $_ | Add-Member -NotePropertyName Actions -NotePropertyValue (($FlowDetails.Internal.properties.definitionSummary.actions.GetEnumerator().swaggerOperationId | sort -Unique) -join ",")
        $_ | Add-Member -NotePropertyName Connections -NotePropertyValue $FlowDetails.Internal.properties.connectionReferences
        $_ | Add-Member -NotePropertyName referencedResources -NotePropertyValue $FlowDetails.Internal.properties.referencedResources
        $_ | Add-Member -NotePropertyName Creator -NotePropertyValue $FlowDetails.Internal.properties.creator.userId
        $_ | Add-Member -NotePropertyName Template -NotePropertyValue $FlowDetails.Internal.properties.templateName
        $_ | Add-Member -NotePropertyName State -NotePropertyValue $FlowDetails.Internal.properties.state -PassThru

        if ($includeRunData) {
            $FlowRuns = Get-FlowRun -FlowName $_.FlowName
        }
        $count++
    } | Export-Csv -Path "$PSScriptRoot\$((Get-Date).ToString('yyyy-MM-dd_HH-mm-ss'))_FlowReport.csv" -NoTypeInformation -Encoding UTF8 -UseCulture

In other words, this approach leaves a lot to be desired. Yeah, well, I’m gonna go build my own solution, with more complicated and unnecessary code…

Power platform APIs – authentication

Unfortunately, going that route is quite annoying as well. The first major annoyance is the sad and sorry state of the Power platform APIs. I might be bashing Microsoft’s implementation of the Graph SDK for PowerShell in every other post, but the Graph itself has certainly made a lot of scenarios possible, and the code you need is often times quite easy and predictable. Things might not look as pretty on the backend, but we don’t care about it – single endpoint, uniform requests FTW. If only the Power platform folks followed such approach.

Anyway, to get started, we will of course need to authenticate, i.e. obtain tokens. Here lies the first hurdle, documentation is almost non-existent, mostly incomplete and in some cases outright misleading. In fact, you’re better off staying away from the documentation and just leveraging the queries used by the PowerShell cmdlets (use -Verbose to show the URL or capture the traffic via Fiddler) and the browser UI. The first thing you will notice is that several different endpoints are being queried, meaning you will need multiple access tokens. In fact, if you have used the PowerApps admin module, you’ve probably noticed the increased number of login prompts.

To save you some time, here’s the set of endpoints you will need, and the corresponding resources and scopes:

  • The PowerApps Service API (with GUID 475226c6-020e-4fb2-8a90-7a972cbfc1d4 and URI https://api.powerapps.com/) – needed to list all environments. If leveraging your own app, add the User scope (with GUID 0eb56b90-a7b5-43b5-9402-8137a8083e90) and use https://service.powerapps.com/.default scope in your token request. No admin consent is required.
  • The Power Automate API, also known previously as Microsoft Flow Service (with GUID 7df0a125-d3be-4c96-aa54-591f83ff541c and URI https://service.flow.microsoft.com/) – needed for most Flow related operations. If leveraging your own app, add the User and Flows.Read.All scopes at minimum to meet our reporting needs, and any other scopes as needed. When requesting a token, use the https://service.flow.microsoft.com/.default scope.
  • DO NOT add the Power Platform API (with GUID 8578e004-a5c6-46e7-913e-12f58912df43 and URI https://api.gov.powerplatform.microsoft.us) as advised in this article. Not only this API does not expose any of the permissions we need, it seems to only be available for Government tenants, so it’s likely of no use to you.
  • The Graph API itself – for user/group resolution.

NOTE: If you are not able to find the API after hitting the Add permissions button, select the APIs my organization uses tab and search for it therein (by GUID). If that does not work either, make sure the corresponding service is enabled for your tenant (you have some licenses for it), and if needed, use the New-MgServicePrincipal cmdlet to provision the corresponding service principal object.

NOTE: All the Power platform APIs we’ve detailed above only accept delegate permissions, thus any of the requests needs to be run in the context of a user with sufficient permissions and suitable license assigned, where applicable. No support for application permissions currently!

With the above in mind, it doesn’t look like we’re on a good start, as we have to take care of multiple prerequisites and after meeting them, obtain a handful of access tokens in order to ensure our queries will work. There’s one shortcut we can use though, namely leveraging the built-in Microsoft Azure PowerShell service principal (with clientID of 1950a258-227b-4e31-a9cf-717495945fc2), which for whatever reason is authorized to obtain tokens with user impersonation permissions for all the resources we need. For valid reasons, I’m sure, and I’m sure the security folks love this approach. In fact, you can even obtain an Exchange Online token, albeit with invalid, deliberately modified scopes. Elegant…

Exchange Online resource token example

But I digress. So, because I’m lazy, we will use this “hack” instead of going over the process of registering our own application and granting all the required permissions therein. It saves me from adding like two variables! If you feel adventurous, feel free to update the code to leverage the ROPC flow instead and get rid of the MSAL dependencies. Or whichever public client flow works best for you.

As mentioned above, only delegate permissions are supported for the APIs we need, which unfortunately means we have to leverage MSAL methods in order to obtain the access token(s). In turn, this means you need to have the MSAL binaries available. As every Azure/M365/Graph-related PowerShell module leverages MSAL nowadays (even the AzureAD module got MSAL support in its latest version!), you likely already have a version of the binaries that should work. Just in case, here’s how to install them, if needed:

Register-PackageSource -Provider NuGet -Name nugetRepository -Location https://www.nuget.org/api/v2
Install-Package -Name Microsoft.IdentityModel.Abstractions -Source nugetRepository -MaximumVersion 6.22.0
Install-Package -Name Microsoft.Identity.Client -Source nugetRepository -SkipDependencies

After the binaries are loaded, we can use the standard MSAL methods to obtain an access token. Since we need at least three of these, we might as well automate the process a bit, which is what the Get-AccessTokens function is supposed to do. And since I cannot be bothered to press a button three times, some logic is put in said function to try and leverage the token cache after the initial token has been obtained. I’m sure there will be tons of corner scenarios where this will not work, so as a fallback the AcquireTokenInteractive method can be used instead.

Power Platform APIs – obtain the list of flows

Assuming all went well with obtaining the access tokens, they will be stored in a global variable called $hashTokens. We will leverage said variable to pass the correct token for each of the HTTPS calls we make, as part of the Invoke-GraphApiRequest wrapper function. Well, technically most of the calls aren’t against the Graph, but you get the idea. The wrapper function has some added error handling, as of course each of the endpoints we query has its own understanding as to which error code and/or message to use. Seriously Microsoft, can we get some uniformity?

And with that, we’re ready to proceed. In order to obtain the full set of flows within your organization, we need to first gather a list of all Power platform environments, then iterate over each of them and query for any flows. Herein lies the first pitfall or the APIs, namely the fact that the “admin” API endpoint (/scopes/admin) does NOT return all the flows. This is quite easy to illustrate if you take a look at any Demo tenant. The screenshot below compares the set of flows available for the current user (left) vs the set of flows obtained via the admin endpoint (right). While you’d expect the set of flows obtained via the admin endpoint to be larger, as it should include flows created/owned by all users, we’re seeing the opposite:

List of Power Automate Flows as obtained via the UI tools

Both sets are from the same environment, and in fact the “user” set is not showing one additional flow, “shared” with the user. The difference in results stems from the fact that the “standard” (“maker”) endpoint includes “solution” flows, via the include=includeSolutionCloudFlows query parameter, whereas the same parameter does not seem to be accepted by the “admin” endpoint. Unfortunately, that’s not the only such example, as we will see throughout our examination of the API in this article. And while a suitable alternative might exist, the lack of proper (or any!) documentation makes it near impossible to implement it. In addition, the API does not seem to support $metadata either…

An alternative approach would be to use the “regular” endpoint and ensure that the user we run the queries with has access to all environments and all flows. Such approach doesn’t seem too plausible to implement, unless you’re willing to do a lot of manual work. So, have in mind that the report generated via the admin endpoints will likely show an incomplete picture. Then again, so will the built-in analytics and the Power Platform admin center’s environment views.

Power Platform APIs – enriching the output

Moving on, once we obtain the set of flows, we need to iterate over each flow to gather additional details. The level of detail returned by the initial LIST operations is sufficient for a basic report, but if you want to present additional details on each flow, such as the type of connectors it uses or when it was last run successfully, additional GET requests are necessary. We start by running a request against the /flows/{flowId} endpoint, the output of which enables us to enrich the original data.

To obtain information about the set of connections used by the flow, we then issue a request against the /flows/{flowId}/connections endpoint. The output is then processed via the processConnections helper function in order to provide a concatenated list of connections, in the format [connection]account, for example [sharepointonline]vasil@michev.info. Keep in mind that the connection names are presented as reported by the API, with only stripping the “shared_” prefix. Thus, some oddities can be observed, such as [office365] being used for a connection to Exchange Online mailbox. The output will also indicate whether a given connection is in a good state, by adding an asterisk (*) suffix to any connection with error status.

Using the same output format, we then process the flow’s triggers, actions and resource references via the processActions helper function. No additional API calls are necessary here, as the definitionSummary property gives us all the interesting data. Well, the definition property would be my first choice here, but unfortunately it does not seem like the “admin” endpoint exposes that. For the same reason, we cannot include details about the Flow’s used plan. We do however have access to the flow runs data via the /flows/{flowid}/runs endpoint, which luckily supports $filter and $top operators, so we can easily include data on the last successful and failed runs of the flow.

Next, we include data on the creator of the flow. As only the GUID is returned, herein we leverage a query against the Graph API to resolve it to a human readable value, such as the UserPrincipalName. We then proceed to check the sharing status of the flow and if it is shared, obtain the list of owners via a call to the /flows/{flowid}/owners endpoint. Some sloppy checks are used to determine whether the flow is shared with an user or group, in which case the corresponding human readable identifier is returned. In scenarios where the flow is shared with a SharePoint list or library instead, or used as part of Dynamics solutions, a resource identifier is presented. In the case of a SPO list, you should be able to directly query it via the Graph API, provided your app has the necessary permissions (which the Azure PowerShell app we leverage does not).

Unfortunately, we are not able to cover scenarios where the flow has Run Only Users configured, as the admin API does not expose an /flows/{flowid}/users endpoint. Generally speaking, we should have sufficient data to determine whether the flow is configured with Run Only Users, but as we cannot enumerate them, the corresponding checks have been commented in the code.

The last bit of data we include for each flow is its template, if such was used as a basis for the creation process. Here again we have to make a call to a different API, as the admin one does not feature the /galleries/public/templates/ endpoint. In this case, we’re querying the regular “maker” flow API, and depending on the permissions of the user you’ve authenticated with, we might end up unable to resolve the template name, in which case its ID will be featured instead.

The output of the script is a CSV file, written in the current directory. Here’s a sample of the CSV file, with some columns hidden in order to fit more data on the screen:

Sample output of the Flow report script

You can get the script from my GitHub repo. There are no parameters you need to know about, just run it as is. Do make sure to have the MSAL binaries on the system, and if needed use the cmdlets above to install them. Of, if you prefer, replace the entire auth block to better suit your needs, remembering that only delegate permissions are currently supported. Same goes for replacing the Azure PowerShell app with your own app registration. Lastly, you might want to add some basic throttling handling if you plan to run this against an organization with numerous environments and hundreds of flows. The best place for this will probably be within the Invoke-GraphApiRequest function.

Summary

In summary, we explored the available methods of generating a report of Power Automate flows within your organization, by either leveraging the PowerApps admin module, on the underlying APIs directly. Both approaches have a number of limitations currently, with the lack of support for application permissions and the total lack of documentation posing the biggest challenges. While we can certainly put together an acceptable solution, some interesting properties are only exposed via the “maker” cmdlets/endpoints, and not the admin one. Sadly, that seems to apply to even some of the flow objects themselves, i.e. the query parameters used to obtain a list of “solution” flows. Hopefully, things will improve in the future, as Microsoft is slowly realizing the need for more admin controls for the entire Power platform.

1 thought on “Reporting on Power Automate flows and some notes on the current state of the Power platform APIs

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.