Skip to content

resource provisioning for M365 - made as easy as possible

License

Notifications You must be signed in to change notification settings

tmaestrini/easyProvisioning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

easyProvisioning – resource provisioning made as easy as possible

easyProvisioning offers a quick and easy way to provision site resources and a range of predefined assets in SharePoint Online (SPO). By defining a YAML template that contains all the information about the desired site resources, this method is both a straightforward approach to provisioning and also serves as documentation for the provisioned resources.

Under the hood, the provisioning engine is powered by the PnP.Powershell module and the PnPProvisioning concept. This provides us with a powerful toolset for setting up and managing all resources that need to be provisioned within M365 – driven by the power of PowerShell 😃.

Give it a try – I'm sure you will like it! 💪

Note

👉 For now, SPO is currently the only targeted service in M365 – but other services will follow asap.
Any contributors are welcome! 🙌

Dependencies

PowerShell PnP.PowerShell Microsoft.Graph

Applies to

Get your own free development tenant by subscribing to Microsoft 365 developer program

Solution

Solution Author(s)
easyProvisioning Tobias Maestrini @tmaestrini

Version history

Version Date Comments
1.0 November, 2023 Initial release
1.1 March, 2024 Updated provisioning schema for tenants
1.1.1 April, 2024 Add reference templates integration

Disclaimer

THIS CODE IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.


Minimal path to awesome

Installation

Install-Module -Name powershell-yaml -Scope CurrentUser
Install-Module -Name PnP.PowerShell -RequiredVersion 2.4.0 -Scope CurrentUser

Generate SharePoint structure

The resource provisioning generally follows the structure that is defined by the YAML structure within the template file (for reference: see section below). Simply start the provisioning process by importing the Provisioning.psm1 module and then calling the Start-Provisioning command as follows:

Import-Module .\src\Provisioning.psm1 -Force
Start-Provisioning -TemplateName "standard.yml" #-KeepConnectionsAlive

# To test the "consistency" of your template, simply call:
Test-Template -TemplateName "standard.yml"

The resource provisioning process is idempotent; each defined resource or setting is only provisioned once. You can start the provisioning process as many times you want without expecting any side effects!

Sync hub navigation from template site

You can sync any given hub navigation to any given site. Although the provisioning process for creating the SharePoint structure includes this (if defined), the function can also be executed again in a separate step.

The hub navigation synchronization copies an existing navigation structure from a relative site url (e.g. /sites/IntranetHome) that is specified in the template attribute CopyHubNavigation in the template file (see section below) and applies it to the target site (that is the site where the template attribute CopyHubNavigation was defined). Just make sure that the target site has a proper hub navigation and the relative path to the site url exists. This is really nice – it leads to a consistent navigation experience on all intranet sites!

Simply start the provisioning process by importing the Provisioning.psm1 module (if not already done so!) and then calling the Sync-Hubnavigation command as follows:

Import-Module .\src\Provisioning.psm1 -Force
Sync-Hubnavigation -TemplateName "standard.yml"

Note

The resource provisioning process is idempotent; each defined resource or setting is only provisioned once. You can start the sync process as many times you want without expecting any side effects!

Create a folder structure for a given site

You can define any folder structure in a given site. While running the regular provisioning setup (see paragraph «Generate SharePoint structure»), a given folder structure will be created along its optional Folder definition in the site scope.

Warning

Before provisioning any specific folder structure, a connection to the according site (target site) must be established. The site (target site) must match the site identifier in the content structure of the tenant template!

Although the provisioning process for creating the SharePoint structure includes this (if defined), the function can also be executed within a desired site in a separate step. Make sure to establish a connection to the destination site before you start the generation of the folder structure:

$siteConn = Connect-PnPOnline "https://yourtenant.sharepoint.com/sites/site" -Interactive -ReturnConnection
Import-Module .\src\Provisioning.psm1 -Force
Add-FolderStructureToLibrary -TemplateName "standard.yml" -siteConnection $siteConn

Note

The resource provisioning process is idempotent; each defined folder is only provisioned once. You can run the process as many times you want without expecting any side effects!

The template file (YAML)

To get your resources provisioned, just write down the structure in one single YAML file. All you have to do is to make sure that your YAML file implements the following schema.

Assuming the file is referenced as standard.yml (in the usage example above) and exists under the path /templates), a new SharePoint Communication Site named One will be created:

Tenant: <your tenant name> # name can be set according to your needs

