Intro to Site Scripts and Site Designs with a Simple SharePoint Modern Site provisioning

Microsoft announced Site Scripts and Site Designs in late 2017 which became available for Targeted release in Jan 2018, and released to general use recently. It is a quick way to allow users to create custom modern sites, without using any scripting hacks. Hence, in this blog we will go through the steps of Site Scripts and Site design for a Simple SharePoint Modern Site Creation.

Before we get into detailed steps, lets’ get a brief overview about Site Designs and Site Scripts.

Site designs: Site designs are like a template. They can be used each time a new site is created to apply a consistent set of actions. You create site designs and register them in SharePoint to one of the modern template sites: the team site, or communication site. A site design can run multiple scripts. The script IDs are passed in an array, and they will run in the order listed.

Site Scripts: Site script is custom JSON based script that runs when a site design is selected. The site scripts detail the provisioning items such as creating new lists or applying a theme. When the actions in the scripts are completed, SharePoint displays detailed results of those actions in a progress pane. They can even call flow trigger that is essential for site creation.

Software Prerequisites:

  1. PowerShell 3.0 or above
  2. SharePoint Online Management Shell
  3. Notepad or any notes editor for JSON creation – I prefer Notepad++
  4. Windows System to run PowerShell
  5. And a must – SharePoint Tenant 🙂

Provisioning Process Overview:

The Provisioning process is divided into 4 steps

1. Create a Site Script using JSON template to call actions to be run after a Modern Site is created.
2. Add the Script to your tenant
3. Create a Site Design and add the Site Script to the design. Associate the Site Design to Modern Site Templates – Team Site template is 64 and Communication Site Template is 68
4. Create a Modern Site from SharePoint landing page
5. Wait for the Site Script to apply changes and refresh your site

Quick and Easy right!? Now lets’ get to the “how to”.

Steps

1. JSON Template: We will need to create a JSON template that will have the declarations of site resources that will be provisioned after the site is created. For more details, here is a link to Microsoft docs. The brief schema is below.

{
"$schema": "schema.json",
"actions": [
  ... [one or more verb   actions] ...
  ],
"bindata": { },
"version": 1
};

For our blog here, we will use the below schema where we are creating a custom list with few columns.


{
"$schema": "schema.json",
"actions": [
{
"verb": "applyTheme",
"themeName": "Custom Dark Blue custom"
},
{
"verb": "createSPList",
"listName": "Training Registration",
"templateType": 100,
"subactions": [
{
"verb": "SetDescription",
"description": "List of Registered Training"
},
{
"verb": "addSPField",
"fieldType": "User",
"displayName": "Trainer Name",
"isRequired": true,
"addToDefaultView": true
},
{
"verb": "addSPField",
"fieldType": "Number",
"displayName": "Students Strength",
"addToDefaultView": true,
"isRequired": true
},
{
"verb": "addSPField",
"fieldType": "DateTime",
"displayName": "Training Date",
"addToDefaultView": true,
"isRequired": true
},
{
"verb": "addSPField",
"fieldType": "Note",
"displayName": "Notes",
"isRequired": false
}
]
}
],
"bindata": { },
"version": 1
}

2. Site Script: Add the above site script to your tenant using PowerShell. The below code with return the Site Script GUID. Copy that GUID and will be used later

Get-Content '[ JSON Script location ]' -Raw | Add-SPOSiteScript -Title “[ Title of the script ]

3. Site Design: After the Site Script is added, create the Site Design from the Site Script to be added to the dropdown menu options for creating the site.

Add-SPOSiteDesign -Title “[ Site design title ]” -WebTemplate "64"  -SiteScripts “[ script GUID from above step ]”  -Description "[ Description ]"

4. Create a Modern Site: After the Site Design is registered, you could see the design while creating a site as shown below

ModernTeamSiteWIthcustom

5. Click on the Manual Refresh button as per screenshot after the site upgrade process is complete.

SiteScriptFinish

When ready, the final Team site will look like the screenshot below after provisioning is complete.

CustomTeamSiteWithScriptResult

In this blog, we came to know about Site Script, Site design and how to use them to provision modern team sites.

How to make cool modern SharePoint Intranets Part 4 – Custom components #makespintranetcoolagain

