Thanks to the workflow that notifies me of updates to the Microsoft Graph API, I saw a new addition to the list: the sitePage resource type.
This is exciting for me, as I currently have some workflows that distribute SharePoint pages to various sites both within our own tenant, as well as client tenants. Currently these are triggered by a page being published in a central location, with specific information used as trigger conditions.
What’s annoying about this scenario is that I need to create connectors in Power Automate to the client tenant using an account in their environment. It also means I need a workflow per client (to keep it clean).
Now with the addition of the sitePage resource type in Microsoft Graph, I can make this work programmatically across any number of clients – all from a single workflow.
WARNING: This is a beta feature at present, so don’t use it for production systems unless you’re find to accept the risks.
Requirements
The requirements of this are fairly simple. We need:
- An app registration in Azure AD that has the “Sites.ReadWrite.All” application permission added
-
A repository where the details are stored, including:
- Client name
- Tenant ID
- App/Client ID
- Secret
- SharePoint site ID
Now, we could use a different way to authenticate, and we could also use an action to perform a search in the tenant to find the relevant site by name or URL, but if we’ve got that – then it’s not exactly difficult to get the SharePoint site ID and store it in our repository.
For the purposes of this, I’m going to store it in a SharePoint list:
Â
Workflow
At a high-level, my workflow is quite simple:
Â
In my specific scenario, all the workflow is doing is publishing a page with an embedded video, as part of a program of regular content I create for clients. So all I need to provide is a page title and the URL suffix from the embed code.
The next step of the workflow takes my page title, and turns it into a file name:
The code used here is:
concat(replace(triggerBody()['text'],' ','-'),'.aspx')
Â
From here, we’re now ready to retrieve all the sites we want to apply this to:
Within our Apply to Each, we have three steps:
- Create the page
- Parse the JSON of the page creation
- Publish the page, using the ID from step 2
(If you’re comfortable with extracting the page ID value directly from the results of step 1, then you don’t need the Parse JSON action.)
In the page creation action, I’m creating a very simple page that only has a single embed web part on it, and I’m passing variables from both the trigger as well as the Get Items action.
Â
The Parse JSON is relatively straightforward:
And for the final step we hit publish on the page:
Â
And that’s it! We have a simple page published in each tenant listed, with the same content.
If you want something more glamorous, refer to the sitePage resource type page to get a breakdown of the structure of the body of the content.
Â
Appendix
Here’s the full details of the body of the page creation and Parse JSON actions.
Create page
{
"name": "@{outputs('Compose_-_replace_spaces_with_hyphens_and_add_file_extension')}",
"title": "@{triggerBody()['text']}",
"pageLayout": "article",
"promotionKind": "newsPost",
"showComments": false,
"showRecommendedPages": false,
"titleArea": {
"enableGradientEffect": true,
"imageWebUrl": "/_layouts/15/images/sleektemplateimagetile.jpg",
"layout": "plain",
"showAuthor": false,
"showPublishedDate": true,
"showTextBlockAboveTitle": false,
"textAboveTitle": "",
"textAlignment": "left",
"imageSourceType": 2,
"title": "@{triggerBody()['text']}"
},
"canvasLayout": {
"horizontalSections": [
{
"layout": "oneColumn",
"id": "1",
"emphasis": "none",
"columns": [
{
"id": "1",
"webparts": [
{
"id": "669d4d75-eca0-4e8b-95d7-2e765dd4859a",
"webPartType": "490d7c76-1824-45b2-9de3-676421c997fa",
"data": {
"audiences": [],
"dataVersion": "1.2",
"description": "Embed content from other sites such as Sway, YouTube, Vimeo, and more",
"title": "Embed",
"properties": {
"embedCode": "<iframe src=\"https://player.vimeo.com/video/@{triggerBody()['text_2']}\" width=\"640\" height=\"360\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"\"></iframe>",
"cachedEmbedCode": "<iframe src=\"https://player.vimeo.com/video/@{triggerBody()['text_2']}\" width=\"640\" height=\"360\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen=\"\"></iframe>",
"shouldScaleWidth": true,
"thumbnailUrl": "",
"cachedEmbedCodeThumbnail": ""
},
"serverProcessedContent": {
"imageSources": [
{
"key": "imageSource",
"value": "/_LAYOUTS/IMAGES/VISUALTEMPLATEIMAGE1.JPG"
}
]
}
}
}
]
}
]
}
]
}
}
Parse JSON
{
"type": "object",
"properties": {
"@@odata.context": {
"type": "string"
},
"@@odata.etag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"id": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"webUrl": {
"type": "string"
},
"title": {
"type": "string"
},
"pageLayout": {
"type": "string"
},
"thumbnailWebUrl": {
"type": "string"
},
"promotionKind": {
"type": "string"
},
"showComments": {
"type": "boolean"
},
"showRecommendedPages": {
"type": "boolean"
},
"createdBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
}
}
}
}
},
"lastModifiedBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
}
}
}
}
},
"parentReference": {
"type": "object",
"properties": {
"siteId": {
"type": "string"
}
}
},
"contentType": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"publishingState": {
"type": "object",
"properties": {
"level": {
"type": "string"
},
"versionId": {
"type": "string"
}
}
},
"reactions": {
"type": "object",
"properties": {}
},
"titleArea": {
"type": "object",
"properties": {
"enableGradientEffect": {
"type": "boolean"
},
"imageWebUrl": {
"type": "string"
},
"layout": {
"type": "string"
},
"showAuthor": {
"type": "boolean"
},
"showPublishedDate": {
"type": "boolean"
},
"showTextBlockAboveTitle": {
"type": "boolean"
},
"textAboveTitle": {
"type": "string"
},
"textAlignment": {
"type": "string"
},
"title": {
"type": "string"
},
"authors@odata.type": {
"type": "string"
},
"authors": {
"type": "array"
},
"authorByline@odata.type": {
"type": "string"
},
"authorByline": {
"type": "array"
},
"imageSourceType": {
"type": "integer"
},
"serverProcessedContent": {
"type": "object",
"properties": {
"imageSources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"key",
"value"
]
}
}
}
}
}
}
}
}
Also published on Medium.
Discover more from Loryan Strant, Microsoft 365 MVP
Subscribe to get the latest posts sent to your email.