During my last project we were asked to create a fairly simple site template on Office 365. I typically cringe when I hear the word site definition / site template or web template. The reason goes way back to the beginning of my career with SharePoint. One of my first projects I had to upgrade a site definition from 2007 to 2010. I’m not going to complain too much but let’s just say that there was barely any documentation on how to accomplish this. For a n00b I struggled through it and I guess I completely it successfully.

Fast forward to today… the last time I created a web template for Office 365 I was able to build the site and then “Save as Template”. This worked because it was not a publishing site, but a team site. That template saved successfully by it created some monstrous WSP with tons of files, folders, feature and XML definitions. To make any changes to the template I had to scan through those XML definitions. The experience was not fun to say the least.

I was researching the deep and dark interwebs on “the best method to create site templates on Office 365”. The result of that research showed that the shift is towards remote site provisioning using either your client machine, a provider hosted add-in or another method like web job, remote timer job, Azure function. Basically anything capable of running remote client object model code to programmatically build out your site.

I chose to perform remote site creation via PnP-Powershell [See GitHub Site]. The PnP-Powershell team has been doing fantastic work. They currently a couple of ways to do remote site provisioning.

  1. provision from a live site (as a template)
  2. provision from a xml definition – this can be exported from a live site and saved for reuse

Unfortunately I wasn’t able to get either of those options to work for me because of a know issue that they are working on. [Apply-PnPProvisioningTemplate : Value cannot be null. Parameter name: stream #1010]. I decided that I was going to continue to use the PnP-Powershell commands to programmatically create the site instead of using some of their provisioning commandlets.

If your just getting started then I suggest checking out these following resources on remote provisioning

Getting Started

The first thing you’ll need to do is visit the GitHub site for PnP-PowerShell – https://github.com/SharePoint/PnP-PowerShell. You’ll need to make sure that you have the pre-requisites installed. You also need to install the PnP commandlets with the command below

Install-Module SharePointPnPPowerShellOnline -AllowClobber

In the following paragraphs I’m going to demonstrate the code that was ultimately used as the solution.

My code template required the following items:

  1. Lists
    1. Announcements
    2. Calendar
  2. Web Parts on Home Page
    1. Announcements
      1. Custom Display (using display template)
    2. Calendar
    3. Document Library

This should give you a basic idea of what we need to accomplish.

The Commands

Next up is the current set of commands that I ran to provision the new site.

#the root site collection where the subsites will be created
$url = "https://bandrdev.sharepoint.com"

#the parameters for my new web we are going to create
$siteTitle = "My New Site"
$siteUrl = "my-new-site"
$siteTemplate = "CMSPUBLISHING#0"

#get and save your O365 credentials
$cred = Get-Credential -Message "Enter SPO credentials"

#connect to the root site collection first
Connect-PnPOnline –Url $url –Credentials $cred

#create the new web site on the root site collection using the Publishing template and inherit the parent navigation
New-PnPWeb -Title $siteTitle -Url $siteUrl -Template $siteTemplate -InheritNavigation

#connect to the new web site using the stored credentials
Connect-PnPOnline –Url $url/$siteUrl –Credentials $cred

#create 2 new lists Calendar using Events template and Announcements using the Announcements template
New-PnPList -Title "Calendar" -Template "Events" -OnQuickLaunch
New-PnPList -Title "Announcements" -Template "Announcements" -OnQuickLaunch

#create a new view for the Announcements list called Rollup with a row limit of 4, a custom query to change the order, and a set of fields to display
Add-PnPView -List "Announcements" -Title "Rollup" -RowLimit 4 -Query "<OrderBy><FieldRef Name=""Created"" Ascending=""False""/></OrderBy>" -Fields @("Title","Body","Created")

#get the current web server relative url
$currentWebServerRelativeUrl = Get-PnPWeb | %{ $_.ServerRelativeUrl }
#get the current web home page for modification
$homePage = "$currentWebServerRelativeUrl/Pages/default.aspx" 

#read the web part xml of the custom view we created. This will then be imported to the home page
$announcementsWpXml = GetWebPartXml "$currentWebServerRelativeUrl/Lists/Announcements/Rollup.aspx"

#check out the homepage for editting
Set-PnPFileCheckedOut -Url $homePage

#take the announcements roll up view xml and add it as a web part to the homepage in the Top Column Zone first position
Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $announcementsWpXml -ZoneId "TopColumnZone" -ZoneIndex 1

#grab all the web parts off the home page
$allWebParts = Get-PnPWebPart -ServerRelativePageUrl $homePage 
#we know there is only 1 right now so pull that
$wp = Get-PnPWebPart -ServerRelativePageUrl $page -Identity $allWebParts[0].Id
#update the web part properties of the Announcements web part to use my custom JSLink file so that I can control the look of it
Set-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key "JSLink" -Value "~siteCollection/Style Library/jslink/team-announcements.js"
#update the web part properties of the Announcements web part to have no chrome
Set-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key "ChromeType" -Value 2

#get the view xml of the calendar
$calendarWpXml = GetWebPartXml "$currentWebServerRelativeUrl/Lists/Calendar/calendar.aspx"
#take the view xml of the calendar and add it as a web part to the home page in the Left Column Zone first position
Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $calendarWpXml -ZoneId "LeftColumnZone" -ZoneIndex 1

#get the view xml of the document library (this is on the site already as part of CMSPUBLISHING#0)
$documentsWpXml = GetWebPartXml "$currentWebServerRelativeUrl/Documents/Forms/AllItems.aspx"
#take the view xml of the document library and add it as a web part to the home page in the Right Column Zone first position
Add-PnPWebPartToWebPartPage -ServerRelativePageUrl $homePage -Xml $documentsWpXml -ZoneId "RightColumnZone" -ZoneIndex 1

#check in the home page
Set-PnPFileCheckedIn -Url $homePage -Comment "Provisioned by Powershell"

This worked for what we needed however I wanted to point out a few things

1. There is a problem is you run this in a script. If you run this as part of a .ps1 it seems to throw an error but doesn’t appear to cause an issue. I write the out as $null an continue. If you type it into the command line it doesn’t throw an error. [Related:Add-SPOView breaks View Menu #320]

Add-PnPView -List "Announcements" -Title "Rollup" -RowLimit 4 -Query "<OrderBy><FieldRef Name=""Created"" Ascending=""False""/></OrderBy>" -Fields @("Title","Body","Created") -ErrorAction SilentlyContinue > $null

2. Chrome Type Property – It’s not clearly listed anywhere and it took some playing around till I got this to work. I’m assuming since it accepted 2 that it’s the PartChromeType Enumeration (i added the red numbers in the picture below)

Set-PnPWebPartProperty -ServerRelativePageUrl $homePage -Identity $wp.Id -Key "ChromeType" -Value 2


In end the I was happy with the results. I found out shortly after speaking with a colleague, Josh Carlisle aka our provisioning guru, that this could be all nicely packaged up and put into an Azure function. This way that someone doesn’t have to run the code on their desktop. It could be implemented in a cloud way where you just need to trigger that function to run… possibly using Flow where you just enter the site creation information into a list and it fires away or a custom form. Bottom line is that I’m excited to really see the potential of the PnP initiatives. It has some glitches with some of the commandlets during the time that I tried it but I can see how it will become very powerful once it fully matures.

About the Author

Developer, Designer, Thinker, Problem Solver, Office Servers and Services MVP, & Collaboration Director @ SoHo Dragon.

View Articles