In the series to make Intranet cool again we have looked at the strategy, design and build (OOB) options in the prior blogs here. In this blog we will look at the options to customise the Intranet and best practices for the same.

With every intranet there are cases, we will need to customize the look and feel. We will need custom components to add to the functionality.

Since there are many aspects where we could add customizations to the Intranet, this blog could become very long. So to keep it of optimal length, I am just highlighting the high level scenarios for customisations. For detail information, either look for other blogs on my site, or please leave a note and will blog the details about them.

So here we go..

Webparts

We could create SPFx custom web parts for specific requirements for the business. The web parts could scale whole width of the page or section as per requirement. One of the other greater benefit of SPFx web parts is that they are inherently mobile compatible*. The challenge of SPFx is that the client functions run on the client browser so we will need to be sure to that it is light weight and browser compatible**.

For blogs regarding building webparts with SharePoint Framework, please check here.

* Note: When I say mobile compatibility, it means that the webpart as a whole will be able to scale to mobile layout but doesn’t necessarily mean the controls in the web part will scale to mobile layout. Hence the code to scale the controls needs to be added to the app.

** Note: The browser compatibility becomes a key consideration if the organisation’s browser standards still include older browsers such as IE 11. So check here to consider challenges and tips regarding this.

Full width webparts

Sometimes we are given requirements to have full width webparts on Intranet pages. Currently there is no support for full width web parts in section layouts, hence the best way to approach this is using the CSS extender webpart here.  In the CSS customiser web part we could provide the path to the custom css file (recommended to store in Site assets so all users will have access) and the webpart applies the css to the page. Easy !!

Provisioning and Branding

Provisioning and Branding are important components of Intranet.

Provisioning

Normally it is possible to start with a blank communication site and then customise it as per Intranet requirements but this process doesn’t provide a consistent pipeline for dev, test, to prod releases. Also this doesn’t allow to add/update items seamlessly in case of a change. Having a custom Provisioning process provides a consistent approach to test and implement changes for custom solutions. This is beneficial in many ways such as access isolation and solution validation prior release.

Tips for custom Provisioning process

1. Use Site Scripts for creating the site and implementing assets. For reference about this process, check here.

2. Site designs could be used to prepare the custom elements for the site.

3. Use PnP PowerShell to apply the scripts. For reference check here.

For other options to create sites using a custom provisioning process, click here.

Branding

Branding is another important component for the Intranet. Branding using OOB features is mostly covered in this blog, however sometimes there could be custom requirements for header or footer components. For adding header or footer components, we could use the Application customiser to populate them. Some of the common requirement for a customiser could be banners, company footer sections, contact info etc. For details of using application customiser, check the doc at here.

Note: SharePoint has some default footer components such as Feedback and like sections that’s could overlap with the custom footer sections, so plan for them accordingly.

 

Content Authoring and Site Management

Another most common requirement that we receive with Intranets is content management such as adding and removing the pages and content. Communication sites provide many OOB features to manage content, some of these are listed in this blog. But we have many custom scenarios that might need to be added. Some of the custom ones are listed below:

1. News approval / Content publishing – There is a OOB approach to promote a page to a News. This can be combined with the OOB Request sign off Flow to add approval flows but what about demotion of promoted pages. If that is needed, then check the blog here.

2. Custom metadata management – The default forms of SharePoint don’t support custom rules. There are only limited choices based on data type. With custom forms we could add our own business rules. This could be done through PowerApps or SPFx List customiser. For more information on SPFx customiser check here.

3. Site hierarchy / Content hierarchy – One of the legacy trend that many of the Intranet leads keep hanging on to is hierarchy of content. Tree hierarchy has been how content was organised earlier and provided a feasible approach for some aspects. Teams are still looking for the same functionality. Instead of walking down the Tree hierarchy path, in my opinion, we could use metadata hierarchy (term store) to provide the same approach. Then Highlighted content or custom SPFx web part could be used to find content.

4. Permissions – This is one important component of the Intranet. Managing permissions in scope of Intranet is challenging as everyone needs access to the intranet. This also needs to be updated with the new employee and exit process of the company. Hence it is advisable to use a Active Directory (AD) security group with all the users of the company added to it. New users/ Exiting users could be maintained using this AD group. For content autors and editors, these could be given contribute rights. For Intranet admins, they could be provide with Edit or Design rights depending on the access they need. The Site collection admins should be maintained with the IT support team, so the Intranet and all custom builds could be maintained properly.

