Skip to content

Site templates for SharePoint Online Modern Sites – part 2

Here we are, at part two. We are diving straight into it. In this post we look at the provisioning engine itself, not the deployment of the engine. That is covered in the third part of this series.

What do we need to build this engine?

  • An Azure subscription
  • Contributor access to this subscription
  • The SharePoint administrator role in Office365
  • An existing teamsite which we will use as a template

Solution design

First off I will give you a schematic overview of what we are building:

Now let’s focus on the individual components of the solution. Similar to Microsoft’s blueprint, we have a site design which invokes a site script (1b). However, where Microsoft’s blueprint uses Flow (Power Automate), we will use an Azure Logic App. The primary reason is that, since Microsoft published their guide, the used HTTP-trigger became a premium connector which requires additional licensing.

The next deviation from Microsoft’s blueprint is the way we execute the PowerShell-script to apply the PnP provisioning template (4). I prefer using Azure Automation for running scripts rather than Azure Functions:

  • Less complex
  • Easier to manage & maintain
  • Only Azure Functions V1 are currently compatible with the PnP-module

There is one downside to using the automation account over the function. We don’t have the option to store our site template anywhere. Therefore we will need a storage account.

Let’s start building

We begin by setting up the necessary service principals needed for this solution.

Exporting a site as PnP provisioning template

Note: at the time of writing the version of the SharePoint PnP PowerShell Online module available in the Automation account modules gallery is 3.19.2003. This is the one I used when exporting my demo template. When exporting your own template make sure you use the same version of the module as you are using in the automation account.

  1. Connect to your template site via the Powershell PnP module
    • Connect-PnPOnline -url <your template site url>
  2. Export the site design as a template
    • Get-PnPProvisioningTemplate -out template.xml

There’s an example template.xml in my github repository. It creates a document library called ‘AppliedSuccessfully’, used to verify the engine itself before spending time on creating your own PnP provisioning templates.

Azure Service Principal

First we need an Azure App Registration. This is used to authorize the Logic App to start runbooks on the Automation Account. Head to the Azure Portal and create a new app registration.

We need to assign permissions to this app registration. I am going to assign the ‘Contributor’-role to my subscription. Technically, for the scope of this part of the guide, the ‘Automation Operator’ on resource group level would suffice. However, in the next part we will be automating the deployment of the solution, which requires a service principal with contributor permissions. If you are implementing this for an organization always consider the least privilege principle. One way to mitigate the risk is using a separate subscription for this solution.

SharePoint Service Principal

Secondly we need a SharePoint-app registration. Use the instruction provided by Microsoft.

Provision the Azure Resources

Now, we move on to creating the needed Azure Resources. In the next post I will show you how to do this programmatically, but for now I’ll assume you do this manually via the Azure portal.

  1. Create a resource group with a name of your choosing, e.g. ‘PnpProvisioningEngine’
  2. Provision the following resources in this resource group:
    • Automation account
    • Storage account
    • Logic App

Your resource group should look like this:

Configure the Storage account

Not much to do here, just create a blob and upload your template.xml to it. For this demo I will use a blob with anonymous read access. However, again, for production scenarios you might want to add a layer of authentication to the way the template is read from the storage account. Be sure to copy the URL of the file after uploading.

Configure the Automation account

We need to do 3 things to here;

1. Import the SharePoint PnP PowerShell Online module:

Through the Azure portal, navigate to the modules section of the Automation account and import the SharePointPnpPowershellOnline-module.

2. Set the necessary variables:

We need a bunch of variables to pass to the script so it can connect to the storage account and SharePoint.

NameTypeValue
PNP_TemplateUrlStringThe URL of template.xml in the blob container
SPO_AppIdStringThe client ID of your SharePoint Service Principal
SPO_AppSecretEncrypted StringThe client secret of your SharePoint Service Principal

After setting these variables the result should look like this:

3. Add the runbook which applies the PnP provisioning template

For this part we’ll use a very simple script which only applies the provisioning template. There’s basically no limit to the actions you can do from this script. I will keep this part simple for now.

Copy or download this script and import it as a runbook in the automation account:

#parameter provided by logic app
Param(
    [Parameter(Mandatory = $true)]
    [ValidateNotNullOrEmpty()]
    [string]$url
)

