Microsoft Flow and PowerApps monitoring strategy

Developer Support

App Dev Manager Om Chauhan demonstrates an approach for monitoring Flows and PowerApps to enable alerts or taking custom actions.


What is Microsoft Flow?

Flow is an online workflow service that enables you to work smarter and more efficiently by automating workflows across the most common apps and services. It’s a recommended solution for workflows in the modern Office 365 ecosystem. Going forward Microsoft does not plan to make any updates to their legacy workflow products like SharePoint Designer and SharePoint 2013 workflows. Using Microsoft Flow, you can connect to more than 100 services and manage data in either the cloud or on-premises sources such as SharePoint and SQL Server. The list of applications and services that you can use with Microsoft Flow grows constantly.

What is Microsoft PowerApps?

PowerApps allows the user to build business apps that run in a browser, or on a phone or tablet via a mobile app. It’s is an interface design tool that creates form and screens for users to perform CRUD operations. Microsoft has positioned PowerApps as their recommended replacement for InfoPath. Like Flow, PowerApps also provides 100s of connectors to integrate with apps and services in the cloud and on-premise.

Why the need for a monitoring solution?

The Admin centers for Microsoft Flow (https://admin.flow.microsoft.com) and PowerApps (https://admin.powerapps.com) allow administrators to manage the environments, the resources, security, Data Policies, licenses, quotas etc. What the Admin Center does not provide is an automated way of monitoring the Flows, PowerApps, Connectors etc. created within the different environments. From a security and governance point of view, organizations might want to build their own solution that could monitor the Flows and PowerApps being created across the organizations and trigger appropriate alerts and remediation actions as necessary.

Below are few of the many example scenarios that an organization may want to monitor:

  1. Daily summary of new Flows and PowerApps being created in different environments.
  2. Unusually high number of Flows or Power Apps being created on a specific day.
  3. Flow or PowerApps using connectors that are being restricted to use within the organization.
  4. Newly available connectors from Microsoft or custom connectors build by the organization users.

Building your own custom monitoring solutions

You could build your own custom monitoring solution using the below two approaches:

  1. Using Power platform PowerShell Cmdlets (Preview) Microsoft has exposed the Power Platform API’s through the PowerShell cmdlets that will allow the App Creators and the Administrators to automate many of the monitoring, administration and management tasks using PowerShell. Here is the article with the preview launch announcement of the PowerApps cmdlets https://docs.microsoft.com/en-us/powerapps/administrator/powerapps-powershell
  2. Using Power platform management connectors (Preview) Microsoft has also exposed Power Platform API’s through the standard connectors that easily allows to interact with PowerApps and Flow resources within PowerApps and Flow itself.Here is the link to blog article about the announcement of these new connectors https://powerapps.microsoft.com/en-us/blog/new-connectors-for-powerapps-and-flow-resources/The Admin Connectors (Power Platform for Admins, Microsoft Flow for Admins and PowerApps for Admins) will give the user access to resources tenant-wide, whereas the Maker Connector (PowerApps for App Makers) only gives access to the resources if the user has some ownership of the resource (e.g., Owner, Editor, Shared with, etc.).

Monitoring solution using Power Platform PowerShell cmdlets

While still in preview these cmdlets allow to programmatically perform almost the same level of administration operations that are currently available through the Flow/PowerApps admin centers.

Below is one sample PowerShell script that uses the PowerApps cmdlets and generates two files in the current folder named FlowPermissions.csv and AppPermissions.csv. The files list all the flows and PowerApps details like Flow/PowerApp name, Creation DateTime, Modification DateTime, Owner Names, Connectors and so on.

Import-Module (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "Microsoft.PowerApps.Administration.PowerShell.psm1") -Force

$AppRoleAssignmentsFilePath = ".\AppPermissions.csv"
$FlowRoleAssignmentsFilePath = ".\FlowPermissions.csv"

# Add the header to the app roles csv file
$appRoleAssignmentsHeaders = "EnvironmentName," `
        + "AppName," `
        + "CreatedTime," `
        + "LastModifiedTime," `
        + "AppDisplayName," `
        + "AppOwnerObjectId," `
        + "AppOwnerDisplayName," `
        + "AppOwnerDisplayEmail," `
        + "AppOwnerUserPrincipalName," `
        + "AppConnections," `
        + "RoleType," `
        + "RolePrincipalType," `
        + "RolePrincipalObjectId," `
        + "RolePrincipalDisplayName," `
        + "RolePrincipalEmail," `
        + "RoleUserPrincipalName,";
Add-Content -Path $AppRoleAssignmentsFilePath -Value $appRoleAssignmentsHeaders

# Add the header to the app roles csv file
$flowRoleAssignmentsHeaders = "EnvironmentName," `
        + "FlowName," `
        + "CreatedTime," `
        + "LastModifiedTime," `
        + "FlowDisplayName," `
        + "FlowOwnerObjectId," `
        + "FlowOwnerDisplayName," `
        + "FlowOwnerDisplayEmail," `
        + "FlowOwnerUserPrincipalName," `
        + "FlowConnectionOwner," `
        + "FlowConnections," `
        + "FlowConnectionPlusOwner," `
        + "RoleType," `
        + "RolePrincipalType," `
        + "RolePrincipalObjectId," `
        + "RolePrincipalDisplayName," `
        + "RolePrincipalEmail," `
        + "RoleUserPrincipalName,";
Add-Content -Path $FlowRoleAssignmentsFilePath -Value $flowRoleAssignmentsHeaders


Add-PowerAppsAccount

#populate the app files
$apps = Get-AdminPowerApp

foreach($app in $apps)
{
    #Get the details around who created the app
    $AppEnvironmentName = $app.EnvironmentName
    $Name = $app.AppName
    $DisplayName = $app.displayName -replace '[,]'
    $OwnerObjectId = $app.owner.id
    $OwnerDisplayName = $app.owner.displayName -replace '[,]'
    $OwnerDisplayEmail = $app.owner.email
    $CreatedTime = $app.CreatedTime
    $LastModifiedTime = $app.LastModifiedTime

    $userOrGroupObject = Get-UsersOrGroupsFromGraph -ObjectId $OwnerObjectId
    $OwnerUserPrincipalName = $userOrGroupObject.UserPrincipalName

    #Get the list of connections for the app
    $connectionList = ""
    foreach($conRef in $app.Internal.properties.connectionReferences)
    {
        foreach($connection in $conRef)
        {
            foreach ($connId in ($connection | Get-Member -MemberType NoteProperty).Name) 
            {
                $connDetails = $($connection.$connId)

                $connDisplayName = $connDetails.displayName -replace '[,]'
                $connIconUri = $connDetails.iconUri
                $isOnPremiseConnection = $connDetails.isOnPremiseConnection
                $connId = $connDetails.id


                $connectionList += $connDisplayName + "; "
            }
        }        
    }

   
    #Get all of the details for each user the app is shared with
    $principalList = ""
    foreach($appRole in ($app | Get-AdminPowerAppRoleAssignment))
    {
        $RoleEnvironmentName = $appRole.EnvironmentName
        $RoleType = $appRole.RoleType
        $RolePrincipalType = $appRole.PrincipalType
        $RolePrincipalObjectId = $appRole.PrincipalObjectId
        $RolePrincipalDisplayName = $appRole.PrincipalDisplayName -replace '[,]'
        $RolePrincipalEmail = $appRole.PrincipalEmail
        $CreatedTime = $app.CreatedTime
        $LastModifiedTime = $app.LastModifiedTime

        If($appRole.PrincipalType -eq "Tenant")
        {
            $RolePrincipalDisplayName = "Tenant"
            $RoleUserPrincipalName = ""
        }
        If($appRole.PrincipalType -eq "User")
        {
            $userOrGroupObject = Get-UsersOrGroupsFromGraph -ObjectId $appRole.PrincipalObjectId 
            $RoleUserPrincipalName = $userOrGroupObject.UserPrincipalName  
            
        }

        # Write this permission record 
        $row = $AppEnvironmentName + "," `
                + $Name + "," `
                + $CreatedTime + "," `
                + $LastModifiedTime + "," `
                + $DisplayName + "," `
                + $OwnerObjectId + "," `
                + $OwnerDisplayName + "," `
                + $OwnerDisplayEmail + "," `
                + $OwnerUserPrincipalName + "," `
                + $connectionList + "," `
                + $RoleType + "," `
                + $RolePrincipalType + "," `
                + $RolePrincipalObjectId + "," `
                + $RolePrincipalDisplayName + "," `
                + $RolePrincipalEmail + "," `
                + $RoleUserPrincipalName;
        Add-Content -Path $AppRoleAssignmentsFilePath -Value $row 
    }
}
        

#populate the flow files
$flows = Get-AdminFlow

foreach($flow in $flows)
{
    #Get the details around who created the flow
    $FlowEnvironmentName = $flow.EnvironmentName
    $Name = $flow.FlowName
    $DisplayName = $flow.displayName -replace '[,]'
    $OwnerObjectId = $flow.createdBy.objectid
    $OwnerDisplayName = $flow.createdBy.displayName -replace '[,]'
    $OwnerDisplayEmail = $flow.createdBy.email
    $CreatedTime = $flow.CreatedTime
    $LastModifiedTime = $flow.LastModifiedTime

    $userOrGroupObject = Get-UsersOrGroupsFromGraph -ObjectId $OwnerObjectId
    $OwnerUserPrincipalName = $userOrGroupObject.UserPrincipalName

    $flowDetails = $flow | Get-AdminFlow

    $connectionList = ""
    $connectorList = ""
    $connectionPlusConnectorList = ""
    foreach($conRef in $flowDetails.Internal.properties.connectionReferences)
    {
        foreach($connection in $conRef)
        {
            foreach ($connId in ($connection | Get-Member -MemberType NoteProperty).Name) 
            {
                $connDetails = $($connection.$connId)

                $connDisplayName = $connDetails.displayName -replace '[,]'
                $connIconUri = $connDetails.iconUri
                $isOnPremiseConnection = $connDetails.isOnPremiseConnection
                $connId = $connDetails.id
                $connName = $connDetails.connectionName

                $connectionObject = Get-AdminPowerAppConnection $connName
                $connectorName = $connectionObject.ConnectorName
                $environmentName = $connectionObject.EnvironmentName
                $connectionOwner = $connectionObject.CreatedBy.UserPrincipalName

                $connectionList += $connectionOwner + "; "
                $connectorList += $connDisplayName + "; "
                $connectionPlusConnectorList += "{" + $connectionOwner + ":" + $connDisplayName + "}; "
            }
        }        
    }
    
    $principalList = ""
    foreach($flowRole in ($flow | Get-AdminFlowOwnerRole))
    {        
        $RoleEnvironmentName = $flowRole.EnvironmentName
        $RoleType = $flowRole.RoleType
        $RolePrincipalType = $flowRole.PrincipalType
        $RolePrincipalObjectId = $flowRole.PrincipalObjectId
        $RolePrincipalDisplayName = $flowRole.PrincipalDisplayName -replace '[,]'
        $RolePrincipalEmail = $flowRole.PrincipalEmail

        If($flowRole.PrincipalType -eq "Tenant")
        {
            $RolePrincipalDisplayName = "Tenant"
            $RoleUserPrincipalName = ""
        }
        If($flowRole.PrincipalType -eq "User")
        {
            $userOrGroupObject = Get-UsersOrGroupsFromGraph -ObjectId $flowRole.PrincipalObjectId 
            $RoleUserPrincipalName = $userOrGroupObject.UserPrincipalName  
            
        }

        # Write this permission record 
        $row = $RoleEnvironmentName + "," `
            + $Name + "," `
            + $CreatedTime + "," `
            + $LastModifiedTime + "," `
            + $DisplayName + "," `
            + $OwnerObjectId + "," `
            + $OwnerDisplayName + "," `
            + $OwnerDisplayEmail + "," `
            + $OwnerUserPrincipalName + "," `
            + $connectionList + "," `
            + $connectorList + "," `
            + $connectionPlusConnectorList + "," `
            + $RoleType + "," `
            + $RolePrincipalType + "," `
            + $RolePrincipalObjectId + "," `
            + $RolePrincipalDisplayName + "," `
            + $RolePrincipalEmail + "," `
            + $RoleUserPrincipalName;
        Add-Content -Path $FlowRoleAssignmentsFilePath -Value $row 
    }
}
 

The script can be made to run on a regular schedule and can be easily extended to send the two CSV files as an attachment in an email to the configured recipients.

Monitoring solution using Power platform management connectors

Microsoft recently released a Flow Template named “Get List of new PowerApps, Flow and Connectors” that sends an email with a report of the newly created PowerApps, Flows and Connectors that have been introduced in to your tenant within a configurable window. Please note this Flow would require PowerApps/Flow administrator permissions in order to use the admin connectors within the Flow.

Here is the documentation link to this Flow template https://us.flow.microsoft.com/en-us/galleries/public/templates/0b2ffb0174724ad6b4681728c0f53062/get-list-of-new-powerapps-flows-and-connectors/

The Flow template make use of the below 5 connectors:

clip_image002

The Flow Template uses a Recurrence trigger that can be configured to run the flow on a regular schedule. It uses the Power platform for Admins connectors Get Environments action to get all the environments within the tenant. It then loops through each of the environments and uses

  • Flow Management connectors List Flows as Admin action
  • Flow Management connectors List Connectors action
  • PowerApps for Admins connectors Get Apps as Admin action

to generate a list of Flows, Connectors and PowerApps that were created during that window of time. It then formats the three list and uses Office 365 Outlook connectors send an email action to send the email to the configured recipients.

paflow1

Below is the sample of the email that is generated and sent by this Flow.

clip_image006

You can also use Microsoft Flow to access the Office 365 Security and Compliance Center Audit Logs for Flow and PowerApps and monitor specific operations and send alert email notifications. Please do note that there is a delay of 30+ minutes before the audit events show up in the Office 365 Security and Compliance Center Audit Logs.

Here is a blog article by Kent Weare, PM at Microsoft about the same https://flow.microsoft.com/en-us/blog/accessing-office-365-security-compliance-center-logs-from-microsoft-flow/.

Conclusion

Both Flow and PowerApps are great tools that can be in the hands of business and power users to accelerate the building of automated workflows and business apps across on-premise and the cloud services. It’s easy to see how there could be many such Flows and Apps built, deployed and running within your tenant. It’s a best practice for the IT administrators to devise a monitoring strategy in place that could proactively keep a watch on these Flows, Apps and other related resources and alert and perform remediation actions as necessary.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • JB Lewis 0

    There are parts of that script that I feel demonstrate some less-than-ideal PowerShell practices, namely making heavy use of string concatenation techniques, and appending content to manually build a csv file.  PowerShell gives us these tools and lets us work natively with objects before outputting them to a CSV file.  Instead of 
    “`$appRoleAssignmentsHeaders = “EnvironmentName,” `+ “AppName,” `+ “CreatedTime,” `+ “LastModifiedTime,” `+ “AppDisplayName,” `+ “AppOwnerObjectId,” `+ “AppOwnerDisplayName,” `+ “AppOwnerDisplayEmail,” `+ “AppOwnerUserPrincipalName,” `+”AppConnections,” `+ “RoleType,” `+ “RolePrincipalType,” `+ “RolePrincipalObjectId,” `+ “RolePrincipalDisplayName,” `+ “RolePrincipalEmail,” `+ “RoleUserPrincipalName,”Add-Content -Path $AppRoleAssignmentsFilePath -Value $appRoleAssignmentsHeaders# and$appRoleAssignmentsHeaders = “EnvironmentName,” `+ “AppName,” `+ “CreatedTime,” `+ “LastModifiedTime,” `+ “AppDisplayName,” `+ “AppOwnerObjectId,” `+ “AppOwnerDisplayName,” `+ “AppOwnerDisplayEmail,” `+ “AppOwnerUserPrincipalName,” `+ “AppConnections,” `+ “RoleType,” `+ “RolePrincipalType,” `+ “RolePrincipalObjectId,” `+ “RolePrincipalDisplayName,” `+ “RolePrincipalEmail,” `+ “RoleUserPrincipalName,”Add-Content -Path $AppRoleAssignmentsFilePath -Value $appRoleAssignmentsHeaders“`
    The following is better practice, working with objects, rather than text:
    “`$appRoleAssignment = [PSCustomObject]@{ EnvironmentName = $AppEnvironmentName AppName = $Name CreatedTime = $CreatedTime LastModifiedTime = $LastModifiedTime AppDisplayName = $DisplayName AppOwnerObjectId = $OwnerObjectId AppOwnerDisplayName = $OwnerDisplayName AppOwnerDisplayEmail = $OwnerDisplayEmail AppOwnerUserPrincipalName = $OwnerUserPrincipalNameAppConnections = $connectionListRoleType = $RoleTypeRolePrincipalType = $RolePrincipalTypeRolePrincipalObjectId = $RolePrincipalObjectIdRolePrincipalDisplayName = $RolePrincipalDisplayNameRolePrincipalEmail = $RolePrincipalEmailRoleUserPrincipalName = $RoleUserPrincipalName}$appRoleAssignment | Export-CSV -Path $AppRoleAssignmentsFilePath -NoTypeInformation -Append“`

Feedback usabilla icon