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:
It's still available in the 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):
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:
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:
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):
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:
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:
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:
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 😁.