Supporting Structured Teams Creation

When I speak to customers about Microsoft 365 governance and administration, a common challenge for them is how to manage Microsoft Teams effectively. By default, any user can create a new Team in Microsoft 365. Without proper lifecycle management in place, the sheer number of Teams in a Microsoft 365 tenant often becomes hard to manage for tenant administrators.

To combat this “Teams spawl” issue, many larger tenants tend to prevent users from creating Teams (and SharePoint sites). Instead, these organizations provision Teams through their IT team. While creating Teams can be done manually by a member of the IT team, there are better ways busy tenant administrators can spend their time. I explored automating the creation of Teams in my article on Keeping Teams tidy through automation.

In this article, I show how PnP PowerShell helps improve automated Teams provisioning using templates and preset structures.

Note: The code used in this article is available on GitHub here.

Preparing

I’ve detailed the process of installing the PnP PowerShell Module and connecting to your tenant in the article, Manage Your SharePoint Online Environment with PnP PowerShell, so I won’t repeat that here, but if you need a refresher, go check out the previous article before continuing.

For the provisioning tasks in this article, the permissions required on the app registration are shown in Figure 1.

Figure 1: PnP App Registration permissions
Figure 1: PnP App Registration permissions

The next preparation step is to start the PowerShell script. To get started, we declare the variables that are to be modified by the user when running the script (DisplayName, Description and MailNickname, Client ID and Tenant Name). At the top of the script, add the following variables (and update with your chosen details). Note that the MailNickname of the Team is also used for the URL path of the backend SharePoint site.

$DisplayName = "Project - Project Spring"
$Description = "Project Spring is a project team for the Spring project"
$MailNickName = "Project-ProjectSpring"
$ClientID = "<ClientID>"
$tenantName = "<TenantName>"

Get the Sensitivity Label GUID

Sensitivity Labels for SharePoint Sites, Teams and Microsoft 365 Groups (Container labels) enforce preconfigured settings to Sites, Teams, and Groups. Container labels control guest access, visibility, privacy, external sharing, and shared channel functionality that is available to the container.

At a basic level, I recommend creating at least two container labels for your tenant, one for externally available Teams and one for internal-only Teams. Separating external and internal Teams is a great way to protect sensitive areas such as your finance or HR Teams from accidental external sharing.

To use a container label during Team provisioning, we first need the GUID of the labels. This can be retrieved using Security & Compliance PowerShell (available in the Exchange Online PowerShell Module) as shown in Figure 2.

Figure 2: Get the GUID of container labels
Figure 2: Get the GUID of container labels

Read More: For more information on container labeling and other controls to Include in a SharePoint/Teams Provisioning Solution, check out this article.

Define Team Settings

Teams have a number of settings that control what functionality is available for end users, for example, if Team members can delete or modify their channel messages. These settings are managed by the Team owner(s) after creation but can also be configured during the creation of the Team.  The below hashtable defines the configuration options available during Team creation:

$ProjectTeamSettings = @{
    AllowDeleteChannels = $false
    AllowAddRemoveApps = $true
    AllowChannelMentions = $true
    AllowCreateUpdateConnectors = $true
    AllowCustomMemes = $true
    AllowGiphy = $true
    AllowStickersAndMemes = $true
    AllowTeamMentions = $true
    GiphyContentRating = "Moderate"
    AllowUserEditMessages = $true
    AllowOwnerDeleteMessages = $false
    AllowCreateUpdateChannels = $true
    AllowCreateUpdateRemoveTabs = $true
    AllowUserdeleteMessages = $false
    AllowGuestCreateUpdateChannels = $false
    AllowGuestDeleteChannels = $false
    Visibility = "Private"
    SensitivityLabel = "258c86d0-ca73-4202-a8d5-d01dd9abaf80"
}

The sensitivity label GUID we returned earlier is also added to the settings here to apply the label during Team creation.

Provision the Team

With the settings defined, the next step is to create the initial Team object. The New-PnPTeamsTeam cmdlet takes the parameters defined above to create the Team with the settings defined:

$TeamObject = New-PnPTeamsTeam -DisplayName $DisplayName -Description $Description -MailNickName $MailNickName -AllowDeleteChannels $ProjectTeamSettings.AllowDeleteChannels -AllowAddRemoveApps $ProjectTeamSettings.AllowAddRemoveApps -AllowCreateUpdateChannels $ProjectTeamSettings.AllowCreateUpdateChannels -AllowCreateUpdateRemoveTabs $ProjectTeamSettings.AllowCreateUpdateRemoveTabs -AllowUserdeleteMessages $ProjectTeamSettings.AllowUserdeleteMessages -AllowUsereditMessages $ProjectTeamSettings.AllowUsereditMessages -AllowGuestCreateUpdateChannels $ProjectTeamSettings.AllowGuestCreateUpdateChannels -AllowGuestDeleteChannels $ProjectTeamSettings.AllowGuestDeleteChannels -Visibility $ProjectTeamSettings.visibility -SensitivityLabel $ProjectTeamSettings.SensitivityLabel

