Provisioning teams in Microsoft Teams using the PnP Provisioning Engine

The PnP Provisioning Engine is an open source provisioning technology that was introduced more than five years ago and since then it has been really useful to provision the information architecture of a Site Collection in SharePoint Online and SharePoint on-premises (2013, 2016, 2019). The overall idea of the PnP Provisioning Engine is to use an object model for .NET Framework to provision artifacts in SharePoint. On top of the object model there is also a set of cmdlets for PowerShell, included in the PnP PowerShell library.

In 2019, we introduced the so-called tenant-level templates, which allow the creation of a hierarchy of Site Collections eventually connected to a SharePoint Hub. Moreover, with a tenant-level template you can provision users in Azure Active Directory, files and folders in OneDrive for Business, and teams in Microsoft Teams.

In this article you will learn what the potentials of the PnP Provisioning Engine are in the fields of provisioning resources in Microsoft Teams, within the context of Microsoft 365 and SharePoint Online.

Basics of Provisioning for Microsoft Teams

Let’s start with a very basic tenant-template that creates a team and which is illustrated in the following code excerpt.

Basic tenant-template that creates a team

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2020/02/ProvisioningSchema"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  Author="Paolo Pialorsi"
                  Generator="Human being"
                  Version="1.0"
                  Description="A sample Provisioning template to test Teams provisioning"
                  DisplayName="Teams Provisioning">

  <pnp:Preferences Author="PiaSys.com" Version="1.0" Generator="Human being">
    <pnp:Parameters>
      <pnp:Parameter Key="TeamTitle">Test Team</pnp:Parameter>
      <pnp:Parameter Key="TeamAlias">testteam</pnp:Parameter>
    </pnp:Parameters>
  </pnp:Preferences>

  <pnp:Teams>

    <pnp:Team DisplayName="{parameter:TeamTitle}"
		Description="This is a Team provisioned with PnP"
		Visibility="Private" Archived="false" MailNickname="{parameter:TeamAlias}">
      <pnp:FunSettings AllowGiphy="true" GiphyContentRating="Strict"
		AllowStickersAndMemes="true" AllowCustomMemes="true"/>
      <pnp:GuestSettings AllowCreateUpdateChannels="true" AllowDeleteChannels="false"/>
      <pnp:MembersSettings AllowCreateUpdateChannels="true" AllowDeleteChannels="false"
		AllowAddRemoveApps="true"
		AllowCreateUpdateRemoveConnectors="true"
		AllowCreateUpdateRemoveTabs="false" />
      <pnp:MessagingSettings AllowUserEditMessages="true" AllowUserDeleteMessages="true"
		AllowOwnerDeleteMessages="false"
		AllowTeamMentions="true"
		AllowChannelMentions="true"/>
      <pnp:Channels>
        <pnp:Channel DisplayName="General"
                     Description="General"
                     IsFavoriteByDefault="true">
          <pnp:Tabs>
		<pnp:Tab DisplayName="Wiki" TeamsAppId="com.microsoft.teamspace.tab.wiki"
			Remove="true" />
		<pnp:Tab DisplayName="OneNote"
			TeamsAppId="0d820ecd-def2-4297-adad-78056cde7c78" />
          </pnp:Tabs>
          <pnp:Messages>
            <pnp:Message>
              <![CDATA[ 
              {
                "body": {
                  "contentType": "html",
                  "content": "Hello World"
                },
                "subject": "Welcome to this channel!"
              }
              ]]>
            </pnp:Message>
          </pnp:Messages>
        </pnp:Channel>
      </pnp:Channels>
    </pnp:Team>

  </pnp:Teams>

</pnp:Provisioning>

