Use the Full Page Canvas in modern SharePoint Online sites

A common requirement in the past with classic SharePoint solutions was to create start pages utilizing the full canvas of the page. In classic SharePoint this could easily be accomplished by adding a few lines of CSS in a Page Layout.

For modern pages we don’t have Page Layouts anymore, and to accomplish the same result a SPFx Extention can be used. This is more spesific a Application Customizer registering one simple CSS override in the top placeholder of the page.

This solution will work with both Communication and Team Sites, but is recommended to be used with Communication Sites. This template will give us full control of the page canvas below the Office 365 suite bar. Using it on a Team Site will not make much sense as we have a different layout with a left navigation bar etc.

Common usage scenarios

  • Intranet front page
  • Global start page
  • Full page apps hosted in SharePoint Online

Example of communication site before the app is added

example-communicationsite-before

Example of communication site after the app is added

example-communicationsite-after

The solution

I have created an example solution for this that can be found at Github:

https://github.com/aflyen/spfx-extension-fullpagecanvas

Create a Communication Site from code in SharePoint Online using PowerShell

Lately Microsoft released the new  and long awaited modern site template for publishing sites in SharePoint Online (Office 365). The site templates is named “Communication Site” and is the second template released after the modern “Team Site”. This site template can be created if you have permissions from the SharePoint Home page using the “Create a site” form:

cpcomsite1

This approach is fine if you have permissions to create sites and are happy with the default setup. For my case this is almost never the case. Often the tenants I work with have restricted who can create new sites/groups, and also requires additional configuration after it has been created. This can be accomplished for the new “Team site” template, so I was curious how this can be done for the Communication site.

At the time of writing Microsoft have not released any documentation on how to do this, so this was done by re-creating the steps of the “Create a site” wizard (found by using Fiddler). I assume and hope that we sooner or later will find official documentation from Microsoft on how to do this. They might provide us with a bit more elegant way to use their API’s. But if you are familiar with using general REST API’s, this is pretty straight forward as soon you get authenticated with SharePoint.

UPDATE (30.10.2017): This documentation have now been released, and this method is now a supported approach to provision new Communication Sites.

PowerShell script for creating a Communication site

This script requires the “SharePoint Online Client Components” to be installed.

# Configuration
$TenantUrl = "https://contoso.sharepoint.com"
$Username = "danj@contoso.com"
$Password = "Password1"
$SiteTitle = "My Communication Site 1"
$SiteUrl = "https://contoso.sharepoint.com/sites/mycomsite1" # Note this URL must be available (check with "/_api/GroupSiteManager/GetValidSiteUrlFromAlias")
$SiteTemplate = "" # "Topic" => leave empty (default), "Showcase" => "6142d2a0-63a5-4ba0-aede-d9fefca2c767" and "Blank" => "f6cc5403-0d63-442e-96c0-285923709ffc"
# Communication site creation request
$RequestBody = '{"request":{"__metadata":{"type":"SP.Publishing.PublishingSiteCreationRequest"},"Title":"' + $SiteTitle + '","Url":"' + $SiteUrl + '","Description":"","Classification":"","SiteDesignId":"' + $SiteTemplate + '","AllowFileSharingForGuestUsers":false}}'
if ($SiteTemplate -eq "")
{
$RequestBody = '{"request":{"__metadata":{"type":"SP.Publishing.PublishingSiteCreationRequest"},"Title":"' + $SiteTitle + '","Url":"' + $SiteUrl + '","Description":"","Classification":"","AllowFileSharingForGuestUsers":false}}'
}
# Get a user based context for SharePoint (app credentials not supported for this approach)
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($TenantUrl)
$Context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, $(ConvertTo-SecureString AsPlainText $Password Force))
$Context.ExecuteQuery()
# Get url, cookie and forms digest for authentication
$RequestUrl = "$($TenantUrl)/_api/sitepages/publishingsite/create"
$AuthenticationCookie = $Context.Credentials.GetAuthenticationCookie($TenantUrl, $true)
$FormsDigest = $Context.GetFormDigestDirect()
$WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$WebSession.Credentials = $Context.Credentials
$WebSession.Cookies.SetCookies($TenantUrl, $AuthenticationCookie)
$Headers = @{
'X-RequestDigest' = $FormsDigest.DigestValue;
'accept' = 'application/json;odata=verbose';
'content-type' = 'application/json;odata=verbose' }
# Call REST API to create new site
$Result = Invoke-RestMethod Method Post WebSession $WebSession Headers $Headers ContentType "application/json;odata=verbose;charset=utf-8" Body $RequestBody Uri $RequestUrl UseDefaultCredentials
$Context.Dispose()
# Site has been created
Write-Output "New site created at: $($Result.d.Create.SiteUrl)"