The user running the script will be added as the owner of the Team object and a quick check shows the settings defined have been applied (Figure 3).

Figure 3: Teams settings configuration
Figure 3: Teams settings configuration

Building the Channel Structure

Teams templates (as described in this article on using Teams template policies) are useful for building Teams structures and making them available to users. Teams Templates come with some limitations though, such as a lack of support for Private or Shared channels. As PnP PowerShell allows granular control of Team structures, we don’t need Team templates here. Instead, we build out our Teams structure in PowerShell.

Figure 4 shows a typical structure for a project-focused Team, where the important aspects of a project have a channel each and we then have a private channel for project budget and financials.

Figure 4: A sample Project Team structure
Figure 4: A sample Project Team structure

 Translating this structure into PowerShell, we create an array to store the channel information.

$ProjectTeamChannels = @(
    @{
        DisplayName = "Project Discussion"
        Description = "General discussion channel for team"
        ChannelType = "Standard"
    },
    @{
        DisplayName = "Project Plan"
        Description = "Channel for project plan"
        ChannelType = "Standard"
    },
    @{
        DisplayName = "Project Scheduling"
        Description = "Channel for discussing project scheduling"
        ChannelType = "Standard"
    },
    @{
        DisplayName = "Execution"
        Description = "Channel for execution of the project"
        ChannelType = "Standard"
    },
    @{
        DisplayName = "Project Status Reports"
        Description = "Channel for project status reports"
        ChannelType = "Standard"
    },
    @{
        DisplayName = "Project Budget Tracking"
        Description = "Private channel for project budget and financials"
        ChannelType = "Private"
    }
)

Provisioning the channels is done by looping through the array created and running the Add-PnPTeamsChannel for each new channel. Where there is a private channel, we also need to specify an owner which we retrieve from the Team object itself.

$Owner = [array](Get-PnPTeamsUser -Team $TeamObject.GroupId)[0].userprincipalname
foreach($Channel in $ProjectTeamChannels)
{
    if($Channel.ChannelType -eq "Private")
    {
        Add-PnPTeamsChannel -Team $TeamObject.GroupId -DisplayName $Channel.DisplayName -Description $Channel.Description -ChannelType $Channel.ChannelType -OwnerUPN $Owner
    }
    else
    {
        Add-PnPTeamsChannel -Team $TeamObject.GroupId -DisplayName $Channel.DisplayName -Description $Channel.Description -ChannelType $Channel.ChannelType
    }
}

Once the above script runs, each channel is added to the Team as shown in Figure 5.

Figure 5: Channels are provisioned within the Team
Figure 5: Channels are provisioned within the Team

Add SharePoint Site Templates

Finally, as I outlined in this article on using PnP PowerShell to manage SharePoint Online, we use site templates to customize the SharePoint site for our end users. To apply a site template to the Teams we create, we get the URL of the site from the Team and then apply the template using the Invoke-PnPSiteTemplate cmdlet:

$SiteURL = Get-PnPMicrosoft365Group -Identity $TeamObject.GroupId -IncludeSiteUrl | Select-Object -ExpandProperty SiteUrl
Connect-PnPOnline -Interactive -ClientId $ClientID -Url $SiteURL
Invoke-PnPSiteTemplate -Path C:\SiteTemplates\TeamSiteTemplate.pnp

Flexibility is Key

One of the things I like about PnP PowerShell is the level of flexibility provided by the module. There are multiple areas within the process where we interact with different components of the Microsoft 365 platform which would usually require us to use different modules to achieve. This flexibility means that tenant administrators can get to a high-quality solution in a relatively short space of time.

In this article, I’ve shown how to build the basics of a Teams provisioning script in very little time. There is no doubt that many more additions and enhancements can be made to your provisioning scripts to make the creation of new Teams an easy, well-polished process. The script shown here gives a starting point to build a robust and polished solution.

About the Author

Sean McAvinue

Sean McAvinue is a Microsoft MVP in Office Development and has been working with Microsoft Technologies for more than 10 years. As Modern Workplace Practice Lead at Ergo Group, he helps customers with planning, deploying and maximizing the many benefits of Microsoft 365 with a focus on security and automation. With a passion for creative problem solving, he enjoys developing solutions for business requirements by leveraging new technologies or by extending the built-in functionality with automation. Blogs frequently at https://seanmcavinue.net and loves sharing and collaborating with the community. To reach out to Sean, you can find him on Twitter at @sean_mcavinue

Leave a Reply