Auto-configuring SharePoint Sites with Site Templates — The Setup from Start to Finish
Last updated on August 12, 2024
What I am about to tell you in this blog post isn’t a new invention. In fact, I presented a demo about this exact same scenario during a keynote at European SharePoint, Microsoft 365 and Azure Conference (ESPC) in 2019 in Prague with Vesa Juvonen. Still, as I found myself setting up the same provisioning solution for yet another customer almost three years later, I thought, why wouldn’t I document the entire setup process so other people can do this work effortlessly, too?
SharePoint site provisioning is a common customer request, and you often need to do things like provision page templates or adjust page layouts. For those needs, you definitely want to utilize PnP Provisioning Templates, which you then apply to sites programmatically. To trigger the process, you could create a custom order form, but often a way neater solution is to offer the end users a chance to start the automated customization/configuration process via SharePoint Site Templates that are present in the native SharePoint user interface. The SharePoint Site Template (previously known as SharePoint Site Design) can trigger an Azure Logic app, which can further trigger an Azure Function where we can run custom code; and hence, all kinds of magic can happen. In this particular case, it allows us to apply the PnP template using PowerShell.
This basic solution architecture is always the same, but because templates can be so different, it allows you to create many different kinds of sites, always to match your customer’s exact needs. You may wish to watch the demo video below to better understand what the entire process looks like both to the end user and behind the scenes (mind you, things have slightly changed over the years). After that, let’s proceed to set it up for you!
Table of Contents
- Preparations
- Create the PnP Provisioning Template
- Store the PnP Template on SharePoint
- Create a new Azure resource group
- Create the Azure Logic App
- Create and deploy the SharePoint Site Template
- Create an Azure Function App
- Authenticating with the Azure Function managed identity
- Authenticating with a self-signed certificate
- Finalize Function App configurations
- Finalize the Logic App implementation
- Afterword
Preparations
Before we begin creating all the necessary resources, let’s install the PnP.PowerShell module. It is required by multiple steps along our journey.
- Run Windows PowerShell as an administrator
- Execute the following command:
Install-Module PnP.PowerShell
Create the PnP Provisioning Template
We need to create a PnP Provisioning Template to be able to apply custom layouts on SharePoint Online sites. The easiest way to create a PnP Provisioning Template is to first create and configure a “template site” on SharePoint Online and then export it as an XML file with the Get-PnPSiteTemplate
commandlet.
You can run the commandlet as is, or alternatively specify what resources you wish to export. The template file will be saved into the same directory where you run the script.
Alternatively, you can use one of the very fancy-looking and readily available site templates illustrated on the SharePoint Look Book site.
Store the PnP Template on SharePoint
We want to put the PnP Provisioning Template file in a location where our provisioning logic can easily read it. Storing it in a SharePoint document library allows us to do that, and in addition, we can also update the file easily if necessary. The document library versioning feature can also tell us what has changed in the template file between versions.
So, upload the template to a document library on your tenant’s SharePoint Online environment. The location does not matter, but for your health and well-being, ensure that the template file/library/site permissions are limited so that no unauthorized users can change the template.
After you’ve uploaded the template, take note of the library URL. You’ll also need to remember the template file name later.
Create a new Azure resource group
Next, let’s head off to the Azure portal. I’m assuming you have an Azure subscription set up at this point (hence I won’t give you instructions for creating one).
- Create a new resource group in your subscription with a descriptive name (adhere to your company’s naming conventions) and a region near you.
- You can optionally add tags. Otherwise, click on Review + create.
Create the Azure Logic App
After the resource group has been deployed, let’s create a Logic App in it.
- Click on Create a resource and search for Logic App on the Marketplace. When you find it, click on Create.
- Again, give your logic app a descriptive name according to your organization’s naming conventions (it will also be the URL prefix).
- As the Publish type, select Workflow.
- Select the same region as you selected for the resource group.
- The Plan type should be Consumption as it is the preferred (cheaper) option for our solution.
- Decide whether to enable zone redundancy or not, depending on how crucial you consider the provisioning. Personally, I left it as Disabled.
- Again, you may optionally tag the resource, or directly proceed to Review + create.
Adjust the logic
After the logic app has been deployed, proceed to create a blank logic app.
- Add the When a HTTP request is received trigger to it.
- Specify the following schema to be expected of the request body.
- After the trigger, add a Delay action and set it to 1 minute. This will ensure a new site has enough time to get fully created before we attempt to apply our template onto it if a user starts the provisioning process immediately after creating a new SharePoint site.
- Then, Save the logic app. After saving, the URL at the top of the trigger action will become available.
- Copy the URL to, e.g., Notepad. We’ll need it in just a moment when we create our SharePoint Site Script.
Enable the managed identity
To allow our logic app to trigger an Azure function we’ll be creating in a later step, we need to enable the logic app’s Managed Identity. After the managed identity has been enabled, we can permit it to trigger the function. But don’t think about it too much at this point; let’s focus on enabling the managed identity for now.
- Navigate to the logic app settings by going back to the resource group level and then clicking on the logic app resource.
- Open the Identity blade.
- Change the system assigned managed identity Status to On.
- Click on Save.
Create and deploy the SharePoint Site Template
Next, we’ll create a SharePoint Site Template which functions as the “start button” of our entire site provisioning process. It will allow us to trigger our logic app from the SharePoint Online native user interface. The site template contains a site script (JSON) where we define the task, and then we deploy the site script and template to SharePoint Online with a PowerShell script.
If you’d like to learn more about SharePoint Site Templates, check out my thorough and extremely popular blog post about the topic.
Create the site script
Let’s create the site script.
- Open a new text file and paste the below JSON markup into it.
- Complete the site script by pasting URL we copied earlier from the logic app trigger to the triggerFlow action.
- Save the file as site-script.json.
If you like, you can also add further actions to the site script in addition to the one that triggers the logic app. You can see all the available actions in the schema documentation.
Deploy the site template
When you are done implementing the site script, deploy it to your tenant by running the following PowerShell snippet. You’ll need to install the PnP.PowerShell module first if you did not previously do so, and run the script using credentials that have SharePoint Administrator privileges.
We are already at the point where we can trigger our logic app by applying our site template to an existing site via the site settings! All of this is coming together quite nicely.
Create an Azure Function App
Let’s go back to Azure Portal and create a new Function App into our resource group with the following settings:
- Select your subscription and the resource group you created earlier.
- Give your function app a name (according to your organization’s naming conventions). This will also function as the URL prefix.
- Choose to publish your solution as Code.
- Select PowerShell Core as the runtime stack, and the latest version.
- Change the Region to the same one you chose when creating the resource group.
- Leave the Operating system to Windows and Plan type to Consumption.
- Go to the Hosting tab, click on Create new and rename the storage account according to your organization’s naming conventions.
- Go to the Monitoring tab, click on Create new and rename the application insights resource (and a new workspace) according to your organization’s naming conventions.
- You may optionally tag the resource. Otherwise, click on Review + create.
Add the script to the function app
After the function app resource has been deployed, click on Go to resource, and perform the following configurations.
- Open the Functions blade.
- Create a new function:
- Leave Development environment to its default value (Develop in portal).
- Select the HTTP trigger template.
- Give your function a name, e.g., ApplySiteTemplate.
- Change the Authorization level to Anonymous. We are going to use Entra ID for granting access instead. Changing the level to Anonymous removes the unnecessary function key from the URL.
- Click on Create.
- Click on Code + test in the navigation.
- Finally, replace the default script with the one below, and click on Save.
Specify the required modules to load
If you look at the PowerShell script above (the one you just pasted into the function), you’ll notice that it also uses the PnP.PowerShell module. Because of this, we need to tell our function app to import that module before script execution.
- Go back to the function settings by clicking on its name in the breadcrumb navigation at the top.
- Open the App files blade.
- Select requirements.psd1 from the dropdown.
- Add references to the Az.Accounts and Az.ManagedServiceIdentity modules. I recommend installing these two Azure PowerShell modules (and other possible Az modules required by your script) explicitly instead of installing the entire Az module collection because the latter takes a really long time and results in a lot of hassle when it comes to installing the dependencies for the first time (namely, the function timeouts on a consumption plan)
- Add a reference to the PnP.PowerShell module. The file should now look something like the one below.
- Remember to Save the changes.
Enable Managed Identity
Now, we need to enable the function app’s system managed identity precisely as we did for the logic app earlier. We can either use this identity directly for authentication, or alternatively use it to fetch the certificate for authentication from an Azure Key Vault. Either way, the managed identity always needs to be enabled.
- Open the Identity blade
- Change the system assigned managed identity Status to On.
- Click on Save and Yes when prompted
- Copy the Object ID that appears upon saving to, e.g., Notepad. You’ll need this later.
Enable Entra ID authentication
As I mentioned, we will prevent unauthorized access to the function by enabling Entra ID authentication for our function app. Because we just assigned permissions to the function for our Logic App, it will be able to execute the function, but any outsider making an HTTP POST request to the function URL will receive a 401 Unauthorized response instead.
- Go to the Authentication blade.
- Click on the big blue button to begin adding an identity provider.
- Select Microsoft.
- For the the Client application requirement, select Allow requests from specific client applications and specify the logic app you created earlier.
- Choose to return status code 401 Unauthorized for unauthenticated requests.
- Otherwise, you can keep the default settings (create a new app registration with the same name as the function app, support only the current single tenant, require authentication and enable token store).
- Click on Add
After the identity provider has been added, click on the Edit icon. We need to perform the following changes to ensure authentication from our logic app to our function app succeeds.
- Remove the 2.0 from the end of the Issuer URL
- In the Allowed token audiences list, copy the GUID visible on the first row (after api://) and add the GUID it to its own row immediately below the API URL like in the picture below.
Authenticating with the Azure Function managed identity
Today, it is possible to authenticate to SharePoint Online via the PnP.PowerShell module also with Azure resource managed identities. I highly recommend you to use that approach instead of the certificate authentication detailed below. You can find detailed instructions on how to configure it in another one of my blog posts. Make sure to use the authentication command given in that blog article in the script we added to the Azure Function earlier.
Authenticating with a self-signed certificate
In the past, authenticating with a certificate was the only way; it was not possible to authenticate with a managed identity. Today, I highly recommend you to authenticate with the managed identity directly as setting up a certificate this way just requires additional effort and it is not as secure as the managed identity because it is always possible that the certificate may get leaked. However, I’ll leave these previously written instructions here just in case there is ever a bug or some other scenario which might require you to resort to authenticating with a certificate instead.
Create a self-signed certificate
As I mentioned briefly in the Enable managed identity section, we need to generate a self-signed certificate to allow our PowerShell script to authenticate to SharePoint Online. This happens by executing the following script (after you’ve adjusted the $commonName
and $password
variable values). The certificate files will appear in the same directory where you execute the script.
Configure the Entra ID application registration
If you’ve been following the instructions, you should still be on the function app Authentication blade on Azure Portal.
- In the Identity provider list, copy the App (client) ID to, e.g., Notepad. We’ll need to include this later in the function configuration.
- Then, click the link that appears in brackets after the word Microsoft. The link will direct you to the Entra ID application registration we created when we set up the identity provider.
- In the application registration settings, first go to the API permissions blade, and do the following:
- Click on Add a permission.
- Select SharePoint.
- Select Application permissions.
- Select Sites.FullControl.All.
- Click on Add permissions.
- Click on Grant admin consent for organization. You may need to ask your global admin to perform this step if you do not have the required permissions.
- Then, go to the Certificates & secrets blade.
- Open the Certificates tab.
- Click on Upload certificate.
- Select the .cer file we generated with the PowerShell script above. You may also optionally enter a description to explain what the certificate is for.
That’s it; all configurations for the application registration are now done!
Create and configure an Azure Key Vault
Azure Key Vault offers a secure place to store the certificate so that our function app can also access it during runtime. On Azure Portal, go back to the root of the resource group. Then, click on the button to create a new resource, and search for Key Vault on the Marketplace. Then, create one with the following settings.
- Select the same subscription and resource group as you’ve done for all the resources we’ve created before.
- Give the key vault a name according to your organization’s naming conventions.
- Select the same region as you’ve done before.
- Leave the pricing tier to Standard
- You may wish to enable purge protection.
- Open the Access policy tab and click on the Add access policy link.
- Select Get permission from the Secret dropdown.
- Click on the None selected link. In the search field, paste the function app managed identity GUID you copied into Notepad (or similar) earlier. Add the function app as the principal.
- Again, you may tag the resource. Otherwise, click on Review + create
Add the certificate to the vault
When the key vault resource has been provisioned, go to it.
- Navigate to the Certificates blade.
- Click on the Generate/Import button at the top.
- Select Import.
- Give a descriptive name for the vault certificate.
- Upload the .pfx certificate file you generated earlier.
- Provide the same password as what you used when generating the certificate.
To be able to reference the certificate in your function app, you’ll need its URL.
- Click on the certificate you just added to the key vault.
- In a similar manner, also click on the current certificate version.
- Copy the Certificate Identifier (URL) to, e.g., Notepad. You can remove the version (GUID) from the end of the URL. This way, the latest version of the certificate will always be used. We’ll need this value in just a second!
Finalize Function App configurations
Finally, go back to your function app in Azure Portal. Click on Configuration and add the following application settings.
Name | Value | Description |
---|---|---|
Tenant | yourtenant.onmicrosoft.com | The prefix is the same as in your SharePoint URL. This setting is needed for certificate authentication and can be ommitted if you are using managed identity authentication. |
ClientId | Your Entra ID app/client ID | You copied this earlier from the function app Authentication blade. This setting is needed for certificate authentication and can be ommitted if you are using managed identity authentication. |
Certificate | @Microsoft.KeyVault(SecretUri= https://yourvaultname.vault.azure.net /certificates/yourcertname) |
This is called a key vault reference. It allows our function app to retrieve the certificate from the vault using its managed identity. You’ll need to paste the certificate identifier URL we just copied as the SecretUri value. This setting is needed for certificate authentication and can be ommitted if you are using managed identity authentication. |
TemplateLibUrl | https://yourtenant.sharepoint.com /sites/yoursite/yourlib |
The URL to the library where the template file is located. This setting is needed for fetching the template file from SharePoint. |
TemplateFileName | site-template.xml | The name of the template file. This setting is needed for fetching the template file from SharePoint. |
WEBSITE_LOAD_USER_PROFILE | 1 | This setting is required to ensure the function app is able to fetch the certificate from the key vault (needed for authentication). This setting can be ommitted if you are using managed identity authentication. |
Now our function app is all set!
Finalize the Logic App implementation
The last thing we need to do to get our complete process to work is to add the action to our logic app that will trigger the Azure function.
- On Azure Portal, go back to the logic app resource.
- Take it into Edit mode.
- Click the button to add a new step.
- Search for function and select Azure Functions from the search results.
- Your Azure function app should be listed. Click on it.
- Select the function that contains the PowerShell script.
- Add the following to the Request Body field:
{ "webUrl": "@{triggerBody()?['webUrl']}" }
- In the Add new parameter dropdown, select Authentication
- Select Managed identity as the Authentication type.
- Paste the Client ID (GUID) you copied to Notepad earlier on the function app Authentication blade into the Audience field.
- Save the logic app. We should be now good to go!
Afterword
Phew! That’s the entire process described in great detail from the beginning to the end! You can use this same base architecture for provisioning a limitless variety of SharePoint Online sites. To achieve the desired result, the only thing you need to change is the PnP Provisioning Template.
I hope you enjoyed reading this article and found it helpful! If you’d like to read more of my content, subscribe to my newsletter and follow me on social media. And if you feel like I helped you out in a pinch and want to express your gratitude, I’m always up for drinking more coffee!
Feel free to ask questions regarding the topic and comment on the article below. Otherwise, thanks for reading, and until next time!
Laura
Hi there! Thanks so much for this article. I am getting an error when trying to export the template from my SharePoint site that says “Get-PnPSiteTemplate: GetAccessTokenAsync() called without an ACS token generator. Specify AuthenticationManager construct or the authentication parameters”.
From my understanding “-Interactive” is no longer supported. I am trying to use “-UseWebLogin” which doesnt seem to generate that ACS token. I have registered PnP as an app in Entra but it’s still not working.
Any ideas?
Thanks so much!
Hi Laura,
Thank you for posting this. It was extremely helpful.
Regarding the “403 Forbidden” error encountered while calling a function app from a Logic App, I was able to identify the problem. When setting up the function app identity provider, you need to ensure that the correct additional checks are configured. By default, the client application requirement is set to “allow requests only from this application itself.” This setting needs to be changed to allow calls from the Logic App.
Thanks for figuring this out, Jakub! The default value for this setting must have changed since I originally wrote this blog post. I’ll update it to the instructions. Cheers!
Laura
This has been a go to article for me for the last few years… A great guide into using PnP.Templates with Azure Functions. I do have one question though… It can take some time for PnP.Templates to apply and in the mean time users could potentially start editing the site. Is there any way to add a banner some sort of information to stop the user from editing the site until the PnP.Template (Azure Function) has finished running?
I have tried splitting up the PnP.Template and running various parts of the template using Azure Durable functions but there seems to be issues with using PowerShell Core and Fan Out Fan In patterns. It would be really nice if you could extend the Site Script configuration banner or something similar until the PnP.Template had also completed.
Hi Alan,
I’d probably do the following:
1. Have a list somewhere in SharePoint with columns “SiteUrl” and “ProvisioningStatus”. All users need to be given Read permissions to the list.
2. In your provisioning process, before applying the PnP provisioning template, add a new list item that contains the provisioned site URL and status “In progress”.
3. Create a header banner as an SPFx extension (application customizer). Deploy that to the entire tenant, and make it read the SharePoint list, and display the banner only on the sites for which a list item can be found and which have the status “In progress”.
4. After the PnP provisioning template has been applied, update the “ProvisioningStatus” to “Completed” or delete the list item. This would hide the banner.
Hope this helps!
Laura
Hey Laura – This is a great article and very easy to follow. When I try to test the template, the Logic App fails on the Azure Function with a 403 Forbidden. I have the Function setup exactly as you describe and I am referencing the Client ID (GUID) of the Azure App Registration. That App Registration does have the Sites.FullControl.All as Application Permissions. The only thing I can think of is that your instructions specify to choose “Managed Identity” and up above you outline the steps to use certificates (with a separate article for Managed Identity). Should those last few steps be different if I am using certificates? Or maybe I’m missing something else obvious. Any help would be appreciated. Thanks!