# Sharepoint Specific Settings
SharePoint:
  TenantId: <the SharePoint tenant name> # the name of the Sharepoint tenant (e.g. contoso)
  AdminUpn: <the admin's UPN>

  Structure:
    # creates a new hub site ('Hub' is the site type; can also be set to 'Site') with the title 'One'
    - Hub: One # declare the type either as 'Site' or 'Hub'
        # the relative site url
      Url: /sites/TestOne
      # set to 'Communication', 'Team' or 'SPOTeam' (Modern Team Site w/o M365 Group)
      Type: Communication
      # only needed when type is 'Communication'; set it to 'Blank', 'Showcase' or 'Topic'
      Template: Blank
      Site Admins: # optional
      Lcid: 1031  # optional; set to 1031 by default
      HomepageLayout: Article # optional; set to 'Home' (default), 'Article' or 'SingleWebPartAppPage'
      ConnectedHubsite:  # optional; set the relative path to the parent hub site
      CopyHubNavigation: # optional; set the relative path to the hub site from where the navigation structure will be copied
      Provisioning Template:  # optional; reference any PnP Site Template from your local machine
      Provisioning Parameters: # optional; gives the possibility to set list template parameters that are used within the provisioning template (must be defined!)
        # ParameterName1: ParameterOne
        # ParameterName2: ParameterTwo
      SharingCapability: # optional; set to 'Disabled', 'ExistingExternalUserSharingOnly', 'ExternalUserAndGuestSharing' or 'ExternalUserSharingOnly'
      DisableCompanyWideSharingLinks: # optional; set to False (default) or True

      # the content structure (aka assets) of your site
      Content:
        # creates a standard document library with title 'One'
        - DocumentLibrary: One
          Url: /One # optional; the desired relative url of the document library in the site. If not set, the 'title' of the library will be taken.
          OnQuickLaunch: True # optional; places a link in the quick launch navigation
          Provisioning Template:  # optional; reference any PnP List Template (and only list template!) from your local machine
          Folders: # optional; generates a folder structure (items are folder names)
            - Alpha:
                - Alpha.One:
                    - Alpha.One.1 [Demo 1]:
                        - One
                - Alpha.Two
                - Alpha.Three
            - Beta:
                - Beta.One
                - Beta.Two:
                    - Beta.Two.1
                    - Beta.Two.2
                    - Beta.Two.3
            - Gamma

        # creates a standard document library with title 'Three'
        - List: Three
          Url: /Three # optional; the desired relative url of the document library in the site. If not set, the 'title' of the library will be taken.
          OnQuickLaunch: True
          Provisioning Template: # reference any PnP List Template (and only list template!) from your local machine (e.g. tenants/templates/pnp-list-template.xml)
          Provisioning Parameters: # optional; gives the possibility to set list template parameters that are used within the provisioning template (must be defined!)
            # ParameterNameA: ParameterOne
            # ParameterNameB: ParameterTwo
        # creates a calendar with title 'Four'
        - EventsList: Four
          Url: /Four # optional; the desired relative url of the document library in the site. If not set, the 'title' of the library will be taken.
        # creates a specific media library to store media assets with title 'Five'
        - MediaLibrary: Five
          Url: /Five # optional; the desired relative url of the document library in the site. If not set, the 'title' of the library will be taken.
          OnQuickLaunch: True
          Folders: # optional; generates a folder structure (items are folder names)
            - Alpha:
                - Alpha.One:
                    - Alpha.One.1 [Demo 1]:
                        - One
                - Alpha.Two
                - Alpha.Three
            - Beta:
                - Beta.One
                - Beta.Two:
                    - Beta.Two.1
                    - Beta.Two.2
                    - Beta.Two.3
            - Gamma
    # define the next sites (as many sites as you like)...
    # - Two: ...

Support for PnP Provisioning templates

Within your provisioning template (.yml), you can reference any PnP template according to your needs. By defining an optional attribute Provisioning Template, you can pass a reference to a valid PnP List Template or to a PnP site template, which resides inside your project structure. Optionally, provisioning parameters can be passed to the provisioning template by defining objects within the Provisioning Parameters attribute.

Example: pass aprovisioning template to a site (PnP site template):

SharePoint:
  # <content intentionally omitted>
  Structure:
    # creates a new hub site ('Hub' is the site type; can also be set to 'Site') with the title 'One'
    - Hub: One # declare the type either as 'Site' or 'Hub'
      # <content intentionally omitted>
      Provisioning Template: tenants/templates/pnp-site-template.xml # reference any PnP Site Template from your local machine
      Provisioning Parameters: # (optional) pass template parameters that are used within the provisioning template (must be defined!)
        ParameterNameA: ParameterOne
        ParameterNameB: ParameterTwo