As you can see a provisioning template is just a file in XML format, based on a reference XSD schema. At the time of writing, the current version of the schema is February 2020 (XML namespace: http://schemas.dev.office.com/PnP/2020/02/ProvisioningSchema). Here you can find further information about the PnP Provisioning Schema.

In the template you can define input parameters, which you can then provide as input arguments when you apply the template. In the <pnp:Teams /> element you can define one or more teams that you want to provision in Microsoft Teams. In the above code excerpt, we define a team with all the Fun, Guest, Members, and Messaging settings based on a set of boolean attributes, together with some team settings like the DisplayName, Description, Visibility, MailNickname, etc. Moreover, the template defines that the default “General” channel of the team will have an additional tab of type Microsoft OneNote, and will have the out of the box Wiki tab removed. The template also writes a welcome message in the “General” channel.

In order to apply the above template using PnP PowerShell you will simply need to execute the following script in any PowerShell console:

# Connect to the target tenant with specific user's identity via Credential Manager
Connect-PnPOnline "https://your-tenant.sharepoint.com/"

# Apply the tenant template
Apply-PnPTenantTemplate -Path .your-template.xml `
    -Parameters @{"TeamTitle"="PnPTeamDemo";"TeamAlias"="pnpteamdemo"}

The first cmdlet (Connect-PnPOnline) will let you connect to the target environment. You will need to provide the root URL of the SharePoint Online tenant of the Microsoft 365 subscription that you are targeting. The cmdlet will prompt you for credentials. In our example, in order to be able to provision a team in Microsoft Teams, you will need to provide credentials of a Global Tenant Admin account.

The second cmdlet (Apply-PnPTenantTemplate) will actually apply the template to the target tenant. The -Path argument accepts a local file path for the XML template file to apply, while the -Parameters argument accepts a collection of input arguments that should match those defined in the <pnp:Parameters /> element of the template.

A new team will be created in the target tenant, and the user running the script will automatically become an owner of the created team. You can also configure additional owners and members by leveraging an optional <pnp:Security /> element available as a child of the <pnp:Team /> element. The security settings for the team will be covered later in this article.

Advanced Teams Provisioning scenarions

You need to know that you can provision a team in Microsoft Teams not only using a specific set of user credentials, but you can also use an app-only security context. This means that you can create a back-end infrastructure like an Azure Function, an Azure WebJob, a REST API, etc. and provision teams in Microsoft Teams from there using an application identity, instead of a specific user identity. It is really simple and effective. You simply need to use a code excerpt like the following one in order to register an application in Azure Active Directory.

# Configure global variables
$tenant = "your-tenant.onmicrosoft.com"
$spoRoot = "https://your-tenant.sharepoint.com/"
$certificatePassword = "your-certificate-password" | ConvertTo-SecureString -Force -AsPlainText

# Initialize the AAD application to access Graph and SPO
$aadApp = Initialize-PnPPowerShellAuthentication `
    -ApplicationName "TeamsProvisioningApp" `
    -Tenant $tenant `
    -Store CurrentUser `
    -CommonName "TeamsProvisioning" `
    -CertificatePassword $certificatePassword `
    -ValidYears 2 `
    -OutPath C:temp

# Grab the app ClientID and certificate thumbprint
$clientId = $aadApp.AzureAppId
$thumbprint = $aadApp.'Certificate Thumbprint'

The Initialize-PnPPowerShellAuthentication cmdlet will register for you a new application in Azure Active Directory, will configure the application to authenticate as app-only with an X.509 Certificate and by default will configure the application to have the following permissions:

  • Microsoft Graph: ReadWrite.All, User.Read.All
  • SharePoint Online: FullControl.All, User.Read.All

You will have to authenticate with the credentials of a Tenant Global Admin account in order to be able to register the application, and you will have to grant the above permissions to the app. It will be all automatic, you will simply have to wait for the cmdlet to prompt you for admin credentials first and for the permissions grant secondly. Once you have executed the above code once, you can reuse the clientId and thumbprint returned by the Initialize-PnPPowerShellAuthentication cmdlet as many times as you like to connect to the target tenant and to apply a template with an app-only context. In the following excerpt, you can see a PowerShell script to provision a team with app-only context.

# Connect to the target tenant with app-only context
Connect-PnPOnline "https://your-tenant.sharepoint.com/" -ClientId $clientId `
	-Thumbprint $thumbprint -Tenant $tenant

# Apply the tenant template
Apply-PnPTenantTemplate -Path .your-template-for-app-only.xml `
    -Parameters @{"TeamTitle"="PnPTeamDemo";"TeamAlias"="pnpteamdemo"}

In the above scenario you will need to provide a slightly more complete provisioning template. In fact, while provisioning within an app-only context, you will have to explicitly provide at least one owner for the new team. Here is an updated provisioning template.

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2020/02/ProvisioningSchema"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  Author="Paolo Pialorsi"
                  Generator="Human being"
                  Version="1.0"
                  Description="A sample Provisioning template to test Teams provisioning"
                  DisplayName="Teams Provisioning">

  <pnp:Preferences Author="PiaSys.com" Version="1.0" Generator="Human being">
    <pnp:Parameters>
      <pnp:Parameter Key="TeamTitle">Test Team</pnp:Parameter>
      <pnp:Parameter Key="TeamAlias">testteam</pnp:Parameter>
    </pnp:Parameters>
  </pnp:Preferences>

  <pnp:Teams>

    <pnp:Team DisplayName="{parameter:TeamTitle}"
		Description="This is a Team provisioned with PnP"
		Visibility="Private" Archived="false" MailNickname="{parameter:TeamAlias}">
      <pnp:FunSettings AllowGiphy="true" GiphyContentRating="Strict"
		AllowStickersAndMemes="true" AllowCustomMemes="true"/>
      <pnp:GuestSettings AllowCreateUpdateChannels="true" AllowDeleteChannels="false"/>
      <pnp:MembersSettings AllowCreateUpdateChannels="true" AllowDeleteChannels="false"
		AllowAddRemoveApps="true"
		AllowCreateUpdateRemoveConnectors="true"
		AllowCreateUpdateRemoveTabs="false" />
      <pnp:MessagingSettings AllowUserEditMessages="true" AllowUserDeleteMessages="true"
		AllowOwnerDeleteMessages="false"
		AllowTeamMentions="true"
		AllowChannelMentions="true"/>
      <pnp:Security>
        <pnp:Owners ClearExistingItems="false">
          <pnp:User UserPrincipalName="owner@your-tenant.onmicrosoft.com" />
        </pnp:Owners>
      </pnp:Security>
      <pnp:Channels>
        <pnp:Channel DisplayName="General"
                     Description="General"
                     IsFavoriteByDefault="true">
          <pnp:Tabs>
		<pnp:Tab DisplayName="Wiki" TeamsAppId="com.microsoft.teamspace.tab.wiki"
			Remove="true" />
		<pnp:Tab DisplayName="OneNote"
			TeamsAppId="0d820ecd-def2-4297-adad-78056cde7c78" />
          </pnp:Tabs>
        </pnp:Channel>
        <pnp:Channel DisplayName="Private Channel"
                     Description="This is a private channel provisioned with PnP"
                     IsFavoriteByDefault="true" Private="true">
          <pnp:Tabs>
            <pnp:Tab DisplayName="Bing" TeamsAppId="com.microsoft.teamspace.tab.web">
              <pnp:Configuration EntityId=""
                                 ContentUrl="https://www.bing.com/"
                                 WebsiteUrl="https://www.bing.com/"
                                 RemoveUrl="" />
            </pnp:Tab>
            <pnp:Tab DisplayName="Wiki" TeamsAppId="com.microsoft.teamspace.tab.wiki"
			Remove="true" />
          </pnp:Tabs>
        </pnp:Channel>
      </pnp:Channels>
    </pnp:Team>

  </pnp:Teams>

</pnp:Provisioning>

In the above excerpt, you can see the <pnp:Security /> element through which you can configure one or more owners and members of a team. Moreover, when running in app-only context, you cannot provision messages in the channels, because the Microsoft Graph API to create messages does not support the app-only context.

In the above code excerpt you can also see that we provision a custom channel configured to be private through the Private=”true” attribute.  If you do that within a user’s account authentication context, the user provisioning the team will automatically become the only member of the private channel. If you provision a private channel within an app-only context, all the owners of the team will become members of the private channel. It is interesting to highlight that in the above example we also provision a custom web tab that will render, just for the sake of this example, the Bing search engine home page.

Provisioning Teams and SharePoint at once

One really interesting option that you should consider is the capability to provision, within a unique provisioning context and a unique provisioning template, both a SharePoint Online “modern” Team Site and a team connected to that site. In the following code excerpt, you can see an example of such a template.

<pnp:Provisioning xmlns:pnp="http://schemas.dev.office.com/PnP/2020/02/ProvisioningSchema"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  Author="Paolo Pialorsi"
                  Generator="Human being"
                  Version="1.0"
                  Description="A sample Provisioning template to test Teams provisioning"
                  DisplayName="Teams Provisioning">

  <pnp:Preferences Author="PiaSys.com" Version="1.0" Generator="Human being">
    <pnp:Parameters>
      <pnp:Parameter Key="TeamTitle">Test Team</pnp:Parameter>
      <pnp:Parameter Key="TeamAlias">testteam</pnp:Parameter>
      <pnp:Parameter Key="CompanyName">Contoso</pnp:Parameter>
    </pnp:Parameters>
  </pnp:Preferences>

  <pnp:Templates ID="SAMPLE-TEMPLATES">

    <pnp:ProvisioningTemplate ID="ProjectsTemplate" Version="1.0"
      BaseSiteTemplate="GROUP#0"
      DisplayName="Special Team Site"
      Description="This is a Special Team Site for custom provisioning"
      Scope="RootSite"
      TemplateCultureInfo="1040">

	<!--
	Omissis, for the sake of brevity …
	-->

    </pnp:ProvisioningTemplate>

  </pnp:Templates>

  <pnp:Sequence ID="SAMPLE-SEQUENCE">

    <pnp:SiteCollections>
      <pnp:SiteCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:type="pnp:TeamSite"
	ProvisioningId="TEAM.SITE.01"
	Title="{parameter:CompanyName} Projects"
	Alias="{parameter:TeamAlias}"
	DisplayName="{parameter:CompanyName} Projects"
	IsPublic="true"
	IsHubSite="true"
	Description="{parameter:CompanyName} Projects"
	Classification="MBI"
	Teamify="true"
	HideTeamify="true"
	Language="1033">
        <pnp:Templates>
          <pnp:ProvisioningTemplateReference ID="ProjectsTemplate" />
        </pnp:Templates>
      </pnp:SiteCollection>
    </pnp:SiteCollections>

  </pnp:Sequence>

  <pnp:Teams>

    <pnp:Team GroupId="{sequencesitegroupid:TEAM.SITE.01}"
	DisplayName="{parameter:TeamTitle}"
	Description="This is a Team provisioned with PnP"
              Visibility="Private" Archived="false" MailNickname="{parameter:TeamAlias}">

	<!--
	Omissis, for the sake of brevity …
	-->

      <pnp:Channels>
        <pnp:Channel DisplayName="General"
                     Description="General"
                     IsFavoriteByDefault="true">
          <pnp:Tabs>
            <pnp:Tab DisplayName="Wiki" TeamsAppId="com.microsoft.teamspace.tab.wiki" 
		Remove="true" />
            <pnp:Tab DisplayName="OneNote"
		TeamsAppId="0d820ecd-def2-4297-adad-78056cde7c78" />
            <pnp:Tab DisplayName="{parameter:CompanyName} Projects"
		TeamsAppId="com.microsoft.teamspace.tab.files.sharepoint">
              <pnp:Configuration EntityId=""
                                 ContentUrl="{sequencesiteurl:TEAM.SITE.01}/Projects"
                                 WebsiteUrl=""
                                 RemoveUrl="" />
            </pnp:Tab>
          </pnp:Tabs>
        </pnp:Channel>
      </pnp:Channels>
    </pnp:Team>

  </pnp:Teams>

</pnp:Provisioning>

In the <pnp:Sequence /> element we request the creation of a “modern” Team Site, which will be identified at template level through the ProvisioningId=”TEAM.SITE.01″ attribute. Moreover, the new SharePoint Site Collection will be enriched with the creation of a backing team in Microsoft Teams, thanks to the Teamify=”true” attribute. The unique ID of the site will become useful in the definition of the team provisioning. In fact, the <pnp:Team /> element, instead of creating a new team, will simply update the team with Group ID equal to the ID of the Microsoft 365 group connected to the just created site. We define this behavior setting the GroupId=”{sequencesitegroupid:TEAM.SITE.01}” attribute of the <pnp:Team /> element. In fact, the value of the GroupId attribute instructs the PnP Provisioning Engine to replace that value with the actual ID of the Microsoft 365 Group backing the “modern” team site, which is also the ID of the team in Microsoft Teams.

Using this technique, we can provision the structure of the team that “teamifies” the “modern” Team site and we can also create, just for the sake of this example, a custom tab in the “General” channel of the team to show the content of a document library provisioned in the “modern” Team site.

Wrap up

In this article you have had a quick overview of the potential of provisioning teams with the PnP Provisioning Engine, but you can explore further the engine and become a black belt in automated provisioning solutions. If you are interested in this topic, you can eventually consider subscribing to the “PiaSys Tech Bites” YouTube channel (https://piasys.com/TechBites), and watch a bunch of videos that cover the provisioning topic.

Last but not least, at the following URL (https://github.com/PiaSys/Conferences-Samples/tree/master/Teams-Provisioning) you can find the code samples that are covered in this article. Feel free to play with them and remember that “sharing is caring”!

Collaboration means two-way communication!

Did you like this article? Are you interested in this topic? Would you like to share your knowledge and experience with the authors and other readers? Our #communityrocks group on Facebook is bringing everyone involved together: authors, editors and readers. Join us in sharing knowledge, asking questions and making suggestions. We would like to hear from you! 

Join us on Facebook

Related Articles

Submit a Comment

Your email address will not be published. Required fields are marked *