Summary

With the modern sites Microsoft has created several new REST based API’s used in their own wizards. These API’s can be used as long as you have a user authenticated context.

This code demonstrates how you can create the new Communication Site from code. After the site have been created you are free to connect to the site and apply customizations using in example the PnP PowerShell framework.

Create list from custom template in SharePoint with PowerShell

This examples shows how to create new lists from custom list templates in SharePoint using PowerShell and CSOM. Custom list templates are STP-files uploaded to the List Template Gallery on the site. This should work both on-premises (2013/2016) and Online (Office 365).

#region Configuration
$Url = "https://TENANTID.sharepoint.com" # URL of the site
$ListTemplateInternalName = "CorpMenu.stp" # Change this with your own list template
$ListName = "Menu" # Change this with the name of the list to be created
#endregion
#region Create list from template
Connect-PnPOnline Url $Url
$Context = Get-PnPContext
$Web = $Context.Site.RootWeb
$ListTemplates = $Context.Site.GetCustomListTemplates($Web)
$Context.Load($Web)
$Context.Load($ListTemplates)
ExecutePnPQuery
$ListTemplate = $ListTemplates | where { $_.InternalName -eq $ListTemplateInternalName }
if ($ListTemplate -eq $null)
{
Throw [System.Exception] "Template not found"
}
$ListCreation = New-Object Microsoft.SharePoint.Client.ListCreationInformation
$ListCreation.Title = $ListName
$ListCreation.ListTemplate = $ListTemplate
$Web.Lists.Add($ListCreation)
ExecutePnPQuery
Disconnect-PnPOnline
#endregion

Note: This example uses commands from the Office Dev PnP PowerShell library, ex. “Connect-PnPContext”. I recommend using this library for working this PowerShell and SharePoint (both Online and On-Premises).

Enable ratings in SharePoint with PowerShell and CSOM

In document libraries it is possible to enable ratings, both likes and average score (1-5). In this article I will cover how to enable “Likes” on the Pages library in a publishing site.

When configuring libraries manually, this is enabled from the “Rating settings” in the library:

enableratings

But when we follow the remote provisioning pattern to create new sites by using PowerShell and CSOM, there is currently no available function in the API for this. This pattern is the recommenced approach when working with SharePoint Online or SharePoint 2013/2016 without access to server side PowerShell or code.

To understand what is required to programmatic enable ratings I had to, as always with SharePoint, inspect the code behind with a reflection tool like DotPeek.

How to enable likes ratings

This show the procedure to enable ratings with Likes on a Pages library. To enable “Star rating” (1-5 average) this would be similar, but not covered in this post.

  1. Add the required fields to the library
    FieldName Guid
    AverageRating 5a14d1ab-1513-48c7-97b3-657a5ba6c742
    RatingCount b1996002-9167-45e5-a4df-b2c41c6723c7
    RatedBy 4D64B067-08C3-43DC-A87B-8B8E01673313
    Ratings 434F51FB-FFD2-4A0E-A03B-CA3131AC67BA
    LikesCount 6E4D832B-F610-41a8-B3E0-239608EFDA41
    LikedBy 2CDCD5EB-846D-4f4d-9AAF-73E8E73C7312
  2. Add “LikesCount” to default view
  3.  Add “Ratings_VotingExperience” to RootFolder’s property bag

Use PowerShell to enable ratings

Note: This function requires the Office Dev PnP PowerShell library to be installed and loaded in the current PowerShell session. I recommend using this library for working with PowerShell and SharePoint (both Online and On-Premises).