Example: pass a provisioning template to a list or document library (PnP site template, especially with <List> definition):

SharePoint:
  # <content intentionally omitted>
  Structure:
    # <content intentionally omitted>
    Content:
      - DocumentLibrary: MyDocumentLibrary
        Provisioning Template: tenants/templates/pnp-doclib-template.xml # reference any PnP Site Template from your local machine
        Provisioning Parameters: # (optional) pass template parameters that are used within the provisioning template (must be defined!)
          ParameterNameA: ParameterOne
          ParameterNameB: ParameterTwo
      - List: MyCustomList
        OnQuickLaunch: True
        Provisioning Template: tenants/templates/pnp-list-template.xml # reference any PnP Site Template from your local machine
        Provisioning Parameters: # (optional) pass list template parameters that are used within the provisioning template (must be defined!)
          ParameterNameA: ParameterOne
          ParameterNameB: ParameterTwo

An according list template could like the following:

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2021/03/ProvisioningSchema">
  <pnp:Preferences Generator="PnP.Framework, Version=1.9.1.0, Culture=neutral, PublicKeyToken=0d501f89f11b748c" />
  <pnp:Templates ID="CONTAINER-TEMPLATE-20383B78FCE94E9D95B574CF586C0576">
    <pnp:ProvisioningTemplate ID="TEMPLATE-20383B78FCE94E9D95B574CF586C0576" Version="1" Scope="RootSite">
      <pnp:Lists>
        <pnp:ListInstance Title="Tickets" Description="" DocumentTemplate="" TemplateType="100" Url="Lists/Tickets" EnableVersioning="true" MinorVersionLimit="0" MaxVersionLimit="50" DraftVersionVisibility="0" TemplateFeatureID="00bfea71-de22-43b2-a848-c05709900100" EnableFolderCreation="false" ImageUrl="/_layouts/15/images/itgen.gif?rev=47" IrmExpire="false" IrmReject="false" IsApplicationList="false" ValidationFormula="" ValidationMessage="">
          <pnp:ContentTypeBindings>
            <pnp:ContentTypeBinding ContentTypeID="0x01" Default="true" />
            <pnp:ContentTypeBinding ContentTypeID="0x0120" />
          </pnp:ContentTypeBindings>
          <pnp:Views>
            <View Name="{40AD4F1C-26B8-497E-82AD-55A718663465}" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" Type="HTML" DisplayName="All Items" Url="{site}/Lists/Tickets/AllItems.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/generic.png?rev=47">
              <Query>
                <OrderBy>
                  <FieldRef Name="DateReported" Ascending="FALSE" />
                </OrderBy>
              </Query>
              <ViewFields>
                <FieldRef Name="LinkTitle" />
                <FieldRef Name="Description" />
                <FieldRef Name="Priority" />
                <FieldRef Name="Status" />
                <FieldRef Name="Assignedto0" />
                <FieldRef Name="DateReported" />
                <FieldRef Name="IssueSource" />
                <FieldRef Name="Images" />
                <FieldRef Name="Attachments" />
                <FieldRef Name="Issueloggedby" />
              </ViewFields>
              <RowLimit Paged="TRUE">30</RowLimit>
              <JSLink>clienttemplates.js</JSLink>
              <CustomFormatter>
                <![CDATA[{
    "additionalRowClass": {
        "operator": ":",
            "operands": [{
                "operator": "==",
                "operands": [{
                    "operator": "toLowerCase",
                    "operands": ["[$Status]"]
                }, {
                    "operator": "toLowerCase",
                    "operands": ["Blocked"]
                }]
            }, "sp-css-backgroundColor-errorBackground", ""]
    },
    "rowClassTemplateId": "ConditionalView"
}]]>
              </CustomFormatter>
              <ViewType2>GRIDFIXED</ViewType2>
            </View>
            <View Name="{F2E42828-F4D4-4F6D-85C5-FEAB2AD53CE2}" Type="HTML" DisplayName="Issues grouped by priority" Url="{site}/Lists/Tickets/Issues grouped by priority.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/generic.png?rev=47">
              <Query>
                <OrderBy>
                  <FieldRef Name="ID" />
                </OrderBy>
                <GroupBy>
                  <FieldRef Name="Priority" Ascending="TRUE" />
                </GroupBy>
              </Query>
              <ViewFields>
                <FieldRef Name="LinkTitle" />
                <FieldRef Name="Description" />
                <FieldRef Name="Priority" />
                <FieldRef Name="Status" />
                <FieldRef Name="Assignedto0" />
                <FieldRef Name="DateReported" />
                <FieldRef Name="IssueSource" />
                <FieldRef Name="Images" />
                <FieldRef Name="Attachments" />
              </ViewFields>
              <RowLimit Paged="TRUE">30</RowLimit>
              <JSLink>clienttemplates.js</JSLink>
            </View>
            <View Name="{CE428A11-5432-4DCE-89B9-66930D8AECFB}" Type="HTML" DisplayName="Issues grouped by status" Url="{site}/Lists/Tickets/Issues grouped by status.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/generic.png?rev=47">
              <Query>
                <OrderBy>
                  <FieldRef Name="ID" />
                </OrderBy>
                <GroupBy>
                  <FieldRef Name="Status" Ascending="TRUE" />
                </GroupBy>
              </Query>
              <ViewFields>
                <FieldRef Name="LinkTitle" />
                <FieldRef Name="Description" />
                <FieldRef Name="Priority" />
                <FieldRef Name="Status" />
                <FieldRef Name="Assignedto0" />
                <FieldRef Name="DateReported" />
                <FieldRef Name="IssueSource" />
                <FieldRef Name="Images" />
                <FieldRef Name="Attachments" />
              </ViewFields>
              <RowLimit Paged="TRUE">30</RowLimit>
              <JSLink>clienttemplates.js</JSLink>
            </View>
            <View Name="{5CD0E9E4-7765-4C75-B658-395D9F0959F8}" Type="HTML" DisplayName="Issues grouped by person assigned to" Url="{site}/Lists/Tickets/Issues grouped by person assigned to.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/generic.png?rev=47">
              <Query>
                <GroupBy>
                  <FieldRef Name="Assignedto0" Ascending="TRUE" />
                </GroupBy>
              </Query>
              <ViewFields>
                <FieldRef Name="LinkTitle" />
                <FieldRef Name="Description" />
                <FieldRef Name="Priority" />
                <FieldRef Name="Status" />
                <FieldRef Name="Assignedto0" />
                <FieldRef Name="DateReported" />
                <FieldRef Name="IssueSource" />
                <FieldRef Name="Images" />
                <FieldRef Name="Attachments" />
              </ViewFields>
              <RowLimit Paged="TRUE">30</RowLimit>
              <JSLink>clienttemplates.js</JSLink>
            </View>
          </pnp:Views>
          <pnp:Fields>
          <!-- intentionally omitted -->
          </pnp:Fields>
          <pnp:FieldRefs>
            <pnp:FieldRef ID="76d13cd2-1bae-45a5-8b74-545b87b65037" Name="_ColorTag" DisplayName="Color Tag" />
          </pnp:FieldRefs>
        </pnp:ListInstance>
      </pnp:Lists>
    </pnp:ProvisioningTemplate>
  </pnp:Templates>
