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 Laura,
Thank you for providing this. I enjoy reading your posts. Unfortunately, I have some issues with this config. Under Logic App run history I see “You do not have permission to view this directory or page”. I’m assuming I made some mistakes while giving SPO permissions to managed identity. Do you have any clue why I see this error?
Thank you
Hi Laura,
Firstly, thanks for providing these great instructions and it’s certainly worth at least a coffee! Unfortunately, the script in the function app is not being executed showing the error “forbidden” in the logic app runs history.
I assume has something to do with the authentication (used the azure function managed identity), but it is not the 401 error I enabled beforehand.
Further, I could not find where to insert the Object ID of the function app (under Identity). can this cause my error?
BR
Patrick
Hi Patrick,
Based on your description, I think you might have accidentally skipped this step: https://laurakokkarinen.com/auto-configuring-sharepoint-sites-with-site-templates-the-setup-from-start-to-finish/#authenticating-with-the-azure-function-managed-identity
Laura
Hi Laura,
I recently tried upgrading my PnP.Powershell provisioning solution to use Powershell 7.2 with the latest nightly build as recommended here…
https://pnp.github.io/powershell/articles/azurefunctions.html
Unfortunately if would appear that SharePoint REST API is still not supported by Managed Identities, I then tried to use a certificate but It would appear that this doesn’t work either at present. I would be really great to get some input from you on what you recommend in terms of a stable PnP.Powershell to use with Azure Functions…
I also noticed that in the PnP.Powershell guide they state that you should comment out the:
# if ($env:MSI_SECRET) {
# Disable-AzContextAutosave -Scope Process | Out-Null
# Connect-AzAccount -Identity
# }
If you are using Managed Identities this is not a good idea…
Would love to hear your input on the above… 🙂
Hi Myatix,
The nightly build is the beta version, so it is not guaranteed to work. I’d use the production-ready version to avoid issues.
And indeed, if you want to use the managed identity approach for authentication, you should not comment out that section.
Laura
Hi Laura,
I see the following error from the Function App / Monitor: ERROR: The term ‘Connect-PnPOnline’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. Exception : Type : System.Management.Automation.CommandNotFoundException ErrorRecord….
I followed all the steps carefully and othervise I dont see any error, but the template is not applied.
Anything I can do to connect to PnP successfully? Seems Microsoft just keep changing things so we always have to fight our way to be able to create site templates…
Thanks
Robert
Hi Robert,
When you get that error, the PnP.PowerShell module has not yet been installed for the Function app. If you added the module reference to the requirements file and then restarted the app, the module installation should begin. Sometimes you also need to run the function once to trigger the module installation. Installing the module(s) can sometimes take a really long time, so be patient. If you still get the error after waiting for half an hour or so, you can check out the situation via Kudu (Advanced Tools blade on Azure portal, then select a Debug console, and navigate to the ManagedDependencies folder).
Laura
Worked, thank you!
Glad to hear! 🙂
Laura
Hi Laura,
I appreciate the step-through. Very helpful. I have completed the steps twice to ensure I didn’t miss anything; however, I have ran my process and everything runs to the point of the FA trigger and it is erroring out on the authentication and cert. The error is a general error and I am not finding anything to direct me how to fix it. The error is “ERROR: AADSTS90002: Tenant ‘XXXXXX.sharepoint.com’ not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant.” Any guidance is appreciated. Thanks.
Hi Anthony,
The tenant name should be given in the format XXXXXX.onmicrosoft.com, not XXXXXX.sharepoint.com.
I hope this helps!
Laura