Microsoft Teams | Create Channel from Microsoft Teams Form

Header teams self service
Reading Time: 7 minutes

Something else this time. Normally the blogs are about Mailbox migrations or Microsoft Azure or PowerShell with Graph API. This time we are having a look into Power Automate, Forms and Teams. Using PowerShell and Graph API to automate a task which can be addressed to a user instead of an admin needs to do so. Let the user take back control instead doing all the boring work as an admin.

Use case

A Company want to use Microsoft Teams instead of an old fashion fileserver. Redirecting the data to Microsoft Teams is no issue. For all the customers of the company is a channel created in a (all) customer teams. Every customer is divided in a channel. Private or standard with additional members.
Every channel had the same folder structure so documents can be found in the correct folder within the channel.

Now the company (say: GetToTheCloud) wants to provide the user with a Microsoft Form. The form hold some input which is needed to provision a new channel when a new customer is onboarded. The folder structure needs to be there and the members must be provided already. A side note to this is, that sometimes a channel needs to be private. This because not all the users from the department are allowed to see the data or content in the channel chat. The form must be available in a Microsoft Teams. The teams settings organisation wide is that creation of channels by users is not allowed. With this form users are allowed to create teams channels but it is monitored.

Needed

For this we are using:

  • Microsoft Teams Forms
    a form to get the input from the user
  • Power Automate
    to start a flow when the respose of the form is received
  • Azure Function (serverless consumption plan)
    this is needed to create with PowerShell to create the channel through Graph API and create the folder structure. i use an azure function because the teams connector in Power Automate is not able to create private channels

Let’s get started

First we are creating an App registration

Browse to https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview to create a new app registration. It is just a basic app registration with a client secret and some API Permissions.

The API permissions you need to add are:

After adding the permissions, save the client secret en app id and tenant id.

Create Azure Function App

Browse to Function App – Microsoft Azure and click Create

Choose a Resource group and a name for the function app. Select PowerShell Core and your region which is close to your country. If you choose for Consumption you will have a max of 60 minute duration that the function app can run before it timed out.

All the other tabs are just clicking next or if you like to change something to it. For this example I just click all next.

When the app is created, browse to it and click on configuration.

Add:

  • Appid
    AppId of App registration
  • ClientSecret
    Client secret of App registration
  • TenantId
    Add the tenantId
  • TeamsId
    Add the teamsId of the teams where the channel needs to be created

Browse to App Files and select requirements.psd1

Add:

'MSAL.PS' = '4.*'

Browse to Functions and add a Http-Trigger. When the trigger is created, select within the trigger Code + test. Select Run.ps1 and add the following script:

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Body.Name
$description = $Request.Body.description
$owner = $request.Body.Owner
$private = $request.Body.private
$defaultFolders = $request.Body.defaultFolders

# function to send e-mail
function Send-MailToinform {
    param(
        [Parameter(Mandatory)]
        [string]$to,
        [Parameter(Mandatory)]
        [string]$from,
        [Parameter(Mandatory)]
        [string]$TeamsName,        
        # [Parameter(Mandatory)]
        # [array]$content,
        [Parameter(Mandatory)]
        [bool]$created
    )

    $emailSender = $from
    $emailSubject = "Creation of Teams channel: $TeamsName"
    #endregion 1


    #region 2: Run

    $Message = "<html><head>"
    $message += "<style>
    table, th, td {
      border: 1px solid black;
      border-collapse: collapse;
    }
    th, td {
      padding: 5px;
    }
    th {
      text-align: left;
    }
    </style><body>"
    $Message += "Hi,<br>"
    $message += "<br>"
    if (!($Created)) {
        $message += "Unable to create a channel with the name $TeamsName. This is because it already exist.<br>"
        $message += "Please submit the form again with a different unique name.<br>"
    }
    else {
        $message += "Channel with the name $teamsname is created<br>"
    }
    $message += "<br>"
    $message += "Kind regards,<br>"
    $Message += "<br>"
    $message += "Your Teams Channel provision tool!"
    $MessageParams = @{
        "URI"         = "https://graph.microsoft.com/v1.0/users/$emailSender/sendMail"
        "Headers"     = $Header
        "Method"      = "POST"
        "ContentType" = 'application/json'
        "Body"        = (@{
                "message" = @{
                    "subject"      = $emailsubject
                    "body"         = @{
                        "contentType" = 'HTML' 
                        "content"     = $Message 
                    }
                    "toRecipients" = @(
                        @{
                            "emailAddress" = @{"address" = $to }
                        } ) 
                    "importance"   = "High"
                }
            }) | ConvertTo-JSON -Depth 6
    }   # Send the message
    Invoke-RestMethod @Messageparams
    
}

# setting variables for GraphAPI call
$appid = $ENV:appId
$clientsecret = $ENV:clientSecret | ConvertTo-SecureString -AsPlainText -Force
$tenantID = $ENV:TenantID
$teamsid = $Env:Teamsid

# getting tokens
do {
    try {  
        $Token = (Get-MSALToken -Clientid $Appid -ClientSecret $ClientSecret -TenantId $TenantId -ForceRefresh)
        $mustRetry = 0
    }
    catch {
        $mustRetry = 1
        Start-Sleep -seconds 2
    }
    
} while (
    $mustRetry -eq 1
)

## private channel parameters
if ($Private) {
    $membershipType = "private"
}
else {
    $membershipType = "standard"
}