function Enable-CustomLikesRatingsOnLibrary {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[Microsoft.SharePoint.Client.Web]$Web,
[Parameter(Mandatory=$true)]
[String]$ListRelativeUrl
)
# Get required fields
$AverageRatingField = $Web.Fields.GetById([guid]"5a14d1ab-1513-48c7-97b3-657a5ba6c742") # AverageRating
$RatingCountField = $Web.Fields.GetById([guid]"b1996002-9167-45e5-a4df-b2c41c6723c7") # RatingCount
$RatedByField = $Web.Fields.GetById([guid]"4D64B067-08C3-43DC-A87B-8B8E01673313") # RatedBy
$RatingsField = $Web.Fields.GetById([guid]"434F51FB-FFD2-4A0E-A03B-CA3131AC67BA") # Ratings
$LikesCountField = $Web.Fields.GetById([guid]"6E4D832B-F610-41a8-B3E0-239608EFDA41") # LikesCount
$LikedByField = $Web.Fields.GetById([guid]"2CDCD5EB-846D-4f4d-9AAF-73E8E73C7312") # LikedBy
$Context.Load($AverageRatingField)
$Context.Load($RatingCountField)
$Context.Load($RatedByField)
$Context.Load($RatingsField)
$Context.Load($LikesCountField)
$Context.Load($LikedByField)
ExecuteSPOQuery
# Get the library for the site
$ServerRelativeUrl = $Web.ServerRelativeUrl
if ($ServerRelativeUrl -ne "/")
{
$ServerRelativeUrl = $ServerRelativeUrl + "/"
}
$List = $Web.GetList($ServerRelativeUrl + $ListRelativeUrl)
$ListRootFolder = $List.RootFolder
$ListRootFolderProperties = $ListRootFolder.Properties
$Context.Load($List)
$Context.Load($ListRootFolder)
$Context.Load($ListRootFolderProperties)
ExecuteSPOQuery
# Add fields
$List.Fields.Add($AverageRatingField)
$List.Fields.Add($RatingCountField)
$List.Fields.Add($RatedByField)
$List.Fields.Add($RatingsField)
$List.Fields.Add($LikesCountField)
$List.Fields.Add($LikedByField)
$List.Update()
ExecuteSPOQuery
# Update default view
$ListDefaltView = $List.DefaultView
$Context.Load($ListDefaltView)
ExecuteSPOQuery
$ListDefaltView.ViewFields.Add("LikesCount")
$ListDefaltView.Update()
ExecuteSPOQuery
# Update property bag
$ListRootFolderProperties["Ratings_VotingExperience"] = "Likes"
$ListRootFolder.Update()
ExecuteSPOQuery
}

Example: How to use this function with a site

# Connect to SharePoint Online site collection and get context
Connect-SPOnline Url "https://TENANTID.sharepoint.com/sites/news
$Context = Get-SPOContext
$Web = $Context.Site.RootWeb
$Context.Load($Web)
Execute-SPOQuery
# Enable likes ratings
Enable-CustomLikesRatingsOnLibrary -Web $Web -ListRelativeUrl "Pages"
# Close connection
Disconnect-SPOnline

Using this with subsites within the Site Collection would require some extension to the function, as the Fields always are loaded form the RootWeb, but the library reside in the subsite.

When we have run the “Enable-CustomLikesRatingsOnLibrary” function on the desired library, we can see that Likes now are available:

ratings-library

Summary

Using this function it is now possible to provision new sites with PowerShell and CSOM and enable likes rating on libraries.

Enabling scheduling on Publishing Pages in SharePoint Online (Office 365) using CSOM and PowerShell

Scheduling is easily enabled through the web interface when configuring the “Pages” library, but when deploying solutions using PowerShell, this must be automated as part of the configuration. Unfortunately, as many other sources also state, this is not directly supported in the Client Side API (CSOM).

The solution is to manually set up the Pages library the same way Microsoft does by adding two event receivers, changing some columns from hidden to visible and adding them to the default view.

Note: This example uses commands from the Office Dev PnP PowerShell library, ex. “Get-SPOContext”. I recommend using this library for working this PowerShell and SharePoint (both Online and On-Premises).

Function to enable Scheduling on a library on a given web site:

