Microsoft Flow: Identify Soon-to-Be-Deprecated Office 365 Connectors in Your Environments Using Powershell


For the Time-Crunched:

Jump Ahead to the Script and Usage Instructions

Did you feel that?

It was the collective shudder throughout the Power Platform community as they came to grips with this week's announcement from Microsoft regarding the deprecation of certain actions in the Office 365 Outlook connector.

Like a large tremor out in the deep ocean, the impact of this notice may not strike you immediately. But the inevitable wave has been put in motion and will reach the shore soon enough.

Thankfully, Microsoft does provide us the tools to plan for and mitigate the effects of this change - you may just have to dig a bit to find them - and this post has been created to help guide you through doing just that.

Together, we'll weather the storm and you may even learn some PowerShell along the way!

What's Happening?

I'll leave it to the previously mentioned announcement for the nitty-gritty details, but the watered down version is if any of your Flows or PowerApps use any of the to-be-deprecated Actions or Triggers (eg: 'Send email', 'When a new email arrives', etc.) you will want to update them prior to them riding off into the sunset and leaving you with a broken Flow/PowerApp.

For an individual user, this is easy enough as chances are you have a decent grasp of what your Flows and PowerApps do (and thus, which Actions/Triggers they use). For you, simply go into those Flows and PowerApps and change any to-be-deprecated Actions and Triggers to their associated new actions (which are conveniently laid out in the announcement).

For a Power Platform Administrator? Things are a bit murkier as they'll need to not only account for their creations - but also all of those in the Environments they oversee. Yikes!

Plan

For a change of this magnitude, we'll want to establish a plan of attack to help reduce the number of future headaches.

If you just want to follow one I conveniently made up on the spot, then here you go!
  • Plan: That's what we're doing here!
  • Identify: Identify the affected Flows and PowerApps.
  • Notify: Notify the affected Owners of those Flows and PowerApps.
  • Communicate: Communicate to the Makers in your Organization so that they can begin/continue using the new Actions and Triggers going forward.
  • Monitor: Keep an eye on your Environments to make sure any new Flows and PowerApps don't use these deprecated Actions and Triggers.
For the sake of brevity, I'll cover the first two bullets in this article and perhaps branch it out into the remaining bullets as time permits later on.

Identify

If you're a Power Platform Admin, then you may already know that the PowerApps/Flow Admin Center can provide you a list Environments and the Resources (PowerApps/Flows) within them.

You can even export a list of all of the PowerApps in that Environment! Convenient!

To do so, simply:
  • Go to the Admin Center,
  • Select an Environment,
  • Click on the Resources tab,
  • And click on the 'Download list' button on the right-hand side.

That'll get you a CSV report of all the PowerApps in the Environment.

Open the file, press 'Ctrl + T' to turn the data into a Table (you'll want to resave it as an Excel file at this point) and then look closely and you'll notice the column titled 'Connection References'.

Search for 'Office 365 Outlook' and apply the filter to give you all of the PowerApps that may* need to be updated!


*Why 'may'?

Because you'll notice that the 'Connection References' column only shows you that the Office 365 Outlook connector was used - but unfortunately doesn't tell you which of the many Actions or Triggers were used from it in the app. But it's progress nonetheless!

For now, let's take that small victory and move on to identifying any Flows that use the Office 365 Outlook connector.

The eager amongst you may have already realized that, when you click on 'Flows' for the Environment, you are provided a list of all the Flows - but no way to export them! Inconvenient!

While this could have been disastrous, luckily for us we can turn to an old friend of mine for help in solving this dilemma - PowerShell!

For the code-averse, please don't tune out! The basic structure and logic is very simple for this scenario (and I'm happy to walk you through it).

Not convinced? What if I simply gave you the script in its entirety for you to just run and be past this? You drive a hard bargain, so here you go!

PowerShell Script Overview 

If you're crunched for time, or simply have no interest in learning a very small bit of PowerShell, then you can jump ahead to the usage section.

If you're anything like me though, you'd want to understand what that thing That Stranger on the Internet gave you generally does before actually using it.

Especially one that looks across your entire Tenant using Global/Environment Administrator permissions!

Hopefully your conscience has now given you no other option than to read on, so let's dive in! I promise it'll be relatively painless (and for the most-part very high-level).