# setting url for owner
$userDatabind = "https://graph.microsoft.com/v1.0/users('$($owner)')"

# setting parameters to create teams channel
$params = @{
    "@odata.type"  = "#Microsoft.Graph.channel"
    membershipType = $membershipType
    displayName    = $name
    description    = $description
    members        = @(
        @{
            "@odata.type"     = "#microsoft.graph.aadUserConversationMember"
            "user@odata.bind" = $userDatabind
            roles             = @(
                "owner"
            )
        }
    )
}

# convert parameters
$body = $params | ConvertTo-Json -Depth 5
$apiUri = "https://graph.microsoft.com/v1.0/Teams/$teamsId/channels"
$header = @{
    'Authorization' = "BEARER $($Token.accesstoken)"
    'Content-type'  = "application/json"
}

# create channel with parameters
try {  
    $createChannel = Invoke-Restmethod -Method POST -Uri $apiUri -headers $header -Body $body
}
catch {
    $webError = $_
    $webError = ($weberror | convertFrom-json).error.innererror.code
}

# if creation of folders is True, create default folders
if ($defaultFolders -eq $true) {
    do {
        try {  
           # getting folder location
            $folderlocation = (Invoke-Restmethod -method Get -headers @{Authorization = "Bearer $($Token.accesstoken)" } -Uri https://graph.microsoft.com/v1.0/teams/$teamsid/channels/$($CreateChannel.id)/filesFolder).weburl.split(".com")[1] + "/Children"
            $mustRetry = 0
        }
        catch {
            $weberror = $_
            $mustRetry = 1
            Start-Sleep -seconds 2
        }
        
    } while (
        $mustRetry -eq 1
    )

    $drive = Invoke-Restmethod -method Get -headers @{Authorization = "Bearer $($Token.accesstoken)" } -Uri https://graph.microsoft.com/v1.0/teams/$teamsid/channels/$($CreateChannel.id)/filesFolder
    $driveId = $drive.id
    $driveitemId = $drive.parentReference.DriveId
    
    # determine default folders to create
    $foldersToCreate = "Communication", "Administration $((Get-Date).year)", "Another folder"
    ForEach ($FolderToCreate in $foldersToCreate) {
        ## create folderstructure
        $params = @{
            name                                = $FolderToCreate
            folder                              = @{
            }
            "@microsoft.graph.conflictBehavior" = "rename"
        }

        $params = $params | ConvertTo-Json
        Invoke-Restmethod -Method POST -Uri "https://graph.microsoft.com/v1.0/drives/$driveitemid/items/$DriveId/children" -headers $header -body $params
    }
}

# check if error is that channel already exist
if ($weberror -eq "NameAlreadyExists") {
    # send email to inform
    Send-MailToinform -to $owner -from $owner -created $false -TeamsName $name
}
else {
    # send email to inform
    Send-MailToinform -to $owner -from $owner -created $true -TeamsName $name
}

#

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body       = $body
    })

Browse to the Azure Function Trigger and copy the Function Url. Write it down to use later.

Building the form in teams

Add Teams Forms to the teams where the channels needs to be created. When added and installed create a new form from blank.

As in the example, the form below is needed to create the channels.

Power Automate Flow

After creating the form and the azure function, it is time to connect them. In Power Automate we use a premium function HTTP for this. It is in this case 90-day free trial and after $0,60 per run. Depending how often this is needed to run, you can decide which plan fits the best to your needs.

Browse to Power Automate and go to My Flows

Create a new flow, Automated cloud flow

Provide a name en search for Forms. Select When a new form response is submitted and click Create

Create the flow by adding the form as the trigger and the next step is a HTTP

In the Body of the HTTP place the same as above with your fields. Make sure the Name, Description and owner are between ” “.

Save the flow.

Testing

In Microsoft Teams go to your form and fill the form

As you can see, only the General channel is created by default. With the form we are now creating a new channel called: Test channel for website and set the Private and Default Folders checked.

Click SUBMIT

After creating you will receive an email like:

and within Microsoft Teams you will see:

A new private channel is created with in the Files tab the default folders as defined in the Azure Function.

#happyprovisioning

Share and Enjoy !

Shares
Designer (23)

Stay close to the action—follow GetToThe.Cloud across social!
Deep dives and hands‑on how‑tos on Azure Local, hybrid cloud, automation, PowerShell/Bicep, AVD + FSLogix, image pipelines, monitoring, networking, and resilient design when the internet/Azure is down.

🔗 Our channels
▶️ YouTube: https://www.youtube.com/channel/UCa33PgGdXt-Dr4w3Ub9hrdQ
💼 LinkedIn Group: https://www.linkedin.com/groups/9181126/
✖️ X (Twitter): https://x.com/Gettothecloud
🎵 TikTok: https://www.tiktok.com/@gettothecloud
🐙 GitHub: https://github.com/GetToThe-Cloud/Website
💬 Slack: DM us for an invite
📲 WhatsApp: DM for the community link

We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners. View more
Cookies settings
Accept
Privacy & Cookie policy
Privacy & Cookies policy
Cookie name Active

Who we are

Our website address is: https://www.gettothe.cloud

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection. An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year. If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser. When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed. If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website. These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Who we share your data with

If you request a password reset, your IP address will be included in the reset email.

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue. For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.
Save settings
Cookies settings