function Enable-CustomItemScheduling {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[Microsoft.SharePoint.Client.Web]$Web,
[Parameter(Mandatory=$true)]
[String]$PagesLibraryName
)
$List = $Web.Lists.GetByTitle($PagesLibraryName)
# Get function from: https://gist.github.com/aflyen/4a098b69b9faa43fd1a3
$ListParameter = Get-CustomLoadParameter Object $List PropertyName "EventReceivers"
$Web.Context.Load($List, $ListParameter)
ExecuteSPOQuery
# Prerequisites for using scheduling
$List.EnableModeration = $true
$List.EnableMinorVersions = $true
$List.Update()
$Assembly = "Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
$FullName = "Microsoft.SharePoint.Publishing.Internal.ScheduledItemEventReceiver"
$EventReceiverItemAddedExists = $false
$EventReceiverItemAddedType = [Microsoft.SharePoint.Client.EventReceiverType]::ItemAdded
$EventReceiverItemAddedName = "Item Added Event Handler For Scheduling"
$EventReceiverItemUpdatingExists = $false
$EventReceiverItemUpdatingType = [Microsoft.SharePoint.Client.EventReceiverType]::ItemUpdating
$EventRecieverItemUpdatingName = "Item Updating Event Handler For Scheduling"
# Check if the event receivers already exists
foreach ($EventReceiver in $List.EventReceivers)
{
if ($EventReceiver.ReceiverName -eq $EventReceiverItemAddedName)
{
$EventReceiverItemAddedExists = $true
}
elseif ($EventReceiver.ReceiverName -eq $EventRecieverItemUpdatingName)
{
$EventReceiverItemUpdatingExists = $true
}
}
# Add event receiver for ItemAdded
if ($EventReceiverItemAddedExists -ne $true)
{
$EventReceiverItemAdded = New-Object Microsoft.SharePoint.Client.EventReceiverDefinitionCreationInformation
$EventReceiverItemAdded.EventType = $EventReceiverItemAddedType
$EventReceiverItemAdded.ReceiverName = $EventReceiverItemAddedName
$EventReceiverItemAdded.ReceiverClass = $FullName
$EventReceiverItemAdded.ReceiverAssembly = $Assembly
$List.EventReceivers.Add($EventReceiverItemAdded) | Out-Null
}
# Add event receiver for ItemUpdating
if ($EventReceiverItemUpdatingExists -ne $true)
{
$EventReceiverItemUpdating = New-Object Microsoft.SharePoint.Client.EventReceiverDefinitionCreationInformation
$EventReceiverItemUpdating.EventType = $EventReceiverItemUpdatingType
$EventReceiverItemUpdating.ReceiverName = $EventRecieverItemUpdatingName
$EventReceiverItemUpdating.ReceiverClass = $FullName
$EventReceiverItemUpdating.ReceiverAssembly = $Assembly
$List.EventReceivers.Add($EventReceiverItemUpdating)| Out-Null
}
# Make fields for start and end date visible and add them to the default view
if ($EventReceiverItemAddedExists -ne $true -or $EventReceiverItemUpdatingExists -ne $true)
{
$FieldPublishingStartDateName = "PublishingStartDate"
$FieldPublishingExpirationDateName = "PublishingExpirationDate"
$FieldPublishingStartDate = $List.Fields.GetByInternalNameOrTitle($FieldPublishingStartDateName)
$FieldPublishingStartDate.Hidden = $false
$FieldPublishingStartDate.Update()
$FieldPublishingExpirationDate = $List.Fields.GetByInternalNameOrTitle($FieldPublishingExpirationDateName)
$FieldPublishingExpirationDate.Hidden = $false
$FieldPublishingExpirationDate.Update()
$ListDefaultView = $List.DefaultView
$ListDefaultView.ViewFields.Add($FieldPublishingStartDateName)
$ListDefaultView.ViewFields.Add($FieldPublishingExpirationDateName)
$ListDefaultView.Update()
$List.Update()
}
ExecuteSPOQuery
}

On line 11 an additional function is needed to create the parameters for the “Load” method:

function Get-CustomLoadParameter {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[Microsoft.SharePoint.Client.ClientObject]$Object,
[Parameter(Mandatory=$true)]
[string]$PropertyName
)
# Reference: http://sharepoint.stackexchange.com/questions/126221/spo-retrieve-hasuniqueroleassignements-property-using-powershell
$Context = $Object.Context
$Load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")
$Type = $Object.GetType()
$ClientLoad = $Load.MakeGenericMethod($Type)
$Parameter = [System.Linq.Expressions.Expression]::Parameter(($Type), $Type.Name)
$Expression = [System.Linq.Expressions.Expression]::Lambda(
[System.Linq.Expressions.Expression]::Convert(
[System.Linq.Expressions.Expression]::PropertyOrField($Parameter,$PropertyName),
[System.Object]
),
$($Parameter)
)
$ExpressionArray = [System.Array]::CreateInstance($Expression.GetType(), 1)
$ExpressionArray.SetValue($Expression, 0)
return $ExpressionArray
}