To start, go ahead and open up PowerShell ISE, making sure to right-click on it to run it as an Administrator (you'll see why in a moment).


If you haven't already, download the script provided above and navigate to its save location to open it in PowerShell ISE.

You should now be looking at something like this:


Quick Note: Anytime you see 'Write-Host', it's there to simply show a message in the console to let you, the end-user, know what's happening at that given moment in the script. It serves no other functional purpose. Also, lines beginning with Hashtags (#) are comments to let you know what that piece of the script does (a good habit to get into!).

Moving on, the very first set of lines load the Modules we'll be needing in order for this script to function. These 'Modules' hold the 'cmdlets' we run later on. To install modules, you'll need to run PowerShell as an administrator (hence why I asked you to do so earlier). If you run into any errors on this step that'd be a good place to check first to resolve it.

A good analogy for those coming from PowerApps is that a Module acts as a Connector (eg: 'Office 365 Users') and a cmdlet acts similar to an Action/Function in that is the action we get from the the Connector (eg: 'Office365.SendEmail()').

Like PowerApps, in PowerShell some cmdlets are built in and don't need to be installed (eg: 'SubmitForm()', 'Write-Host') - but others need to explicitly loaded to become available for use. In this case we are installing three modules, all provided by Microsoft. The first two will allow us to interact with our Envirionment(s) and the last one will allow us to get information from our Active Directory (you'll see why later on).

The next two sections, as their comments imply, create two Array variables (or 'Collections', in PowerApps parlance) for us to use as references for filtering further down.



PowerShell has many different ways to initiate arrays depending on their intended use. Without getting too technical, the method used on these lines ( by using '@( )' ) creates an array of a fixed size for us (meaning we can't Add/Remove from it - at least not directly). Since we don't need to do that with these, that won't be a problem here.

You'll also notice that the name of the variable is prefixed with a dollar sign ('$'), followed by the name we want to give it, what we're doing with it ( '=' ), and finally what we're setting it to.

Moving on, the next section creates another variable for us ('$appEnvironments') and sets it to the output from the cmdlet 'Get-AdminPowerAppEnvironment'. When this cmdlet is run, it will prompt the user to provide their credentials and then return all the Environments that account has access to.

Below that section is another where we establish a connection to the Azure Active Directory (AzureAD) - we need to do this ahead of time because later on we'll want to call upon it to resolve user names and emails.

Finally, we're on the last bit of 'warm-up' before we finally start really doing something. In this piece we are again initiating another array (an empty one for now) - but this time we're prefixing the variable name with '[System.Collections.ArrayList]'. Unlike earlier, initiating an array in this fashion allows us to directly Add/Remove from it as needed - which we will definitely need for this!

Ok! Did you make it this far? Excellent, because now we're into where the real fun stuff is happening. Let's speed on through!

We've now come across the first Loop we're be going through - in PowerShell, we do this by using a 'ForEach'.


Breaking the rest of this line apart you'll see the common syntax needed to get us going. Inside of the parenthesis, we're saying "For each object/record inside of '$appEnvironments', give me an object/record called '$environment' to work with".

As some of you may have already guessed, the '$environment' variable is a temporary variable with all of the properties of that Environment (including its name). The curly brackets after the close-parenthesis simply signify the start the actual 'stuff' that we'll be doing 'for each environment'.

Right off the bat, the script wants to let you know which environment it is currently working on - to do so we use the Write-Host cmdlet, followed by a string, and finally use the temporary variable mentioned above followed by '.DisplayName'.

Note: To get to a variables properties, you can follow it with a period ('.') and the name of the property.

Next we want to create yet another array - this time only within the context of this particular loop - and set it to the outcome of the cmdlet 'Get-AdminFlow'. You can probably figure out that this line is retrieving a collection of all of the Flows (that the logged-in user has access to) for the given environment. We tell it what environment by adding the '-EnvironmentName' parameter followed by the DisplayName property of our $environment variable.

Skipping forward a bit, I'll quickly note that the section commented as 'Initialize Counts for Progress Bar' does just that - but I'd be lying if I said I had a complete understanding of how exactly it does it. Truth be told, this is one of those formulas I stumbled across long ago and added to my toolbelt for future use. If I ever recall where I got it from I'll be sure to give credit where credit is due!

One thing that I do know that's happening there, however, is we are creating a variable arbitrarily named '$t' and setting it through some fancy logic that effectively says:

"Give me the Count of Flows inside of '$allFlows' that include Actions or Triggers inside of the '$deprecatedActions' or '$deprecatedTriggers' arrays."

Next we check to see If, for this environment (remember we're still inside of that first Loop), we actually found any Flows that met that criteria.

If so, we will then start another Loop through each affected Flow ("For each affected flow, give me a temporary variable '$flow' to refer to."):


Otherwise ('Else') we'll just tell the user that there were no affected Flows:


Jumping back to the nested loop, you'll see that we are creating a temporary PowerShell Custom Object with all of the Flow properties we care to put out into our report.

I'll leave it to you to dig into them further, but I'd like to call out the two properties 'flowCreatorUsername' and 'flowCreatorEmailAddress'.


Through testing, I discovered that the provided cmdlets used earlier return all of the Flows - but rather unfortunately the properties included in them do not include the creator's name or email address - which are absolutely crucial for any meaningful report.

Thankfully, they do return a property 'userId' - which, if you noticed, we use to query Active Directory and resolve the desired properties.

Again, explaining this piece fully is probably outside of the scope of this article, but in simple terms this is doing a couple of things which can best be summed up as this:

"Give me the username/email address of the user whose ID matches this, and if none is found set this property to 'None/Not Found' instead (of erroring out)."

Once we've got this Flow's properties sorted out, we add this Flow to our earlier-initialized array $affectedFlows and then do some more progress bar witchcraft.

One other thing to point out, the array $affectedFlows was initiated and exists outside of both of the Loops we've gone over.

This means that once we've gone through and added each affected Flow inside of each Environment to it....


... we can finally spit it all out into a report!

I'll breeze through this portion, but you can probably take it apart fairly easily.

This last set of actions:
  • Creates a full filepath for the report, including appending the current time to the filename so that you don't accidentally overwrite any previously written reports.
  • Checks to see if the target folder (in this case, 'C:\PowerPlatformReports\Flow') already exists - and if it doesn't it will create it (otherwise the Export function will throw an error).
  • Most importantly, it takes our now full array '$affectedFlows', sorts them by the flowID, and exports it to the filepath created above as a CSV file.
  • And lastly, uses the Invoke-Item cmdlet to launch the report (and save you a button click).
Whew! That was certainly a lot to run through, but you made it!

To recap...

PowerShell Script Usage Instructions

  • Download the script (link).
  • Run PowerShell ISE (or PowerShell) as an Administrator to install needed modules.
  • Log in using your Global/Environment Administrator credentials to look across all Environments for affected Flows.
  • By default, the report will be placed in a (automatically created, if necessary) folder at 'C:\PowerPlatformReports\Flow'. It will also be automatically launched for convenience.

In Closing

For those of you completely new to the world of PowerShell, I hope this post didn't scare you off completely. The cmdlets available in PowerShell (for a wide-variety of platforms) provide you with a near-infinite amount of capabilities once you start to get a grasp on it. Also, the language itself is very forgiving and generally human-readable. I'd highly encourage you to take a deeper look at it and see what use it may serve you (especially any admins out there reading this!).

Admittedly, I didn't go into every detail in the provided script, but I do think I've covered enough to give you (and your System Administrators) some peace of mind that this script from That Stranger on the Internet won't take down your entire organization. At least not on purpose!

If you come across any errors or issues, or have any PowerShell functions I'm not aware of that are more efficient to use in place of these - please do drop them in the comments and I'd be happy to listen!

As mentioned earlier, I may follow up this post later with ideas on how to handle the other bullets of my 5-part plan to handle this situation (especially considering I will indeed be having to deal with it regardless).

One final point, this post only touched on identifying Flows that will be affected by this change. I will soon be posting a follow-up to help you identify PowerApps that will also be affected. This will not only give you PowerApps using the Office 365 Outlook connector (which you can already get from the Admin Center) - but will also filter those down to any PowerApps that are actually using the affected functions ('SendEmail()', etc.). Exciting!