Hub

Intranet can also be a Hub for information which allows it to collate information from various locations without replicating all the information in Intranet.  This providesd two main benefits – single source of truth and defined ownership. Most of the Hub components such as News, Search are OOB. Some of the custom requirements might be with automatic assocation of sites to the Hub and audience targeting. For more information on SharePoint hub sites, please check here.

Conclusion

In this blog we looked at some approaches for custom implementations and extending the Intranet using custom components. The custom web parts could be managed using the SPFx bulid and release cycle as mentioned here. The other components could also be managed in a similar fashion as per requirements. Again this blog calls out the high level scenario for custom components. Please let me know if there are any specific scenarios of interest.

Happy Coding !!

Deploy and Add SPFx webparts to Modern Pages using OfficeDevPnP CSOM library

In the previous blog here, we looked at how to install apps on a SharePoint site. With SharePoint and Office Dev PnP CSOM, we could also add web parts to Modern Pages, both out of the box (OOB) web parts and custom web parts. For out of box web parts, refer to Chris O’Brien article here , where he has provided steps and also the web part IDs for the OOB webparts which is really helpful.

In this blog, we will look at steps to add a custom web part and set it properties.

For the below code, we will use OfficeDevPnP CSOM library. The high level steps for implementation are below:

  1. Find the page where to add the web part. For creating a modern page programmatically, refer to this link here.
  2. Find if the web part is added to the page, if not then add it using web part ID. We will read the web part ID from a JSON class where we have kept the details of the custom web part
  3. After the web part is added, then set the web part properties using JSON schema of the properties using NewtonSoft.Json.
Note: If the web part app is just installed, then it might take time for 
the web part to be ready for use. 
Hence put a wait state till the web part is loaded for use.

The below code has the steps for adding the web part programmatically using CSOM


private string appJsonInfo;
private AppDeclaration Apps;
// A class to hold JSON converted objects for Apps
private class AppDeclaration
{
public string appName;
public string appId;
public string clientsideId;
}
// Declare and create a Json Object for all Apps
public void DeclareAppIds
{
appJsonInfo = "[{appId: \"xxxxx-xxxx-xxxx-xxxx-xxxxxxxx\", appName: \"testApp\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx\"}, " +
"{appId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx\", appName: \"testApp1\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxxx-xxxxxx\"}, " +
"{appId: \"xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx\", appName: \"testApp2\", clientsideId: \"xxxxxx-xxxx-xxxx-xxxxx-xxxxxx\"}]";
Apps = JsonConvert.DeserializeObject<List<AppDeclaration>>(appJsonInfo);
}
public void AddWebPartsToPage(string pageName)
{
OfficeDevPnP.Core.AuthenticationManager am = new OfficeDevPnP.Core.AuthenticationManager();
using (var cc = am.GetSharePointOnlineAuthenticatedContextTenant(<site url>, UserName, SecurePass))
{
log.Info("Finding and Adding Webpart to page");
ClientSidePage page = ClientSidePage.Load(cc, pageName);
ClientSideComponent clientSideComponent = null;
AppDeclaration WP = Apps.Find(app => app.appName.Contains(<WPSearchString>));
bool controlPresent = false;
bool done = false;
int count = 0;
do
{
try
{
ClientSideComponent[] clientSideComponents = (ClientSideComponent[])page.AvailableClientSideComponents();
foreach (ClientSideComponent csc in clientSideComponents)
{
if (csc.Id.ToLower().Contains(WP.clientsideId))
{
clientSideComponent = csc;
continue;
}
}
foreach (var control in page.Controls)
{
ClientSideWebPart cpWP = control as ClientSideWebPart;
if (cpWP != null && cpWP.SpControlData.WebPartId.ToString() == WP.clientsideId.ToString())
{
controlPresent = true;
done = true;
}
}
if (!controlPresent)
{
ClientSideWebPart WebPart = new ClientSideWebPart(clientSideComponent);
JToken activeValueToken = true;
// Find the web part configuration string from the web part file or code debugging
string propertyJSONString = String.Format("[{{<WP Configuration string>}}]", <parameters>);
JToken propertyTermToken = JToken.Parse(propertyJSONString);
WebPart.Properties.Add("showOnlyActive", activeValueToken);
CanvasSection section = new CanvasSection(page, CanvasSectionTemplate.OneColumn, page.Sections.Count+1);
page.Sections.Add(section);
page.Save();
page.AddControl(WebPart, section.Columns[0]);
page.Save();
page.Publish();
done = true;
controlPresent = true;
}
}
catch(Exception ex)
{
log.Info("Catched exception while adding Capex web part.. Trying again" + ex.Message);
count++;
}
} while (!done && count <=5);
}
}

