Jan 9, 2023 6 min read

Use Adaptive Cards from the Workflow app in Teams as a UI for your Power Automate flows

Use Adaptive Cards from the Workflow app in Microsoft Teams to request input and start a Power Automate flow.

Use Adaptive Cards from the Workflow app in Teams as a UI for your Power Automate flows

My company heavily depends on non-group connected SharePoint team sites (WebTemplate STS#3), and create them as Extranets to interact with customers related to a specific project. We allowed Project Managers to set up such an Extranet site themselves, so imagine our disappointment when Microsoft removed that option from the "Create Site"-menu:

New "Create Site" menu

It's still available in the SharePoint Admin Center:

"Create Site" menu in SharePoint Admin Center

No clue why it is gone for end-users, and we can't really give them access to the Admin Center, so it was time to get creative with Power Automate and API's!
Usually I'd resort to the Manually trigger a flow for this, as it'll allow me to request inputs from the end-user and it can be triggered from many locations. The biggest downside of this approach is that it doesn't really allow for sending in-context messages (keep you updated on the process, or inform you that the process failed or succeeded) and it's also far from an ideal UI for end-users.
It seemed like an ideal opportunity to finally test out the Microsoft Teams From the compose box (V2) trigger in Power Automate:

Creating the flow

The flow itself is ridiculously simple, it doesn't even require any Premium licenses for Power Automate. The full flow looks like this, although my production flow has some additional SharePoint steps (for applying a Site Design and adjusting some settings):

Power Automate flow to Create a SharePoint Site

Because of the way I want to distribute the flow, it needs to be created in the default environment. If it's created in any other environment it will not show up in the Workflows app in Teams. It's unfortunate but it's the way it is currently, let's hope they'll improve this at some point.

From the compose box

This Microsoft Teams trigger allows a flow to be started from the Compose box in a Teams chat or channel, and it'll pop-up an Adaptive Card to request input from the user. Once the user submits the Adaptive Card, it'll start the flow.
In my case, this is the design of my Adaptive Card:

Adaptive Card for starting the flow

And the JSON of the Card:

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.3",
    "body": [
        {
            "type": "Input.Text",
            "id": "siteName",
            "placeholder": "Extranet <Customer Name>",
            "spacing": "None",
            "isRequired": true,
            "errorMessage": "You have to enter a siteName",
            "label": "Site name"
        },
        {
            "type": "Input.Text",
            "id": "siteAddress",
            "placeholder": "ext_<customername>",
            "spacing": "None",
            "isRequired": true,
            "errorMessage": "You have to enter a siteAddress",
            "label": "Site address"
        },
        {
            "type": "Input.ChoiceSet",
            "choices": [
                {
                    "title": "Dutch",
                    "value": "1043"
                },
                {
                    "title": "English",
                    "value": "1033"
                }
            ],
            "placeholder": "Select a language",
            "id": "siteLanguage",
            "spacing": "None",
            "isRequired": true,
            "errorMessage": "You have to select a siteLanguage",
            "label": "Site language"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Submit"
        }
    ]
}

Besides the requested input data, you also get a lot of contextual information from this trigger:

  • The team where the request originated (Teams id and AAD Group Id)
  • The channel where the request originated
  • The tenant id
  • The requestor (Display name and AADObjectId)
  • ...

All of this is going to be helpful in the next steps.

Post adaptive card in a chat or channel

Second step is to inform the requestor that the flow is running by posting an Adaptive Card into the originating channel:

Configuration of the "post an adaptive card" action

The card itself will show who made the request, and show a Working on it... status message:

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "Provisioning @{triggerBody()?['cardOutputs']?['siteName']}"
        },
        {
            "type": "FactSet",
            "facts": [
                {
                    "title": "Requested by",
                    "value": "@{triggerBody()?['teamsFlowRunContext']?['from']?['name']}"
                },
                {
                    "title": "Status",
                    "value": "Working on it..."
                }
            ]
        }
    ],
    "actions": [],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4"
}

Get user profile (V2)

