Monday, December 16, 2019

Copying files between Microsoft Teams using Microsoft Graph

In  2018 I attended a session held by Luis Manez about Microsoft Graph. The session's title  was: Domina la Graph API, domina el mundo. Translating it to English means "Take over the world by dominating the Graph API". The session's title is a little bit ambitious, but there is some truth in that. Luis did not show us how to take over the world, but he did explain Microsoft Graph to us really well and the advantages you have using it. Since then, I have started implementing Graph more and more in my projects. For instance, I created a provision engine completely based on Graph that is capable of entirely provisioning Microsoft Teams. At that time, PnP did not provide support for provisioning Teams. So, thank you Luis for helping me keep the Graph flame burning in my heart 😊 I'm feeling so poetic right now!

In this blog post, I will demonstrate how to copy files between Microsoft Teams using Graph. Maybe you have a file that must be in different Teams. For instance, each Team in your company represents a project which must contain a PDF document called Rules of Communication. In addition, this document must be available as a Team's tab so Team members can easily find it. The picture below shows the process that I'll demonstrate to you in the next steps:


1. Register an Azure app
2. Install the PowerShell module "AzureAD"
3. Metadata required
4. The PowerShell logic
5. Logic's limitation
6. Ideas how you could enhance the script's logic

1. Register an Azure app

 1. Go to
 2. Register an app using your Office 365 account (the account must have permissions to add applications to your Azure AD)
 3. Give the app the proper scope permissions - Groups.ReadWrite.All (delegated permission). Don't forget to consent the permission
 4. Configure Desktop + Devices as the platform
 5. Set an arbitrary Redirect URI (e.g. https://localhost/)
 6. Note your Redirect URI and Application ID since these values will be used in the PowerShell logic

2. Install the PowerShell module "AzureAD"

Install AzureAD module if you haven't done it yet in your environment, since you will need it for authentication against Graph. Here is how the command to install looks:

Install-Module -Name AzureAD

3. Metadata required

- ClientID: The application ID assigned by the Azure app registration portal
- ID of the source team
- ID of the target team
- Filename (including extension): File to copy from source team to target team

4. The PowerShell logic

The script below copies a file from a source team to a target team. In detail, it copies a file from the General source's channel to the General target's channel. After the file has been successfully uploaded, it will be added as a Team's tab! For better understanding, I added comments to the logic. In addition, I made notes of important aspects that you must keep in mind when developing such a solution. Instead of using the new Microsoft Graph PowerShell (MS Graph PS) module or the existing Microsoft Teams PowerShell (MS Teams PS) module, I'm directly using REST to communicate with Graph. The new Microsoft Graph PowerShell is still in beta, but looks very powerful. I will give it a try it in further implementations. The existing MS Teams PS module is very limited. I assume, it will be replaced with the new MS Graph PS module. Here is how the script looks:

- entityId must be uppercase. The entityId is the sourceDoc ID of the file
- you find the entityId in the @microsoft.graph.downloadUrl property
- contentUrl must be a decoded URL, otherwise file's content won't display in Team's tab

The code below shows how to request this PowerShell logic:

-ClientId '1002ec18-ab42-4369-9e4d-f4221e7fdb40' 
-SourceTeamId '5a5cc296-ebcd-4fac-b3c6-619f8bfa7363' 
-TargetTeamId '7b2d81da-0010-45c6-bbfb-bbadccb06e75' 
-Filename 'Rules of Communication.pdf'

5. Logic's limitations

- Graph Search API works with delay. The specified file must be first indexed before the API can find it. This process can take a few minutes. For instance, if you have just uploaded a file to Teams, it takes a few minutes until the Search API can find it
- Target Teams must exist. This logic won't create the team if it doesn't exist
- Target Channel must exist. This logic won't create the channel within the team if it doesn't exist
- Copying file into folder is not supported
- The file to copy is a PDF document
- Source and target channel are General

6. Ideas how you could enhance the script's logic:

- Find teams by mailNickname instead of working with teams' ID
- Create the target team if it doesn't exist
- Create the target channel within the target team if it doesn't exist
- Define the document's teamAppId depending of the file extension. This current script uses the one for PDF
- Replace the search API with a reliable solution

Did you implement some of these ideas? Then share it with the community :)


The Microsoft Graph API provides extensive support for working with documents and teams. Take advantage of Graph in combination with PowerShell using it to simplify and automate recurring tasks!

Monday, November 25, 2019

Speaking at SharePoint Saturday Oslo 2019

I'm looking forward to speaking at SharePoint Saturday in Oslo on December 7, 2019.

SPS Oslo will be superb and very special since Jeff Teper and Omar Shahine will give the keynote! In addition, top speakers (international and local) will fill the rest of the agenda. As you can see, there will be a lot of people to meet and a lot of knowledge to acquire. I've attended to several SharePoint/Office 365 events in the past and I've never met Jeff Teper or Omar Shahine in a community event before! It is amazing to have you here. Thanks for joining us 👍

