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

  1. Create a Power Automate Flow
  2. Add an HTTP action with:
    • Method: POST
    • URL: Function URL (https://<functionapp>.azurewebsites.net/api/clonespsite?code=<function-key>)
    • Body:
{
  "SiteUrl": "https://yourtenant.sharepoint.com/sites/TargetSite",
  "Action": "export",
  "User": "user@yourtenant.onmicrosoft.com",
  "Password": "yourpassword"
}

  1. 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 functionTimeout in host.json.
  • Storage → instead of c:\pnp, store templates in Azure Blob Storage for scalability.
  • Logging → redirect logs to Application Insights for monitoring.

✅ Key Takeaways

AreaBenefit
Modular export/importKeeps provisioning clean and maintainable
Azure FunctionMakes it callable via HTTP, Power Automate, or schedules
JSON ParametersSimple integration with automation tools
LogsMonitor both via console/file and Azure Insights
ScalabilityReplace 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.

Edvaldo Guimrães Filho Avatar

Published by