{"id":2227,"date":"2018-11-27T11:00:43","date_gmt":"2018-11-27T18:00:43","guid":{"rendered":"https:\/\/developer.microsoft.com\/en-us\/office\/blogs\/?p=2227"},"modified":"2018-11-27T11:00:43","modified_gmt":"2018-11-27T18:00:43","slug":"30daysmsgraph-day-27-use-case-create-a-team","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/30daysmsgraph-day-27-use-case-create-a-team\/","title":{"rendered":"30DaysMSGraph \u2013 Day 27 \u2013 Use case: Create a Team"},"content":{"rendered":"<p><a href=\"https:\/\/aka.ms\/30DaysMSGraph\">List of all posts in the #30DaysMSGraph series<\/a><\/p>\n<p>-Today&#8217;s post written by Nick Kramer<\/p>\n<p>In <a href=\"https:\/\/developer.microsoft.com\/en-us\/graph\/blogs\/30daysmsgraph-day-26-use-case-calling-microsoft-graph-using-flow\/\">Day 26<\/a> we concluded calling Microsoft Graph using Flow.\u00a0 Today, we&#8217;ll show how to create teams and automate team lifecycles.<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-2231\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/30DaysMSGraph_Day27_Source-1024x538.jpg\" alt=\"\" width=\"800\" height=\"420\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/30DaysMSGraph_Day27_Source-1024x538.jpg 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/30DaysMSGraph_Day27_Source-300x158.jpg 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/30DaysMSGraph_Day27_Source-768x403.jpg 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/30DaysMSGraph_Day27_Source.jpg 1280w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<h2>Introduction to Microsoft Teams<\/h2>\n<p>Microsoft Teams allows people to work together and get projects done by collaborating through chat, calls, and online meetings. Each team has members and owners, channels that contain chat messages, files (stored in SharePoint by default), apps, and tabs. Some of the apps and tabs you might use in a team include OneNote notebooks, Planner plans, and SharePoint lists.<\/p>\n<h2>Teams and Groups<\/h2>\n<p>Every team is part of a Group. (But not every group has a team!) Some of the information you think of as part of the team is actually part of the Group object, such as the name of the team\/group and its members and owners. When you create a new team, you typically create a new group as well. (But it&#8217;s also possible to add a team to an existing group)<\/p>\n<p>When creating teams and groups, you need the Group.ReadWrite.All scope. Both user delegated permissions and application permissions are supported.<\/p>\n<h2>Clone the Sample Application<\/h2>\n<p>To follow along with the code please clone the sample application from the <a href=\"https:\/\/github.com\/microsoftgraph\/contoso-airlines-teams-sample\">contoso-airlines-teams-sample repo<\/a> and follow the instructions in the readme.md file to build the project. The interesting code is in <a href=\"https:\/\/github.com\/microsoftgraph\/contoso-airlines-teams-sample\/blob\/master\/project\/Models\/GraphService.cs\">GraphService.cs<\/a>.<\/p>\n<p>In the Contoso Airlines sample, we create new teams every day for each flight that Contoso Airlines flies &#8212; and then archive the team after the flight is over.<\/p>\n<p><img decoding=\"async\" class=\"size-full wp-image-2232 aligncenter\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams1.png\" alt=\"\" width=\"522\" height=\"471\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams1.png 522w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams1-300x271.png 300w\" sizes=\"(max-width: 522px) 100vw, 522px\" \/><\/p>\n<h2>Creating the team<\/h2>\n<p>Before we create a team, we need the IDs of the team members. Given the user&#8217;s universal principle name (UPN), we can retrieve the user&#8217;s data, including their ID:<\/p>\n<pre style=\"padding-left: 30px\">String userId = (await HttpGet&lt;User&gt;($\"\/users\/{upn}\")).Id;\n\n<\/pre>\n<p>When we add a user to a team, Graph needs to know the full URL to that user:<\/p>\n<pre style=\"padding-left: 30px\">String fullUrl = $\"https:\/\/graph.microsoft.com\/v1.0\/users\/{userId}\";\n\n<\/pre>\n<p>We repeat that for all the users in the flight crew:<\/p>\n<pre style=\"padding-left: 30px\">List&lt;string&gt; ownerIds = await GetUserIds(ownerUpns);\nList&lt;string&gt; memberIds = await GetUserIds(flight.crew);\n\n<\/pre>\n<p>Armed with that list of users, we can now create the group by providing the display name, mail nickname, description, visibility, owners, and members:<\/p>\n<pre>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Group group =\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (await HttpPost($\"\/groups\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0new Group()\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DisplayName = \"Flight \" + flight.number,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MailNickname = \"flight\" + GetTimestamp(),\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Description = \"Everything about flight \" + flight.number,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Visibility = \"Private\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Owners = ownerIds,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Members = memberIds,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GroupTypes = new string[] { \"Unified\" }, \/\/ same for all teams\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MailEnabled = true,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ same for all teams\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SecurityEnabled = false,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ same for all teams\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0}))\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .Deserialize&lt;Group&gt;();\n\n<\/pre>\n<p>This generates a JSON payload of the form:<\/p>\n<pre style=\"padding-left: 30px\">{\n\u00a0\u00a0\u00a0 \"displayName\":\"Flight 157\",\n\u00a0\u00a0\u00a0 \"mailNickname\":\"flight157\",\n\u00a0\u00a0\u00a0 \"description\":\"Everything about flight 157\",\n\u00a0\u00a0\u00a0 \"visibility\":\"Private\",\n\u00a0\u00a0\u00a0 \"groupTypes\":[\"Unified\"],\n\u00a0\u00a0\u00a0 \"mailEnabled\":true,\n\u00a0\u00a0\u00a0 \"securityEnabled\":false,\n\u00a0\u00a0\u00a0 \"members@odata.bind\":[\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/bec05f3d-a818-4b58-8c2e-2b4e74b0246d\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/ae67a4f4-2308-4522-9021-9f402ff0fba8\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/eab978dd-35d0-4885-8c46-891b7d618783\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/6a1272b5-f6fc-45c4-95fe-fe7c5a676133\"\n\u00a0\u00a0\u00a0 ],\n\u00a0\u00a0\u00a0 \"owners@odata.bind\":[\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/6a1272b5-f6fc-45c4-95fe-fe7c5a676133\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"https:\/\/graph.microsoft.com\/v1.0\/users\/eab978dd-35d0-4885-8c46-891b7d618783\"\n\u00a0\u00a0\u00a0 ]\n}\n\n<\/pre>\n<p>(Note the members@odata.bind syntax to provide references to existing resources, in this case users)<\/p>\n<p>The next step is to create a team within the group:<\/p>\n<pre style=\"padding-left: 30px\">await HttpPut($\"\/groups\/{group.Id}\/team\",\n\u00a0\u00a0\u00a0 new Team()\n\u00a0\u00a0  {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GuestSettings = new TeamGuestSettings()\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AllowCreateUpdateChannels = false,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AllowDeleteChannels = false\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\n\u00a0\u00a0\u00a0 },\nretries: 3, retryDelay: 10);\n\n<\/pre>\n<p>We could pass an empty body to PUT \/groups\/{group.Id}\/team, but we prefer to change some of the default settings \u2013 we don&#8217;t want guests to be able to create, update, or delete channels. Note also that sometimes when the team is created immediately after the group, that request can fail with a 404 because the necessary data hasn&#8217;t replicated to all data centers, so we retry in that case after a 10 second delay.<\/p>\n<p>Note that the team has the same ID as the group:<\/p>\n<pre style=\"padding-left: 30px\">string teamId = group.Id; \/\/ always the same<\/pre>\n<h2>Channels<\/h2>\n<p>Teams contain a list of channels, which contain the team&#8217;s chat messages. Channels also have a Files tab (typically linked to SharePoint), as well as any additional tabs you want to add to the team. Let&#8217;s create a channel for pilot talk:<\/p>\n<pre style=\"padding-left: 30px\">Channel channel = (await HttpPost(\n\u00a0\u00a0\u00a0 $\"\/teams\/{teamId}\/channels\",\n\u00a0\u00a0\u00a0\u00a0new Channel()\n\u00a0\u00a0\u00a0\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DisplayName = \"Pilots\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Description = \"Discussion about flightpath, weather, etc.\"\n\u00a0\u00a0\u00a0 }\n)).Deserialize&lt;Channel&gt;();\n\n<\/pre>\n<p>When you create a channel with POST \/teams\/{teamId}\/channels, Graph returns the created channel so you know its ID.<\/p>\n<h2>Tabs<\/h2>\n<p>Let&#8217;s add a tab to channel that shows a map of the airport. Every tab has an associated app, for a map we can use the Website app. We know from <a href=\"https:\/\/developer.microsoft.com\/en-us\/graph\/docs\/concepts\/teams-configuring-builtin-tabs\">documentation<\/a> that the app ID for the Website app is &#8220;com.microsoft.teamspace.tab.web&#8221;. Graph will want that as a reference:<\/p>\n<pre style=\"padding-left: 30px\">var appReference = $\"https:\/\/graph.microsoft.com\/v1.0\/appCatalogs\/teamsApps\/{appid}\";\n\n<\/pre>\n<p>Using that, we can create the tab:<\/p>\n<pre style=\"padding-left: 30px\">await HttpPost($\"\/teams\/{teamId}\/channels\/{channel.Id}\/tabs\",\nnew TeamsTab()\n{\n\u00a0\u00a0\u00a0\u00a0DisplayName = \"Map\",\n\u00a0\u00a0\u00a0\u00a0TeamsApp = appReference, \/\/ It's serialized as \"teamsApp@odata.bind\" : appReference\n\u00a0\u00a0\u00a0\u00a0Configuration = new TeamsTabConfiguration()\n\u00a0\u00a0\u00a0\u00a0{\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0EntityId = null,\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ContentUrl = \"https:\/\/www.bing.com\/maps\/embed?h=800&amp;w=800&amp;cp=47.640016~-122.13088799999998&amp;lvl=16&amp;typ=s&amp;sty=r&amp;src=SHELL&amp;FORM=MBEDV8\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WebsiteUrl = \"https:\/\/binged.it\/2xjBS1R\",\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0RemoveUrl = null,\n\u00a0\u00a0\u00a0 }\n});\n\n<\/pre>\n<p>In the configuration section of the tab, there&#8217;s four properties that every tab has \u2013 the EntityID, the ContentURL, the WebsiteUrl, and the RemoveURL. At its core, a tab is an HTML rendering surface \u2013 and ContentURL is the HTML the render. WebsiteUrl is the HTML the show if the user clicks the &#8220;Go to website&#8221; button. RemoveUrl is the HTML shown when the user clicks the Remove button. And EntityId is a string that means something only to the app that powers the tab (in this case, the website app leaves it null) \u2013 again, see <a href=\"https:\/\/developer.microsoft.com\/en-us\/graph\/docs\/concepts\/teams-configuring-builtin-tabs\">the docs<\/a> for your favorite app.<\/p>\n<h2>Create a SharePoint list<\/h2>\n<p>Next, let&#8217;s create a SharePoint list of the problem passengers on the flight, and pin it to a tab. We&#8217;ll need some additional permission scopes in order to use SharePoint: Sites.ReadWrite.All and Sites.Manage.All. With those, we can get to the SharePoint site using the group:<\/p>\n<pre style=\"padding-left: 30px\">var teamSite = await HttpGet&lt;Site&gt;($\"\/groups\/{groupId}\/sites\/root\",\nretries: 3, retryDelay:30);\n\n<\/pre>\n<p>(Again, there can be replication issues if you access the site to quickly after creating the group, so we put in some retries)<\/p>\n<p>Once we have the site, we can create a new SharePoint List with 3 columns, Name, SeatNumber, and Notes:<\/p>\n<pre style=\"padding-left: 30px\">var list = (await HttpPost($\"\/sites\/{teamSite.Id}\/lists\",\nnew SharePointList\n{\n    DisplayName = \"Challenging Passengers\",\n    Columns = new List&lt;ColumnDefinition&gt;()\n    {\n        new ColumnDefinition\n        {\n            Name = \"Name\",\n            Text = new TextColumn()\n        },\n        new ColumnDefinition\n        {\n            Name = \"SeatNumber\",\n            Text = new TextColumn()\n        },\n        new ColumnDefinition\n        {\n            Name = \"Notes\",\n            Text = new TextColumn()\n        }\n    }\n}))\n.Deserialize&lt;SharePointList&gt;();\n\n<\/pre>\n<p>That creates an empty list with those columns. Next we add some data to it:<\/p>\n<pre style=\"padding-left: 30px\">await HttpPost($\"\/sites\/{teamSite.Id}\/lists\/{list.Id}\/items\",\n    challengingPassenger\n    );<\/pre>\n<p>&nbsp;<\/p>\n<p>Then we add a tab to the channel we created earlier:<\/p>\n<pre style=\"padding-left: 30px\">await HttpPost($\"\/teams\/{groupId}\/channels\/{channelId}\/tabs\",\n    new TeamsTab\n    {\n        DisplayName = \"Challenging Passengers\",\n        TeamsApp = appReference, \/\/ It's serialized as \"teamsApp@odata.bind\" : appReference\n        Configuration = new TeamsTabConfiguration\n        {\n            ContentUrl = list.WebUrl,\n            WebsiteUrl = list.WebUrl\n        }\n    });\n\n<\/pre>\n<p>And now we have a fully functioning team, with members, channels, and tabs.<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-2233\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams2.png\" alt=\"\" width=\"799\" height=\"539\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams2.png 970w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams2-300x202.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams2-768x518.png 768w\" sizes=\"(max-width: 799px) 100vw, 799px\" \/><\/p>\n<h2>Apps<\/h2>\n<p>With user delegated permissions, you can also add an app to the team. (We are working on application permissions) Let&#8217;s add the SurveyMonkey bot to the team. We need to know the app ID of the app we want, we can figure that out by running this in Graph Explorer:<\/p>\n<pre style=\"padding-left: 30px\"><strong>GET<\/strong> https:\/\/graph.microsoft.com\/beta\/teams\/{sampleTeam.id}\/installedApps?$expand=teamsAppDefinition&amp;filter=teamsAppDefinition\/displayName eq 'SurveyMonkey'\n\n<\/pre>\n<p>where {sampleTeam.id} is a test team that you added SurveyMonkey to using Microsoft Teams. Look for the teamsAppId in the response.<\/p>\n<p>Now, using that value, we install survey monkey to the new team:<\/p>\n<pre style=\"padding-left: 30px\">await HttpPost($\"\/teams\/{team.Id}\/installedApps\",\n    \"{ \\\"teamsApp@odata.bind\\\" : \\\"\" + graphV1Endpoint + \"\/appCatalogs\/teamsApps\/\" + teamsAppId + \"\\\" }\");\n<\/pre>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-2234\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams3.png\" alt=\"\" width=\"801\" height=\"539\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams3.png 970w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams3-300x202.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams3-768x517.png 768w\" sizes=\"(max-width: 801px) 100vw, 801px\" \/><\/p>\n<h2>Clone a team<\/h2>\n<p>Alternately, instead of creating a team from scratch, we can make a copy of an existing team. This is a good way to allow admins and end users to change the structure of the team you&#8217;re creating without having to update code. Clone Team also supports application permissions, so it&#8217;s a great way to get new teams with apps preinstalled.<\/p>\n<p>When you clone a team, you get to pick which parts you want to clone. Your choices are apps, settings, channels, tabs, and members. We&#8217;ll choose &#8220;apps,settings,channels&#8221;:<\/p>\n<pre style=\"padding-left: 30px\">var response = await HttpPostWithHeaders($\"\/teams\/{flight.prototypeTeamId}\/clone\",\n    new Clone()\n    {\n        displayName = \"Flight 4\" + flight.number,\n        mailNickName = \"flight\" + GetTimestamp(),\n        description = \"Everything about flight \" + flight.number,\n        teamVisibilityType = \"Private\",\n        partsToClone = \"apps,settings,channels\"\n    });\n\n<\/pre>\n<p>Clone is a long-running operation, so we are actually creating a clone request and getting back a handle to check on its status. The critical part of the response is the Location header:<\/p>\n<pre style=\"padding-left: 30px\">string operationUrl = response.Headers.Location.ToString();\n\n<\/pre>\n<p>We query that operationUrl to see if the cloning is done. If it&#8217;s not, we wait 10 seconds and repeat.<\/p>\n<pre style=\"padding-left: 30px\">for (; ; )\n{\n    TeamsAsyncOperation operation = await HttpGet&lt;TeamsAsyncOperation&gt;(operationUrl);\n    if (operation.Status == AsyncOperationStatus.Failed)\n        throw new Exception();\n\n    if (operation.Status == AsyncOperationStatus.Succeeded)\n    {\n        teamId = operation.targetResourceId;\n        break;\n    }\n\n    Thread.Sleep(10000); \/\/ wait 10 seconds between polls\n}\n\n<\/pre>\n<p>Then, because we chose not to clone the membership, we need to add some people to the team:<\/p>\n<pre style=\"padding-left: 30px\">\/\/ Add the crew to the team\nforeach (string id in flight.crew)\n{\n    string payload = $\"{{ '@odata.id': '{graphV1Endpoint}\/users\/{id}' }}\";\n    await HttpPost($\"\/groups\/{teamId}\/members\/$ref\", payload);\n    if (upn == flight.captain)\n        await HttpPost($\"\/groups\/{teamId}\/owners\/$ref\", payload);\n}<\/pre>\n<p>And that gives us a fully functioning team, with members and apps.<\/p>\n<h2>Archive<\/h2>\n<p>Finally, after the flight is over and we are done with the team, we can archive it. This will put the team into a read-only state and hide it from our list of teams so our UI doesn&#8217;t get cluttered, but we can still read the contents of the team via the gear icon at the bottom of the screen:<\/p>\n<p><img decoding=\"async\" class=\"size-full wp-image-2235 aligncenter\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams4.png\" alt=\"\" width=\"423\" height=\"109\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams4.png 423w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2018\/11\/Teams4-300x77.png 300w\" sizes=\"(max-width: 423px) 100vw, 423px\" \/><\/p>\n<p>Like clone, archiving is a long-running operation, so we will create an archiving request and monitor its status:<\/p>\n<pre style=\"padding-left: 30px\">HttpResponse response = await HttpPostWithHeaders($\"\/teams\/{teamId}\/archive\", \"{}\");\nstring operationUrl = response.Headers.Location.ToString();\n\n<\/pre>\n<p>And like clone, we query that operationUrl to see if the archiving is done:<\/p>\n<pre style=\"padding-left: 30px\">for (; ; )\n{\n    var operation = await HttpGet&lt;TeamsAsyncOperation&gt;(operationUrl);\n\n    if (operation.Status == AsyncOperationStatus.Failed)\n        throw new Exception();\n\n    if (operation.Status == AsyncOperationStatus.Succeeded)\n        break;\n\n    Thread.Sleep(10000); \/\/ wait 10 seconds between polls\n}\n\n<\/pre>\n<p>And there we go \u2013 we&#8217;ve shown how to create a new team, set it up with channels, tabs, and apps, and when the team has outlived its usefulness, how to archive it to make room for the next team.<\/p>\n<h2>Try it Out<\/h2>\n<p><a href=\"https:\/\/github.com\/microsoftgraph\/30DaysMSGraph-TryItOut\/blob\/master\/Day27-Teams.md\">Day 27 repo link<\/a><\/p>\n<ul>\n<li>Navigate to the <a href=\"https:\/\/github.com\/microsoftgraph\/contoso-airlines-teams-sample\">contoso-airlines-teams-sample repo<\/a>.<\/li>\n<li>Check out the <a href=\"https:\/\/aka.ms\/teamsgraph\/v1\">Teams overview<\/a> for more details on the Teams APIs in Microsoft Graph.<\/li>\n<li>Watch the <a href=\"https:\/\/aka.ms\/teamsgraph\/v1\/video\">video<\/a>.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>Join us tomorrow as we leverage webhooks for Microsoft Graph requests in <a href=\"https:\/\/developer.microsoft.com\/en-us\/graph\/blogs\/30daysmsgraph-day-28-use-case-webhooks\/\">Day 28<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Day 26 we concluded calling Microsoft Graph using Flow.\u00a0 Today, we&#8217;ll show how to create teams and automate team lifecycles.<\/p>\n","protected":false},"author":73055,"featured_media":25159,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3,128],"tags":[84],"class_list":["post-2227","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-graph","category-microsoft-teams","tag-30daysmsgraph"],"acf":[],"blog_post_summary":"<p>In Day 26 we concluded calling Microsoft Graph using Flow.\u00a0 Today, we&#8217;ll show how to create teams and automate team lifecycles.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/2227","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/73055"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=2227"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/2227\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/25159"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=2227"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=2227"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=2227"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}