In my session, I'll cover different topics around SharePoint Site Designs and Site Scripts. I've been blogging, using and speaking about this topic and I'm very happy to share my knowledge about it with the attendees! I'm looking forward to seeing you there 😀

I am very happy to be a part of the speakers team! It’s going to be my third speech at a SharePoint/Office 365 Saturday event. Let's get in touch if you're also going to participate in SPS Oslo!

Oslo, I'm coming 😉

Event information:

Home page -
Schedule -
Registration -

Tuesday, November 19, 2019

Adding dynamic references to default components of an Office 365 Group using SharePoint Site Designs

How nice would it be to have links visible to users to the default Planner, Calendar and Team in the quick launch of an Office 365 group from the beginning? Let me answer that for you: That would be awesome 🥳. By default, we already have in an Office 365 group links to the group’s document library, default notebook and mailbox. But what about the other components? I’ve seen many questions about this topic either during my speaker sessions about Site Designs or in Github. I've started looking for a better way to solve that problem and finally I’ve found a very elegant way to implement a solution without the need of unique identifiers. What!? No IDs?

Once again, Site Design offers the easiest way to solve this problem since by default it supports the capability of adding links to the SharePoint navigation through the addNavLink action. This action supports references to web relative links which is very helpful if you don’t know unique identifiers in advance, such as site collection URL or group ID. Therefore, I chose Site Designs as the base of this implementation. The rest of the job is done by the "_layouts/15/groupstatus.aspx?target=<target> URL. Combined to the Office 365 group URL, this URL refers to Office 365 default components such as Planner, Calendar, Team, Documents, Notebook, Members and so on. Checkout Yannick Reekmans blog post if you want detailed information about this URL and its functionality. Since addNavLink supports web relative URL, you can image how simple it is to create those dynamic links using Site Designs.

In this blog post, I will answer three questions:

How to reference to default Planner of an Office 365 group using SharePoint Site Designs?

The solution is to reference to "/_layouts/15/groupstatus.aspx?target=planner" url. This url points to the default planner of the Office 365 group. This is what the site script looks like:


How to reference to default Calendar of an Office 365 group using SharePoint Site Designs?

The solution is to reference to "/_layouts/15/groupstatus.aspx?target=calendar" URL. This URL points to the default calendar of the Office 365 group. This way you don’t need to provide values such as group mail nickname or tenant name since these values are required in the default calendar URL:<TenantName><GroupMailNickname>

This is what the site script looks like:


How to reference to associated Microsoft Team of an Office 365 group using SharePoint Site Designs?

The solution is to reference to "/_layouts/15/groupstatus.aspx?target=team" URL. This URL points to the associated Microsoft Team of the Office 365 group. This way you don’t need to provide values such as channel ID or tenant ID since these values are required in the team URL:

This is what the site script looks like:


Note that the associated team must exist, otherwise that link won’t work properly and the end user will end up with an exception message.

Site Design and Site Script