Conclusion

The above process could be used to add web part programmatically onto a page and set it’s properties.

Happy Coding !!

Findings on Office 365 Groups to use AD groups

In one of the recent projects, we were exploring an approach to manage security access to Office 365 groups through AD security groups during custom provisioning proccess for private Modern team sites and Office 365 groups.

In this blog, I will share some of the observations, findings during the project, and also some workarounds for the security model.

Security Model

The new convention of Unified groups with Office 365 Groups, Modern Team Sites and Teams (with Groups) sets up permissions in Azure AD with SharePoint and Exchange groups components of Office Groups. They can also be found from the admin center of Office 365 tenancy.

All security is managed via Unified Groups in Modern Team Sites and Office 365 groups. These allow access to use the group features such as Conversations, Sharing, Outlook client integration etc.

Observations

1. When a Office 365 Group or Modern Team is created, an AD unified group is created with both members and owners added into it.

2. All Owners of the Unified Group are also added as Members of the Office 365 Group. This is kind of mandatory.

3. Unified Groups are special AD Groups and not similar to AD security groups or Exchange groups.

4. Provisioning of Unified Groups and syncing across Exchange and SharePoint takes some time (about 15-30 min) as per my observation.

Limitations

1. Since Unified Groups cannot have nested security groups, it is not possible to add AD security groups in Modern Team Sites and Office 365 Groups to Unified groups.

2. Any changes to access in Office 365 Groups or Modern Teams take time to sync across. So if you change permission give it about 5-10 min to reflect across SharePoint, Outlook and Active Directory

Workarounds

The workaround approach would be to continue add security groups to SharePoint Groups which allows the members of the security group to access the SharePoint sites. It provides the option to join the Office 365 if interested. When requested the user is added to the Office 365 unified group and get the Office 365 groups functionality for private team sites.

This approach works for the SharePoint security access and doesn’t work much for Exchange part.

 

Automation and Creation of Office 365 groups using Flow, Microsoft Graph and Azure Function – Part 2

In the Part 1 blog here, we discussed an approach for the Group creation process and important considerations for provisioning groups. In this blog, we will look at getting a Graph App ID and App secret for invoking the graph service and then implementation of the group provisioning process.

MS Graph App Set up

Before we start creating groups we will need to set up a Graph App that will be used to create the group in the Office 365 tenancy. The details are in this blog here on how to create a Microsoft Graph app.

Regarding the permissions, below are the settings that are necessary to allow creating groups through the graph service.

GroupApp_Rights

Creating a Group

As discussed in Part 1 here, below are the broad level steps for automating group creation using a SharePoint inventory list, Microsoft Flow and Azure Function

1. Create a SharePoint list, with the metadata necessary for Group and SharePoint assets provisioning

We can use a SharePoint list to act as a trigger to create groups with the custom metadata necessary for provisioning the groups such as Owners and metadata necessary for creating site assets for SharePoint sites. As a best practice, I recommend you create multiple master lists to manage the details separately if there are too many to manage. In our case, we have created three separate lists for managing the Group details.

  1. Group details and metadata
  2. Owners and Team Members List
  3. Site Assets configuration list

2. Create a Microsoft flow. The flow will validate a new or existing group and pick the unique Group Alias from the list which will allow us to find the group if it exists.

The flow will act as a trigger to start the provisioning process and call the Azure function passing the appropriate metadata as shown below. The flow also allows error handling scenarios as described in the Part 1 blog here.

Note: The GroupAlias is the unique name of the Group and is not necessarily the SharePoint URL. For example in the case where a group was created and subsequently deleted, the unique alias could be used again but the Site URL will be different (unless cleared from the SharePoint recycle bin).