A SharePoint site needs an owner, preferably the user that requested the creation of this site, and the API requires the UPN of this user as an input. I don't get the UPN from the trigger so we are using the Get user profile (V2) action to request this information using the user's id:

Send an HTTP Request to SharePoint

Now I call the SharePoint REST API to create the SharePoint site, with the Send an HTTP Request to SharePoint action (one of the few HTTP request actions that doesn't require a premium license):

Config of the "Send an HTTP Request to SharePoint" action

The configuration is straightforward, and I use the SharePoint Admin Center URL as the Site Address. The API is a POST action, and it has the following API path: /_api/SPSiteManager/create. SharePoint API's are very "chatty", returning a lot of unnecessary properties in their JSON objects, so I'm using an Accept header with application/json;odata=nometadata value to reduce this to something workable.
Lastly, I'll use the following body for the API request. It takes the parameters from the card as dynamic data for creation of the site:

{
  "request": {
    "Title": "@{triggerBody()?['cardOutputs']?['siteName']}",
    "Url":"https://contoso.sharepoint.com/sites/@{triggerBody()?['cardOutputs']?['siteAddress']}",
    "Lcid": @{triggerBody()?['cardOutputs']?['siteLanguage']},
    "Description":"",
    "WebTemplate":"STS#3",
    "Owner":"@{outputs('Get_UPN')?['body/userPrincipalName']}"
  }
}

Update an adaptive card in a chat or channel

After the API request, I feed the resulting information back to the user by updating the initial Adaptive Card with the new information. When it's successful, it'll include an action button to open the newly created SharePoint site. When the API fails, it could include a descriptive message (currently not implemented).
The action uses the Team ID and Channel ID from the trigger, and the Message ID is returned from creating the initial Adaptive Card:

Config of the "Update an adaptive card" action

The JSON for the successful card:

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "Provisioning @{triggerBody()?['cardOutputs']?['siteName']}"
        },
        {
            "type": "FactSet",
            "facts": [
                {
                    "title": "Requested by",
                    "value": "@{triggerBody()?['teamsFlowRunContext']?['from']?['name']}"
                },
                {
                    "title": "Status",
                    "value": "Finished!"
                }
            ]
        }
    ],
    "actions": [
        {
            "type": "Action.OpenUrl",
            "title": "Open SharePoint Site",
            "url": "@{body('Create_SharePoint_Site')['SiteUrl']}"
        }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4"
}

The JSON for the failure card:

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "Provisioning @{triggerBody()?['cardOutputs']?['siteName']}"
        },
        {
            "type": "FactSet",
            "facts": [
                {
                    "title": "Requested by",
                    "value": "@{triggerBody()?['teamsFlowRunContext']?['from']?['name']}"
                },
                {
                    "title": "Status",
                    "value": "Failed =("
                }
            ]
        }
    ],
    "actions": [],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4"
}

Sharing the flow

By default the flow is owned by the creator/maker, and only they can trigger it. We needed the flow to be available for all our Project Managers, so we shared it with the Teams team that has all Project Managers as members:

Manage run-only permissions for the flow

I configured it so everyone uses my connection for the SharePoint and Office 365 Users connectors, as I have all the permissions to execute those actions (and they might not). I will let them provide their own connection for Teams, this way the Adaptive Cards will be posted under their name.

Configuration in Microsoft Teams

Every user that wants to trigger the flow from Microsoft Teams needs to have the Workflow app installed:

The Workflows app in Microsoft Teams

Resulting process for the end-user

Now every (authorized) user can start the flow from the compose box in a Microsoft Teams channel, resulting in the following things:

  • Start a new conversation, access the Workflow app
  • Select the Create Extranet Site workflow
  • It will pop-up the Adaptive Card we created, and the user will enter the required data, and select the Submit-button
  • The flow will start and post the initial Adaptive Card
  • When the flow finishes, it'll update the Card with either the Successful Card or the Failure Card.

Finally, everyone is happy again: our PM's at last have a self-service process again, I don't need to create the site for them manually anymore, and I got to use some technology I haven't used before. That's what I call a successful Sunday project 😁.

Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Yannick Reekmans.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.