#first read the automation variables
$appId = Get-AutomationVariable -name "SPO_AppId"
$appSecret = Get-AutomationVariable -name "SPO_AppSecret"
[System.Uri]$templateUrl = Get-AutomationVariable -name "PNP_TemplateUrl"

#download the template.xml to a temporary file on disk
$template = New-TemporaryFile
Invoke-WebRequest -Uri $templateUrl.AbsoluteUri -OutFile $template

#connect to PnPOnline
Connect-PnpOnline -Url $url -AppId $appId -AppSecret $appSecret

#apply the template, clear navigation prevents ending up with duplicate entries in the site navigation
Apply-PnPProvisioningTemplate -Path $template.FullName -clearnavigation

Build the Logic App

Now we start to connect things. First we need to build our logic app to trigger the runbook on an http-request. This is what the site script will invoke in the next stage.

1. Go into the Logic App designer and start with a HTTP-trigger

2. Add the request body JSON schema

This code will allow you to capture the property ‘webUrl’ from the incoming HTTP-request fired by the site script action.

{
    "properties": {
        "webUrl": {
            "type": "string"
        },
        "parameters": {
            "properties": {
                "event": {
                    "type": "string"
                },
                "product": {
                    "type": "string"
                }
            },
            "type": "object"
        }
    },
    "type": "object"
}

3. Add the automation connector

Add a new step to the app. In the search bar type ‘Create Job’ and select the Azure Automation Create Job-Action:

Then connect the connector to your tenant and enter the necessary parameters:

ParameterValue
SubscriptionYour subscription
Resource GroupThe resource group you are using in this solution
Automation AccountThe name of your automation account
Wait for job‘No’
Runbook NameApply-PnpProvisioningTemplate or whatever name you choose when importing the script
Runbook Parameter urlSelect the webUrl from the dynamic context menu

The end result should look like this:

4. Finally, press save!

Once the app saves, the HTTP POST URL will be generated. Copy it, we will need it later.

With the app saved we can also test whether it works. From a PowerShell commandline enter the following lines:

$body = "{ 'webUrl':'http://test.me' }"
$uri = '<your HTTP POST URL here>' 
Invoke-WebRequest -Uri $uri -Method POST -Body $body -ContentType "application/json" -UseBasicParsing

Be sure to update the $uri with your HTTP POST URL property before running. You should be able to see the request trigger the logic app. Which in turn creates the job in automation, passing the webUrl-parameter along with it. Execution of the script will fail due to us not providing a valid url to a SharePoint-site, although the job will have run successfully.

Add the site design

Now that our back-end is fully operational we can create the site script and attach it to a site design.

First let’s create the site script. This is a simple JSON file. You can use the sample provided below or create one yourself. Be sure to update the ‘url’ with the HTTP POST URL from the previous step.

{
    "$schema": "https://developer.microsoft.com/json-schemas/sp/site-design-script-actions.schema.json",
    "actions": [
        {
            "verb": "triggerFlow",
            "url": "<your HTTP POST URL>",
            "name": "Trigger PnP template",
            "parameters": {}
        }
    ],
    "bindata": {},
    "version": 1
}

Edit the JSON above in notepad and copy it to your clipboard.

Now we will upload this script into the SharePoint admin center and attach it to a site design. At this point we are back in sync with the Microsoft blueprint.

To complete the engine, run the following commands in a PowerShell commandline (this requires the SharePoint Online Management Shell or alternatively you can also do this through the PnP-module):

Connect-SPOService -Url https://[yourtenant]-admin.sharepoint.com

$script = Get-Clipboard -Raw
$siteScript = Add-SPOSiteScript -Title "Apply PnP Provisioning Template" -Content $script
Add-SpoSiteDesign -Title "Apply PnP Provisioning Template" -SiteScripts $siteScript.Id -WebTemplate 64 

Presto magic!

You should now have the new site design available in your SharePoint admin center:

And when you create a site your template should be applied. If you used my example template you will see a document library called ‘AppliedSuccessfully’ in the site’s navigation.

Where’s the automation?!

As I stated in part one, I love automation. In the next part of this series we will automate the deployment of this solution with Azure DevOps. This makes it a lot easier to perform updates, roll it out to additional tenants and all kinds of other non-clicky goodness. However I felt it was important to also share the basics of the solution with you. So when you are debugging your release pipelines later you have a good understanding of where the solution ends and the automation begins.

Published inGuides

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *