In the first part we saw how to export and import a SharePoint site template in a modular way using PnP Framework, splitting it into multiple XML files (Fields.xml, List_*.xml, Navigation.xml etc.).
Now let’s see how to wrap this process inside an Azure Function so that it can be triggered programmatically — for example, via Power Automate HTTP action.
Automating Modular SharePoint Provisioning with Azure Functions
In the first part we saw how to export and import a SharePoint site template in a modular way using PnP Framework, splitting it into multiple XML files (Fields.xml, List_*.xml, Navigation.xml etc.).
Now let’s see how to wrap this process inside an Azure Function so that it can be triggered programmatically — for example, via Power Automate HTTP action.
1. Why Azure Functions?
Using an Azure Function to manage provisioning has several benefits:
- Serverless → no need to run scripts locally or on a VM
- Trigger flexibility → can be called via HTTP, scheduled, or from Power Automate
- Scalable → handles multiple site exports/imports in parallel
- Centralized logs → monitor execution through Application Insights
2. Function Structure
We will use a .NET Isolated Azure Function with:
- HTTP Trigger
- Parameters in the request body (e.g.,
siteUrl,mode,action) - Call our modular export/import methods
- Return structured JSON with results and logs
3. Example Function: CloneSPSite
Function Code
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using PnP.Framework.Provisioning.ObjectHandlers;
using PnP.Framework.Provisioning.Providers.Xml;
namespace ModularPnPFunction
{
public class CloneSPSite
{
private readonly ILogger<CloneSPSite> _logger;
public CloneSPSite(ILogger<CloneSPSite> logger)
{
_logger = logger;
}
[Function("CloneSPSite")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "clonespsite")] HttpRequest req)
{
try
{
string body = await new StreamReader(req.Body).ReadToEndAsync();
var request = System.Text.Json.JsonSerializer.Deserialize<RequestModel>(body);
if (request == null || string.IsNullOrEmpty(request.SiteUrl) || string.IsNullOrEmpty(request.Action))
return new BadRequestObjectResult("Missing parameters: SiteUrl and Action are required.");
string logPath = @"c:\pnp\import_log.txt";
using (var ctx = new AuthenticationManager()
.GetSharePointOnlineAuthenticatedContextTenant(request.SiteUrl, request.User, request.Password))
{
var provider = new XMLFileSystemTemplateProvider(@"c:\pnp", "");
if (request.Action.Equals("export", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("📤 Starting export...");
ExportHelper.ExportSite(ctx, provider, _logger);
return new OkObjectResult(new { success = true, message = "Export completed", path = logPath });
}
else if (request.Action.Equals("import", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("📥 Starting import...");
ImportHelper.ImportSite(ctx, provider, _logger);
return new OkObjectResult(new { success = true, message = "Import completed", path = logPath });
}
else
{
return new BadRequestObjectResult("Action must be 'export' or 'import'.");
}
}
}
catch (Exception ex)
{
_logger.LogError($"❌ Error: {ex.Message}");
return new ObjectResult(new { success = false, error = ex.Message }) { StatusCode = 500 };
}
}
}
public class RequestModel
{
public string SiteUrl { get; set; }
public string Action { get; set; } // "export" or "import"
public string User { get; set; }
public string Password { get; set; }
}
}
4. How to Call from Power Automate
- Create a Power Automate Flow
- Add an HTTP action with:
- Method:
POST - URL: Function URL (
https://<functionapp>.azurewebsites.net/api/clonespsite?code=<function-key>) - Body:
- Method:
{
"SiteUrl": "https://yourtenant.sharepoint.com/sites/TargetSite",
"Action": "export",
"User": "user@yourtenant.onmicrosoft.com",
"Password": "yourpassword"
}
- Flow runs → triggers Azure Function → provisioning executed.
5. Best Practices
- Authentication → don’t hardcode user/password. Use MSAL + Managed Identity in production.
- Timeouts → long provisioning tasks may exceed Function default timeout. Adjust
functionTimeoutinhost.json. - Storage → instead of
c:\pnp, store templates in Azure Blob Storage for scalability. - Logging → redirect logs to Application Insights for monitoring.
✅ Key Takeaways
| Area | Benefit |
|---|---|
| Modular export/import | Keeps provisioning clean and maintainable |
| Azure Function | Makes it callable via HTTP, Power Automate, or schedules |
| JSON Parameters | Simple integration with automation tools |
| Logs | Monitor both via console/file and Azure Insights |
| Scalability | Replace local disk with Blob Storage |
👉 With this setup you can automate site provisioning workflows in SharePoint Online using Azure Functions, PnP Framework, and Power Automate — keeping everything modular, reusable, and enterprise-ready.
