Wow!… So first of all, it’s been a long time since I’ve actually written an article (and a bit since I’ve created content on YouTube). Hopefully this year will include new fun projects, write-ups, and reviews!
Onto the current project at hand…
So I had a bit of a project goal of mine. I wanted to kickoff a user community forum, but I needed to make sure I had enough initial content to make it worth while to stay and contribute.
Internally, our employees have a large swath of discussions and data available in Microsoft Teams. Not all of this data is publicly consumable; however, a large portion could be incredibly valuable external facing. Often times we discuss new capabilities, neat tricks, or troubleshooting and resolution. Like most busy people, we often struggle with creating knowledge base (KB) articles in the moment and eventually the data gets lost to the horrible search that is Teams.
My idea was to automate flagged conversation threads and migrate them to a sanitized area within our Discourse forums. The entirety of the thread would be migrated, but at a later date I would have a singular spot to clean the data and re-categorize/tag as necessary.
So I guess Microsoft Power Automate?
Originally I looked at options for integrating Teams and Discourse together. Teams has it’s app extensions and natively offers connectivity with webhooks. Discourse has plugins (webhook notifications in Teams), as well as a specific Teams integration that I’m still not sure what I’m looking at. Additionally, other sites such as IFTTT.com offer webhook and API automation that has been able to fill the gap of missing plugins. Unfortunately, none of these did what I wanted. Push Teams messages to Discourse.
So I started looking at the “easiest” way to initiate automation based on user actions. This lead me to Microsoft’s Power Automate (formally known as Microsoft Flow).
Power Automate (PA) is a web, app, and teams app based platform that can trigger automation tasks. I learned this is probably the best way to describe the platform. “Trigger Automation Tasks”. The struggles are real the more in-depth of tasks you are looking to automate, but I’ll get into that in a bit.
The benefit of PA over other platforms [I thought], was the fact it’s directly integrated with Teams and there was little to no scripting required. PA offers a UI wizard for piecing together your actions, and with the native tie-ins, can initiate off of manual intervention or even things like keywords within specific channels or chats. And from the outside, this appears great.
Grabbing the trigger message and authentication
The easiest portion of the workflow is the trigger. Because of the native Teams integration, I could simply configure Channel or Chat, select the team or teams, and decide on a keyword(s) to monitor.
Next I needed to initialize the variables, which seemed a bit odd when you think of Windows automation (PowerShell). The initialize list took a bit of adjusting to fit the final workflow, but I was able to eventually find the perfect mix of vars required to store the various messaging data.
At this point I wanted to grab information about the thread that the trigger message was posted in. There is a simple “Get Message Details”, which worked fine for grabbing either the initiating message or the parent message being replied to. However, there didn’t seem to be a mechanism to identify the other messages in thread.
Researching online, there seems to be a long standing request for this functionality, but Microsoft feels REST calls to their Graph endpoints solves this. As I came to find out, Graph calls unfortunately over complicate the situation, especially when it comes to chat and channel messages.
In order to make these REST calls, I created an App Registration within the Azure portal and granted admin consent for the API endpoints required.
Note: The App client_id and tenant_id will be needed later. A secret will also need to be generated to authenticate as the application if desired.
- Channel.ReadBasic.All
- ChannelMessage.Read.All
- ChannelSettings.Read.All
- Team.ReadBasic.All
I found that there were two methods of granting these endpoints, either delegated, or application.
Application would be ideal from a programmatic perspective, but allows organization wide data to be retrieved. Additionally, the endpoints required for these actions are protected API calls, which are calls to overly sensitive private data such as conversations, email, etc. In order to allow me to use these API endpoints, I would fill out a Microsoft Form and every Wednesday Microsoft sits in some corner of their building and manually review these requests.
The second option would be delegate access. Delegate has the same rights as the user token being provided. Utilizing a service account (or user), I was able to retrieve my authentication token and pass that as a variable to my Graph calls.
The benefit of delegate is there are no wait periods and third party approval. The negative, is you must scope the user to any of the Teams in order to grant specific access. Also, if there are constraints around users and password rotation, this can be a bit of a pain to keep up on.
It was good, but not great…
Once I had the graph calls figure out, I struggled a bit with interpreting the data in PA. The “Apply to each” functioned nearly like a common For Each loop performed in any scripting language. It did throw me for a bit realizing I could only feed in specific outputs from previous steps and if I chose a var, such as my replyArray, I seemingly could not use that variable again within the loop.
Typically, in PowerShell I would do something like ForEach($reply in $replyArray) {}
.
In the end, I found it a bit cumbersome that PA is all GUI driven, including the variable usages. I found that if I “peaked the code” or hovered over a variable, I could determine how something was actually written. This was incredibly useful for getting the Apply to each loops to work, but isn’t immediately apparent to new users.
Essentially, I had to create my own expressions and reference array data utilizing the ?['']
blocks. As seen in the example above, to reference the replyArray data for user ID, I had to type item()?['from']?['user']?['id']
. Something I would’ve never guessed at until talking to others.
Overall, it took a bit to get the hang of, like most newly learned things.
Ideally, I would’ve liked to see an easier way to trigger a PowerShell script. There are some ways to do this, but they aren’t clean and require even more resources spun up. Since I already had to make calls manually to Graph, if I could’ve just used PowerShell the entire time, I would’ve been done in maybe 20 minutes instead of the 3 days I experienced.
There are definitely benefits to using PA due to the native tie-ins to Microsoft products, but they seem to be limited in use cases no matter the integration. Missing native actions for message threads seemed like a silly oversight, for example.
Other questionable limitations exist as well, such as the inability to sort. From a company that built it’s backbone off of dynamic sorting and pivot tables in Excel, I literally had to use a task to Excel to sort my array -_- This task was the cleanest way to approach this and required random code I found online to actually work.
Lastly, it’s scary modifying your workflow. Slight changes could break the flow even if the values were the exact same. I found a few times I had to delete then recreate the exact same object to correct issues at times. Also, trying to move the tiles of tasks/blocks around rarely worked. If you noticed a branching logic was required towards the beginning of the flow, you often times had to start over. -ugh-
The Entire Workflow
Here is the workflow in it’s entirety. It may not be optimal but it does the job just fine. Note, because Graph limits return to a max of 50, if you have threads longer than 50 replies, you will have to modify this to accommodate paging of the files.
Enjoy the benefits!
Any questions on the workflow, I’ll be happy to supply answers. Hope others find this useful!
Be sure to check out https://youtube.com/codethethings for future DIY, reviews, and news!