view raw
GetLoadParameter.ps1
hosted with ❤ by GitHub

How to enable scheduling:

$Context = Get-SPOContext
$Web = $Context.Site.RootWeb
$Context.Load($Web)
ExecuteSPOQuery
Enable-CustomItemScheduling Web $Web PagesLibraryName "Pages"

Exporting XsltListViewWebPart in SharePoint 2013

By default the XsltListViewWebPart does not support export from the web user interface. I often build solutions by configuring the web parts in the browers, exporting and adjusting the defintions and then deploying them with PowerShell. With a few lines of PowerShell this capability can also be enabled for the Xslt List View Web Part.

Before enabling the Export Mode on the webpart:

XsltListViewWebPart-before

After running the PowerShell script and enabling the Export mode:

XsltListViewWebPart-after

PowerShell script

# Configuration
$WebUrl = "http://intranet.contoso.com"
$PageUrl = "/Pages/default.aspx"
$WebPartName = "Documents"
# Enables export mode
$Web = Get-SPWeb $WebUrl
$File = $Web.GetFile($Web.ServerRelativeUrl + $PageUrl)
$File.CheckOut()
$Wpm = $File.GetLimitedWebPartManager([System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
$WebParts = $Wpm.WebParts
$WebPart = $WebParts | where { $_.Title -eq $WebPartName }
$WebPart.ExportMode = [System.Web.UI.WebControls.WebParts.WebPartExportMode]::All
$Wpm.SaveChanges($WebPart)
$File.CheckIn("", [Microsoft.SharePoint.SPCheckInType]::MajorCheckIn)

Add web parts from the gallery in SharePoint by code

For some reason I have never until now needed to add web parts to a page from the gallery using code. Why would I ever do this? The code runs inside a Feature Receiver, and is executed when creating new sites. The web part had been extended from a standard Content by Query Web Part (CBWP) with no code, and for that reason it was not possible to reference a unique class. So for this rare case I ended up with creating a utility function I thought was nice to share.

Function to add web parts from gallery


private static WebPart AddWebPartFromGallery(SPWeb web, SPLimitedWebPartManager wpm, string webPartFilename)
{
	string errorMessage;

	var query = new SPQuery
	{
		Query = String.Format(CultureInfo.CurrentCulture,
			"<Where><Eq><FieldRef Name='FileLeafRef'/><Value Type='File'>{0}</Value></Eq></Where>",
			webPartFilename)
	};

	var webPartGallery = web.IsRootWeb ? web.GetCatalog(SPListTemplateType.WebPartCatalog) : web.Site.RootWeb.GetCatalog(SPListTemplateType.WebPartCatalog);
	var webParts = webPartGallery.GetItems(query);
	XmlReader xmlReader = new XmlTextReader(webParts[0].File.OpenBinaryStream());
	var webPart = (WebPart) wpm.ImportWebPart(xmlReader, out errorMessage);

	if (!string.IsNullOrEmpty(errorMessage))
		throw new ApplicationException("Error importing Web Part (" + webPartFilename + "): " + errorMessage);

	return webPart;
}

How to use the function

This example code shows how to retreive the web part manager, checkout the file, add the web part and finally publish it again.


// Get web and its web part manager
var web = new SPWeb("http://intranet.contoso.com");
var pagesLibraryName = SPUtility.GetLocalizedString("$Resources:List_Pages_UrlName", "cmscore", web.Language);
var file = web.GetFile(web.Url + "/" + pagesLibraryName + "/" + "default.aspx");
file.CheckOut();
var wpm = web.GetLimitedWebPartManager(web.Url + "/" + pagesLibraryName + "/" + "default.aspx", PersonalizationScope.Shared);

// Get the web part from the sites gallery
var customWebPart = AddWebPartFromGallery(web, wpm, "CustomWebPart.webpart");

// Add the web part to the page and publish the page
wpm.AddWebPart(customWebPart, MyZoneId, 1);
wpm.SaveChanges(customWebPart);
file.CheckIn("Checked in by feature");
file.Publish("Published by feature");

Summary

Retrieving and adding web parts from the web part gallery by code is more complex than utilizing web parts you can access by their class. This post shows you how to retrieve the web part from the gallery and add it to a web part zone within a standard publishing page.