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

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.