The site script below contains all the above navigation entries. Here is what the site script looks like:

    "actions": [
    "bindata": { },

In order to create a Site Script based on that script, you need to run the following PowerShell command (instead of PowerShell you could also use CSOM or REST for instance). Note that the Site Script response will be stored in a variable in order to reuse it during Site Design creation:

$siteScript = Add-SPOSiteScript -Title "Dynamic References" -Content (Get-Content "C:\Temp\DynamicReferences.json" -Raw)

The next PowerShell command creates the Site Design. Here is what it looks like:

Add-SPOSiteDesign -Title "Dynamic References" -WebTemplate 64 -SiteScripts $siteScript.Id

Id                  : 12c6f045-8b61-459e-85d5-2a68224e24f7
Title               : Dynamic References
WebTemplate         : 64
SiteScriptIds       : {acab2014-71a1-48a8-b49d-442b8c276384}
Description         :
PreviewImageUrl     :
PreviewImageAltText :
IsDefault           : False
Version             : 1
DesignPackageId     : 00000000-0000-0000-0000-000000000000

Note that WebTemplate equals 64 represents Modern Team sites.

The end result

Applying the Site Design that we’ve just created, results in a left navigation that contains three new links to the default Planner plan, Calendar and associated Team. Here is what the navigation looks like:

I hope you enjoyed reading this blog postSee you on the next post! Follow me on twitter and keep up-to-date!

Tuesday, November 12, 2019

Extracting a site collection using SharePoint Site Designs (Get-SPOSiteScriptFromWeb)

At Microsoft Ignite 2019, Microsoft announced that the capability of extracting a SharePoint site collection using Site Designs will be available soon 🥳 Lucky me, because it is already working on my Office 365 tenant which run as targeted release 😀 In this blog post, I want to show you how you can extract a site using Site Designs. In addition, I will also show what is supported when extracting the site.

Short feature overview:

Site Designs was enhanced by the capability of extracting a site as a Site Script. It means that you can reuse an existing site collection to create copies of that. This process supports additional parameters which you can use to define what will be extracted.

What can I extract?

Comparing to other extraction tools like PnP or Sites.asmx service, Site Designs is still very limited in terms of the supported components. Nevertheless, it is a good alternative to existing tools, especially if the scope of components to be extracted is small.

The Site Designs feature for extracting site collections can be enhanced by supportive parameters. These parameters define the components to be extracted. During extraction, these parameters will be converted to Site Script actions/verbs which make the extracted output very easy to be reused. Below I've listed all the supported parameters:

IncludeBranding: Supports extracting the branding of a site collection. Here is what the output of this parameter looks like:


IncludeTheme: Supports extracting the custom theme of a site collection. The output of this parameter depends if the site is themed or not. If the site is not themed, you will end up with either an exception or a warning message:

Exception: No actions could be successfully exported. Additional information: This site is not
themed. Please apply a theme to the site before exporting the theme.

Warning: This site is not themed. Please apply a theme to the site before exporting the theme.

Here is the output if the site is themed. Note that I shorted the output of the palette property in order to improve readability.

"themeJson": {
"palette": {

Very interesting here is the themeJson property! The applyTheme action has been supporting only the themeName property. Now you can use the themeJson property to enter a theme based on its JSON definition instead of its name. That makes the extraction very flexible, since it supports applying this script to another tenant which doesn't have a theme by name.

IncludeRegionalSettings: Supports extracting the regional settings. Here is how the output of this parameter looks:


IncludeSiteExternalSharingCapability: Supports extracting the external sharing capability. Here is how the output of this parameter looks:


IncludeLinksToExportedItems: Supports extracting links from the quick launch. There is a logical dependency between a navigation link and a list. Therefore, this parameter must be used along with the IncludedLists parameter. When using this parameter without the IncludedLists parameter, I ended up with the following exception:

"No actions could be successfully exported. Additional information: No QuickLaunch links
suitable for export could be found. In order to export navigation links pointing to lists, the list needs to be included in the site script as well."

I was testing this parameter also with custom links like "", but it didn't extract that. I did also try to extract the link to the Shared Document library but it didn't work. During my tests, this parameter has stopped outputting the link when used correctly. I assume that Microsoft is still working on it 😀

This is what the output looks like if the parameter is used/work correctly:

    "listName""Project Activities",
    "subactions": [
    "url""/Lists/Project Activities",
    "displayName""Project Activities",

IncludedLists: Supports extracting one or more SharePoint lists. Here is what the output of this parameter looks like:

"listName""Project Activities",
"subactions": [
    "name""All Items",
    "viewFields": [
    "addLink""Project Activities"

What kind of site collections are supported?

I tested the Get-SPOSiteScriptFromWeb command with the site collections that my customers have been mostly using. I tried to extract the regional settings, the branding, the theme, the sharing capability and the shared documents library of the corresponding site collections. No matter which site collection I've used, Site Designs was always able to extract the content according to my specifications. Of course, the extraction output depends on the site collection type. For instance, IncludeBranding parameter will always return footer as false for a modern team site (Office 365 group) since a modern team site doesn't support editing the footer area by default. I listed below the site collections that I've used for testing:

- Communication Site
- Modern Team Site (Office 365 Group)
- Classic Team Site
- Modern Team Site without Group

Extract a SharePoint site in different ways:

The extraction of a site collection using Site Design can be done with PowerShell, REST or CSOM. I demonstrated below how to extract a site collection using PowerShell, REST and CSOM:


-IncludedLists ("Protocols""Lists/Project Activities")





var tenant = new Tenant(ctx);

var info = new TenantSiteScriptSerializationInfo()
    IncludeBranding = true,
    IncludeTheme = true,
    IncludeRegionalSettings = true,
    IncludeLinksToExportedItems = true,
    IncludeSiteExternalSharingCapability = true,
    IncludedLists = new[] {"Protocols""Lists/Project Activities"}

var response = tenant.GetSiteScriptFromSite("", info);


Note that in CSOM the method to extract the site collection is called GetSiteScriptFromSite! In PowerShell and REST it is called ...FromWeb. So, don't get confused when extracting sites using PowerShell, REST or CSOM 😀


The capability of extracting a site using Site Designs is definitely a feature that SharePoint admins have been waiting for since it supports reusing the work already done. The scope of supportive parameters is still very small, but I believe that Microsoft will be publishing other parameters over the time.

I hope you enjoyed reading this blog post. See you on the next post! Follow me on twitter and keep up-to-date!