How to build a simple provisioning application using the PnP provisioning engine

This is a sample that I used to demonstrate the provisioning engine during the SSUG (Swedish SharePoint User Group) in Gothenburg at the 3rd of June 2015 (slides can be found here). It shows how simple it is utilize the power of the PnP framework to build an application that you can use to provision webs or even sitecollections.

The solution assumes that you have a web on a sitecollection that you want to use as a template for a newly created web. In the demo I just used a list with a field added to the list and had this field added to the view. The engine is capable of much more, but that is out of scope for this article. If you are interested, go read the schema definition over at https://github.com/OfficeDev/PnP-Provisioning-Schema.

Step 1 - Get context

In the application we will require the user to enter it's credentials manually each time.

class Program  
{
    public static class SecurePasswordFetcher
    {
        public static SecureString GetPassword()
        {
            Console.Write("Password: ");
            SecureString pwd = new SecureString();
            while (true)
            {
                ConsoleKeyInfo i = Console.ReadKey(true);
                if (i.Key == ConsoleKey.Enter)
                {
                    Console.WriteLine("\n");
                    break;
                }
                else if (i.Key == ConsoleKey.Backspace)
                {
                    if (pwd.Length > 0)
                    {
                        pwd.RemoveAt(pwd.Length - 1);
                        Console.Write("\b \b");
                    }
                }
                else
                {
                    pwd.AppendChar(i.KeyChar);
                    Console.Write("*");
                }
            }
            return pwd;
        }

    }

    static void Main(string[] args)
    {

        Console.Write("Site url: ");
        string tenant = Console.ReadLine();

        Console.Write("User: ");
        string username = Console.ReadLine();

        var password = SecurePasswordFetcher.GetPassword();

        Console.WriteLine(
           "Getting connection for '{1}' to '{0}'", username, tenant
        );

        var credentials = new SharePointOnlineCredentials(
                              username, password
        );

        using (ClientContext ctx = new ClientContext(tenant))
        {
            ctx.Credentials = credentials;

        }

        Console.ReadKey();
    }
}

Step 2 - Add PnP nuget package

Next we need to get the CSOM assemblies and the PnP assemblies (which include the provisioning engine). Luckily we can do this just by adding the OfficeDevPnP.Core NuGet package.

Step 3 - Save template to var

Now we probably want to test out our solution.

To use the code below we need to include references to the provisioning framework which can be done by adding the required using statements.

using OfficeDevPnP.Core.Framework.Provisioning;  
using OfficeDevPnP.Core.Framework.Provisioning.Model;  

Now get template the template from target site

Console.Write("Get template from web: ");  
string sourceWebUrl = Console.ReadLine();

var sourceWeb = ctx.Site.OpenWeb(sourceWebUrl);  
ctx.Load(sourceWeb);  
ctx.ExecuteQuery();

Console.WriteLine("Getting template...");  
ProvisioningTemplate template = sourceWeb.GetProvisioningTemplate();  

This is a good time to pause and inspect the template object that we get back from the GetProvisioningTemplate() method.

Step 4 - Create new target web

Being as productive and smart as we are we are using the CreateWeb extension method from the PnP library to add a new web.

Console.Write("Apply template to new web with name: ");  
string targetWebUrl = Console.ReadLine();

Console.WriteLine("Creating subsite");  
var newWeb = ctx.Site.RootWeb.CreateWeb(  
             new OfficeDevPnP.Core.Entities.SiteEntity()
             {
                 Title = targetWebUrl,
                 Url = targetWebUrl,
                 Template = "STS#0",
             }
);

Step 5 - Apply template with progress delegate

Again we want to add a reference, this time to the ObjectHandlers from the provisioning engine.

using OfficeDevPnP.Core.Framework.Provisioning.ObjectHandlers;  

The Create ProvisioningTemplateApplyingInformation is nice enoguh to accept a delegate that will let us track the progress from the actual provisioning. So let's create this.

var applyingInformation = new ProvisioningTemplateApplyingInformation();  
applyingInformation.ProgressDelegate = (message, step, total) =>  
{
    Console.WriteLine("{0}/{1} Provisioning {2}", step, total, message);
};

Now apply the template we fetched earlier to the new web.

newWeb.ApplyProvisioningTemplate(template, applyingInformation);

Console.WriteLine("Finished applying template");  

Step 6 - (Optional) Save template as XML

The engine comes with a few different kind of providers. Being able to write your own/adapt fetched templates is really powerful, let's see what the engine will output.

Add a reference to the XML provider.

using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;  

Save the fetched template to disk using the provider.

Console.WriteLine("Saving template using XML provider");  
XMLFileSystemTemplateProvider provider =  
     new XMLFileSystemTemplateProvider(@"c:\temp\ssug", "");
string templateName = "ssug.xml";  
provider.SaveAs(template, templateName);

Console.WriteLine("Saved template to disk.");  

Now if you open that file up it will contain a lot of things that are redundant, don't fear though. The upcoming June release should handle this better.

Step 8 - (Optional) Inspect propertybag

Seeing as governance is very important to most organizations the engine will add some metadata to the propertybag of the web where we applied the template. I highly encourage you to see what it adds as it will no doubt trigger many ideas in your head about solutions you can build that might be useful.

Console.WriteLine("Look what the engine left behind!");  
Console.Write("_PnP_ProvisioningTemplateId: ");

Console.WriteLine(  
    newWeb.GetPropertyBagValueString("_PnP_ProvisioningTemplateId", "")
);
Console.Write("_PnP_ProvisioningTemplateInfo: ");

Console.WriteLine(  
    newWeb.GetPropertyBagValueString("_PnP_ProvisioningTemplateInfo", "")
);

Thats it. You have now built a functioning provisioning engine in just a few minutes.

Every respectable organization has it's own engine. Hopefully you will see the value in having 1 engine that can be used across organizations and can be improved by the whole community.

All according to the PnP motto:

Sharing is caring

As always, please leave and ideas or suggestions in the comments.