Automating with PowerShell: Deploying Microsoft Teams Templates

One of the biggest challenges we’ve faced when moving over our client base towards a cloud-only infrastructure has been the development of Teams. Teams is amazing tool on it’s own but there is a risk regarding governance and training, but also explosive growth of teams and the way these are implemented. Especially because Teams makes self-services super easy.

So, to assist our clients we’ve created a Teams Template that we deploy to everyone to help them out in how to set-up their Teams environment. This template is pushed towards all our clients using the Secure Application Model. The script I’m sharing can easily be adapted to fit your own needs. As an example we’re creating an IT-support team, but you can also edit the template as a general Team deployment that you use everywhere.

The script

Our template team will have two channels. An announcement channel, and a training channel. The training channel will have a tab to our ticketing system, a tab to our FAQ, and a tab our online training website. You could even attach an email address to a channel directly so you have an easy way to distribute announcements.

Before you continue, make sure your secure application model app has the following permissions added:

  • Go to the Azure Portal.
  • Click on Azure Active Directory, now click on “App Registrations”.
  • Find your Secure App Model application. You can search based on the ApplicationID.
  • Go to “API Permissions” and click Add a permission.
  • Choose “Microsoft Graph” and “Application permission”.
  • Add the following permissions: Team.Create, Group.ReadWrite.All, Directory.ReadWrite.All
  • perform the same for “Delegate Permissions”.
  • Finally, click on “Grant Admin Consent for Company Name.

After you’ve done this, change the parameters in the script to whatever you want them to be. If you want to remove the “General” team or apply other settings such as classroom rules, voting systems, etc. then check out the Teams Templates at the documentation here.

######### Secrets #########
$ApplicationId = 'AppID'
$ApplicationSecret = 'AppSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'YourTenantID'
$RefreshToken = 'TheannoyinglyLongRefreshToken'
######### Secrets #########

$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
 
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All


foreach ($customer in $customers) {
$OwnerID = (get-msoluser -TenantId $customer.TenantId "YourAdminUser@$($customer.DefaultDomainName)" ).ObjectId
    
$TeamsSettings = [PSCustomObject]@{
    "template@odata.bind" = "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"
    "visibility"          = "Public"
    "displayName"         = "IT-Support Team"
    "description"         = "The Team for all your IT support needs"
    "memberSettings"      = @{
        "allowCreateUpdateChannels"         = $false
        "allowDeleteChannels"               = $false
        "allowAddRemoveApps"                = $false
        "allowCreateUpdateRemoveTabs"       = $false
        "allowCreateUpdateRemoveConnectors" = $false
    }
    "guestSettings"       = @{
        "allowCreateUpdateChannels" = $false
        "allowDeleteChannels"       = $false
    }
    "funSettings"         = @{
        "allowGiphy"            = $false
        "giphyContentRating"    = "Moderate"
        "allowStickersAndMemes" = $false
        "allowCustomMemes"      = $false
    }
    "messagingSettings"   = @{
        "allowUserEditMessages"    = $false
        "allowUserDeleteMessages"  = $false
        "allowOwnerDeleteMessages" = $false
        "allowTeamMentions"        = $false
        "allowChannelMentions"     = $false
    }
    "discoverySettings"   = @{
        "showInTeamsSearchAndSuggestions" = $true
    }
    "members" = @(@{
           "@odata.type" = "#microsoft.graph.aadUserConversationMember"
           "roles" = @("owner")
           "user@odata.bind" = "https://graph.microsoft.com/v1.0/users('$($OwnerID)')"
        })
    "channels"            = @(@{
            "displayName"         = "Announcements 📢"
            "isFavoriteByDefault" = $true
            "description"         = "This is a channel for announcement from your IT-Provider"
        } 
        @{
            "displayName"         = "Training 🏋️"
            "isFavoriteByDefault" = $true
            "description"         = "This channel contains all IT-training stuff."
            "tabs"                = @(@{
                    "teamsApp@odata.bind" = "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps('com.microsoft.teamspace.tab.web')"
                    "displayName"         = "YourWebsite.com"
                    "configuration"       = @{
                        "contentUrl" = "https://docs.microsoft.com/microsoftteams/microsoft-teams"
                    }
                }, @{
                    "teamsApp@odata.bind" = "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps('com.microsoft.teamspace.tab.web')"
                    "displayName"         = "An FAQ"
                    "configuration"       = @{
                        "contentUrl" = "https://google.com"
                    }
                }
            )
        }
    )
} | convertto-json -Depth 10
    write-host "Logging into tenant $($customer.DefaultDomainName)"
    $body = @{
        'resource'      = 'https://graph.microsoft.com'
        'client_id'     = $ApplicationId
        'client_secret' = ((New-Object PSCredential "user", $ApplicationSecret).GetNetworkCredential().Password)
        'grant_type'    = "client_credentials"
        'scope'         = "openid"
    }
    $ClientToken = Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($customer.TenantId)/oauth2/token" -Body $body -ErrorAction Stop
    $headers = @{ "Authorization" = "Bearer $($ClientToken.access_token)" }
   
    write-host "Creating Team for tenant $($customer.DefaultDomainName)"
    $Teams = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/teams" -Headers $Headers -Method POST -body $TeamsSettings -ContentType "application/json" -verbose)

} 

As always, Happy Powershelling!

P.S. Have you registered for the CyberDrain CTF yet? The CyberDrainCTF is an event just for SysAdmins, IT-Pros, and MSPs. more info at ctf.cyberdrain.com

1 Comment

  1. Ryan July 6, 2021 at 3:27 am

    Hi Kelvin,

    Just gave this a go but the emojis in the display names don’t seem to be formatted correctly.
    Removing them allowed for the team to be created.

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.