Group_FlowAzureFunctionCall
3. Create the Group in an Azure Function using SharePoint Online CSOM. In order to create a group, we will need to authenticate to the Graph service using the Graph App created earlier. For authenticating the app through Azure AD, please install the NuGet Package for Microsoft.IdentityModel.Clients.ActiveDirectory.

After authenticating, we will create the group using the UnifiedGroup Utility provided through the SharePoint Online CSOM.

Below is a quick snapshot of the code. Note the inclusion of Graph module of the OfficeDevPnP class.


using Microsoft.IdentityModel.Clients.ActiveDirectory;
using OfficeDevPnP.Core;
using OfficeDevPnP.Core.Framework.Graph;
using OfficeDevPnP.Core.Pages;
const string authString = "https://login.microsoftonline.com/<tenant domain>/";
const string clientId = "<Graph App ID>";
const string clientSecret = "<Graph App Secret>";
var authenticationContext = new AuthenticationContext(authString, false);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resourceId, clientCred);
string token = authenticationResult.AccessToken;
var groupNew = UnifiedGroupsUtility.CreateUnifiedGroup(<projectUniqueAlias>, <SharePoint Site Title>,
<URL>, token, owners: <Owners email Array>, members: <Owners email Array>,
groupLogo: <logo url>, isPrivate: <boolean>, retryCount: <numberofretries>);

Note: One important bit to note is that, in the above code owners and members email array is the same. If the owners and members email array differ, then the group provisioning delays significantly. Also, it is important to keep the other parameters same as during creation in the below method because it might reset the other properties to default otherwise. For eg. if isPrivate is not set, then the group becomes public.


UnifiedGroupsUtility.UpdateUnifiedGroup(group.GroupId, token, groupLogo: <logo url>,
members:<Members email array>, isPrivate: <boolean>, retryCount: <numberofretries>);

4. After the group is created, we can fetch the details of the group as below.


var group = UnifiedGroupsUtility.ListUnifiedGroups(token, mailNickname: url).Where(result => result.MailNickname.ToLower().Equals(alias.ToLower())).First();
// We received a group entity containing information about the group
string groupurl = group.SiteUrl;
string groupId = group.GroupId;
string mailNickName = group.MailNickname;

5. The group provisioning generally takes about 2-3 mins to provision. However, if there are multiple hits, then one request might override the second request causing the group creation to fail. In such cases, we can sequence the Azure Functions to run by modifying the host.json file. A quick blog covering this can be found here.

Provisioning SharePoint Assets in Azure Function after Group Creation

1. For provisioning of the SharePoint assets, we might have to wait for the Office 365 AD sync to finish granting access to the Admin account.

Sometimes, the AD sync process takes much longer, so to grant direct access to the SharePoint Site Collection using tenant admin, we could use the below code. Recommendation: Only proceed with the below code approach if the access fails for more than few mins.

Note: As a best practice, I would recommend using a Service Account when working on the SharePoint Site. We could also use an App as suggested in the Site Scripting blog here.


const string spTenantUrl = "https://<tenantname>-admin.sharepoint.com/&quot;;
using (var contextTenant = new ClientContext(spTenantUrl))
{
contextTenant.Credentials = new SharePointOnlineCredentials(userName, secpass);
Tenant tSP = new Tenant(contextTenant);
tSP.SetSiteAdmin(groupurl, userName, true);
contextTenant.ExecuteQuery();
}

2. Once you have access, you can use the normal SharePoint CSOM to do the activities that are pertaining to SharePoint asset provisioning such as Libraries, Site Pages content, Lists, etc.

3. After you’re done, you can return the success from the Azure function as below.

Note: Use HttpStatusCode.Accepted instead of HttpStatusCode.Error in case there is error handling in the Flow or else Flow will trigger another instance of the flow when the Azure Function fails.


return processComplete
? req.CreateResponse(HttpStatusCode.OK, "Success")
: req.CreateResponse(HttpStatusCode.Accepted, "Error");

Conclusion:

Above we saw how we can have a SharePoint Inventory list and create groups using Flow and Azure Functions. For a quick reference, below are the links to the other related blogs.

Blog Part 1 – Automation and Creation of Office 365 groups approach

Graph App Creation Blog: How to create a Microsoft Graph App

Azure Functions: Sequencing calls in Azure Functions