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,
Great walkthrough. I am a novice with these topics, but have a diverse programming background. I am having two problems…
The 1st, trying to run the downgrade script… I’m getting an error at the Set-AzResource line…
Line |
7 | Set-AzResource -ResourceId “/subscriptions/$subscriptionId/resourceGr …
| ~~~~~~~~~~~~~~
| The ‘Set-AzResource’ command was found in the module ‘Az.Resources’, but the module could not be loaded due to
| the following error: [Assembly with same name is already loaded] For more information, run ‘Import-Module
| Az.Resources’.
7.2
Also, why run scripts to create the certificate rather than use the Azure UI? I am attempting to upload the .pfx and Azure is throwing errors stating it is not recognized. Is this part of the downgrade issue you mentioned?
Thank you.
Anthony
Hi Anthony,
Did you try running the “Import-Module Az.Resources” command to find out more information on the issue?
You can create the certificate through other means than the script, if you prefer.
Laura
Tip for those following this in 2023 – Azure functions now default to PowerShell v7.2 which is incompatible with PnP.PowerShell currently, so you need to downgrade function to use 7.0 (so back to a deprecated version).
Github error ticket and script at bottom to downgrade function – https://github.com/pnp/powershell/issues/2136
Hopefully I can save someone a bunch of time 🙂
Thanks Rick! This information has now been included in the blog post. 🙂
Laura
Hi Laura… Thanks for posting this. It was very helpful. Question, when I’m using an XML file as the template I’m getting an error that an logo does not exists: “ERROR: File assets/__footerlogo__logo_contoso_footer.png does not exist”. I already put the assets folder relative to the xml file in the library. Do I need to locally download the file in the Azure function. I noticed it works fine with .pnp template since the files are packaged up in that format. Thanks again.
If I remember correctly, you need to download it to the Azure function files and then upload it on the new site.
Laura
Hi Laura
Great walkthrough but just one question: I wonder why you download the PnP XML file. Didn’t Invoke-PnPSiteTemplate allow for using the URL to the .pnp file at the time?
Hi Laura,
I just tried implmenting this and it would appear that it no longer works with .Net Core 7.2 now that .Net Core 7.0 is deprecated. 🙁
I can also see that PnP.PowerShell have updated the way they connect in the latest version 1.12.0 with regards to Managed Identities??? It would be great to hear your thoughts with regards to the latest changes to Azure Functions and the above guide. 🙂
Thanks for sharing!
I’ve added a script for downgrading the Azure function version to the blog post. That sorts out the compatibility issue. This is a workaround until the actual fix makes its way to the production version of PnP.PowerShell (currently the fix is available only in the preview version).
The managed identity authentication currently works only for commands that use the Microsoft Graph API. For those commands that use the SharePoint REST API, you still need to use a certificate. However, authentication for the SP REST API with a managed identity is on its way and currently available on the preview version (nightly build) of PnP.PowerShell. I’ve personally not verified if it truly works yet. Using a certificate is a safe bet for authenticating against both of the APIs.
Laura