</pnp:Provisioning>

Integrate sub structures into your tenant template

In order to structure or modularize your tenant structure, you can divide a basic structure into a main definition template and an extension template. Therefore, you simply have to integrate the extension template in the main definition template by referencing the relative path (from the project's root) within the contains attribute.

Note

The extension template is a fully functional tenant template file (.yml), which could work as a standalone template definition.

Your main definition template must look like this in the first definition rows:

Tenant: <your tenant name> # name can be set according to your needs

# optional; set the relative path (to the root) to another settings file which contains provisioning settings that should be applied to this site
Contains: tenants/templates/hr.yml

SharePoint:
  TenantId: <the SharePoint tenant name> # the name of the Sharepoint tenant (e.g. contoso)
  AdminUpn: <the admin's UPN>
# further structure omitted

The extension template file can contain any structure along your needs.

Note

To make use of the extension template, make sure that the name of the SharePoint tenant (TenantId) in the extension template matches exactly the tenant's name in the main definition template.

Example structure of the extension template:

Tenant: <your tenant name> # name can be set according to your needs

# Sharepoint Specific Settings
SharePoint:
  TenantId: <the SharePoint tenant name> # the name of the Sharepoint tenant (e.g. contoso) must match the tenant's name in the main definition template
  AdminUpn: <the admin's UPN>

  Structure:
    - Site: HR One
      Url: /sites/HR-One
      Type: SPOTeam
      Template: Blank
      ConnectedHubsite: /sites/HR

    - Site: HR Two
      Url: /sites/HR-Two
      Type: SPOTeam
      Template: Blank
      ConnectedHubsite: /sites/HR

    - Site: HR Three
      Url: /sites/HR-Three
      Type: SPOTeam
      Template: Blank
      ConnectedHubsite: /sites/HR

About

resource provisioning for M365 - made as easy as possible

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published