Tuesday, November 24, 2020

Speaking at Valo Online Summit APAC 2020

I'm looking forward to speaking at Valo Online Summit APAC 2020 on November 25-26, 2020.

Valo is hosting a digital get-together by bringing all the Valo partners together for the very first Valo Online Summit APAC on November 24th – 26th 2020! This Summit is a three-day get-together where the Valo Partner Community meets to delve further into the opportunities and future of Digital Workplace Business. 


I will contribute to this very special event with two sessions:

- Get the latest about Valo Teamwork
- Creating consistent Microsoft Teams teams using Valo Teamwork

I am very happy to be part of the speakers team! Let's get in touch if you're also going to participate in this event!

Event information:

Sunday, November 1, 2020

Speaking at Digital Workplace Summit 2020

I'm looking forward to speaking at Digital Workplace Summit 2020 on November 3, 2020.

The Digital Workplace Summit (#DWSC20) by Communardo is a conference about digital workplace and enterprise collaboration. This digital event offers more than 30 sessions presented by national and international speakers. I posted below my session's description:

"In times of cloud-based software development, a computer program newly introduced to the market can be outdated within a few months. The constant further development and maintenance of a software is therefore very important in order to remain the right tool for the users needs.

In this presentation I would like to share with you experiences from my private life. It surprised me personally how backward some companies are with their tools and what negative impact this has on end customers.

But there is a better way: Valo offers a complete solution for different user problems of the digital workplace. I'll give you an overview of all Valo products (Intranet, Teamwork, Connect and Ideas) and show you how Valo is working continuously to provide quality solutions for Microsoft 365 and the end users."

I am very happy to be a part of the speakers team! Let's get in touch if you're also going to participate in this event!

Event information:

Home Page in German

Monday, October 26, 2020

Microsoft Graph API to create a team is now in GA and supports a new permission level

The Microsoft Graph API to create a team directly without first creating a group has been in GA since September 2020 ๐Ÿฅณ If you have been using POST https://graph.microsoft.com/beta/teams to create a Microsoft Teams team, I'd highly recommend you to use POST https://graph.microsoft.com/v1.0/teams.

The beta/teams API was for a long time the only option to create a team without first having to create a group and then creating a team from the group. In many cases, there is no way around a beta API, but if production APIs are available you should consider replacing it immediately. You must be aware that all APIs under the /beta version in Microsoft Graph are subject to change. Basically, Microsoft can apply changes to /beta APIs that might break your solution.

The example below shows a minimal request to create a team. However, the request can also contain channels, tabs, settings, apps etc.

{
   "template@odata.bind":"https://graph.microsoft.com/v1.0/teamsTemplates('standard')",
   "displayName":"My Sample Team",
   "description":"My Sample Team’s Description"
}

Moreover, since October 2020 the POST /teams API supports a new permission level that allows an application to create teams on behalf of a signed-in user and without a signed-in user.
  • Team.Create (delegated permission)
  • Team.Create (application permission)

Previously, an application must use Group.ReadWrite.All to create a Microsoft Teams team. However, this permission level is very comprehensive and should be avoided whenever possible since it allows the application to create groups/teams, read all group/team properties and memberships, update group/team properties and memberships as well as delete groups/teams. Additionally, it can also be used to read and write group calendar and conversations.

Check out the links below for more information about the POST /teams API:


Wednesday, September 30, 2020

Quick Tip: Avoid limitations when reading SharePoint list items using Microsoft Graph

Microsoft introduced in latest version of CSOM a new tenant property Tenant.DisableCustomAppAuthentication which can be used to disable the capability to grant permissions to app authentication in SharePoint. Unfortunately, new Office 365 tenants have this property set to true by default. I'm not sure if that is a glitch, a bug or intentional. Does Microsoft want us to use Graph instead of SharePoint REST in the future? Anyway, from now on I want to use Graph whenever possible.

I’m developing a Microsoft Teams bot currently which uses SharePoint REST to retrieve data from SharePoint lists, create webhooks etc. Since accessing SharePoint lists and sites is already possible from Microsoft Graph we decided internally to replace the existing SharePoint REST implementation with Graph APIs. Unfortunately, I faced a limitation while retrieving data from a SharePoint list which I want to share with you in this blog post.

The goal

I tried to retrieve a SharePoint list item based on its ID. Along with that, I also tried to read additional content such as the item’s fields. This is what the request looks like:

https://graph.microsoft.com/v1.0/sites/1b3c1c82-8d58-4348-b13f-02898a1c662f/
lists/221395e9-cfca-4ac6-ba33-8b678fbc7836/items/407
?$select=Id&$expand=Fields($select=Title,_ModerationStatus,_ModerationComments)

The limitation

I was retrieving data from a SharePoint list which has content approval settings turned on. During my tests, if I tried to read those items that have an approval status other than Approved (e.g.: Rejected or Pending), the API threw the following error:

System.Exception: {
   "error":{
      "code":"itemNotFound",
      "message":"The specified list was not found",
      "innerError":{
         "date":"2020-09-30T14:33:44",
         "request-id":"56d110a8-4b07-4f37-857d-0dff276742bd",
         "client-request-id":"56d110a8-4b07-4f37-857d-0dff276742bd"
      }
   }
}

This error message is definitely misleading since I was able to retrieve other items from the same list.

The workaround

By turning the content approval settings off, I was able to retrieve all items from that list without limitations. However, that wasn’t the solution I was looking for ๐Ÿ˜€ I tried the same request using the Microsoft Graph Explorer and succeeded. I ended up with the conclusion that the API permission Sites.Manage.All was missing from my AAD app configurations. According to this Microsoft Graph documentationSites.Read.All should be enough to read SharePoint list items. Anyway, after adding the missing permission to my AAD app, I was finally able to read all list items from that list.

I hope it helps!

Saturday, August 1, 2020

Simplifying Proactive Messaging in Microsoft Teams with Microsoft Graph's new API permissions

Proactive Messaging enables you to send notifications in Microsoft Teams without user interaction. Basically, a user or team doesn’t need to interact with the bot to receive its notifications. However, this functionality requires the bot to be installed as a personal app or in a team that the user is a member of. Otherwise, the bot doesn’t have the needed information to proactively contact the user.

There has already been guidance explaining how to combine Microsoft Teams with Microsoft Graph to fulfill the installation requirement. Previously, this process had to be executed manually or programmatically using Graph permissions such as User.ReadWrite.All or Directory.ReadWrite.All that allow any application to be installed. In case of users installing the app manually to receive bot notifications, the proactive concept was no longer complete.

The table turns and the process becomes simpler! Microsoft Graph API introduces two new application permissions TeamsAppInstallation.ReadWriteSelfForUser.All and TeamsAppInstallation.ReadWriteSelfForTeam.All. Those new scoped API permissions allow a Teams app to read, install, upgrade, and uninstall ITSELF for any user/team, without a signed-in user ๐Ÿ’› How cool is that!!! This is how it looks in Azure AD:

To use these new permissions, add a webApplicationInfo key to your team’s app manifest. It must include your Azure AD app ID and the resource URL for the app. Ensure your Azure AD app has the application permission that will fulfill your requirement. E. g.: TeamsAppInstallation.ReadWriteSelfForUser.All if you want to manage notifications for users. This is how webApplicationInfo looks:

"webApplicationInfo": {
  "id":"692107eb-eca3-45da-b81b-e336c762d5e4",
  "resource":"api://692107eb-eca3-45da-b81b-e336c762d5e4"
}

Microsoft has also provided detailed information about the usage of this new approach which is still in public preview. Check out this documentation for more information.

I gave this new approach a try with a bot I've been developing at my company and must say that it works very well. Thanks for the Microsoft Graph Teams team for providing us with these two new application permissions ๐Ÿ’› 

Monday, July 13, 2020

New approach to manage membership of Microsoft Teams using Microsoft Graph Teams API

I am so proud of the Microsoft Graph Teams team ๐Ÿ’› It took a while, but the MS Graph Teams team released a new approach to manage Teams membership directly from a dedicated Graph teams API ๐Ÿ˜ฏ

Background

Currently, changes made to the team’s membership had to be made to the Microsoft 365 group backing a team via the Microsoft Graph API “groups/members” / “groups/owners”. Those membership changes don’t apply immediately to the team and must be synced to the Teams service in order for newly added users to access the team. Unfortunately, the sync mechanism can take up to 24 hours according Microsoft. From my experience, it can take even longer.

The new approach

New teams Graph APIs have been introduced in order to remove the dependency on the group Graph API. Basically, it is no longer required to use group Graph APIs to manage the Teams membership since the new beta/teams/{teamId}/members API is available and it supports CRUD operations!!!

Those APIs support both, delegated and applications permissions ๐Ÿฅณ

List all team members and owners

You can use this API to retrieve all members and owners of a TEAM within the same call ๐Ÿ˜ฏ That would be perfect if this API would support pagination!!! ATM, this is a known limitation! However, in many cases it might improve performance since you only need one call to retrieve owners and members of a team instead of different calls to first retrieve the group and then retrieve the owners/members. As far as I have tested, this API returns up to 100 users within one request. This is how the request to retrieve all owners and members looks:

GET https://graph.microsoft.com/beta/teams/{teamsId}/members

Check out the corresponding Microsoft Graph documentation for more information! (List all team members and owners)

Add a user

It will add users immediately (always happend during my tests) to the team or return a corresponding error code if the adding process fails. This is a great improvement since the sync delay issue between group and team will no longer be an issue as described previously. This is what the request to add new users to the team looks like:

POST https://graph.microsoft.com/beta/teams/{teamsId}/members

{
   "@odata.type": "#microsoft.graph.aadUserConversationMember",  
   "roles": [""],
   "user@odata.bind": "https://graph.microsoft.com/beta/users/666a3fb6-d598..."  
}

Check out the corresponding Microsoft Graph documentation for more information! (Add members or owners to a team)

Update a user

This API updates the user’s role in a team and is only supported on private channels. For instance, you can use this API to promote an user from member to owner:

PATCH https://graph.microsoft.com/beta/teams/{id}/channels/{id}/members/{id}

{
   "@odata.type": "#microsoft.graph.aadUserConversationMember",
   "roles": ["owner"]
}

Check out the corresponding Microsoft Graph documentation for more information! (Update team members or owners)

Delete a user

This API removes a user from a team ๐Ÿ˜Š

DELETE https://graph.microsoft.com/beta/teams/{teamsId}/members

Check out the corresponding Microsoft Graph documentation for more information! (Delete team members or owners)

Summary

I have already had different discussions with partners and customers about the problem caused by the sync delay. This API is a big improvement in the Graph teams API. Just love it!!!

Note that this API is still in beta and seems to be still under development. During my tests, I was facing different errors like getting Bad Request when trying to delete a user from a team. However, this will be the way to go and I am very happy that Microsoft is introducing this new approach. I am really looking forward to seeing the improvements that the Microsoft Graph Teams team will be adding to those new APIs.

Sunday, June 21, 2020

Extra! Extra! Name property no longer supported by Graph Tabs API

It is difficult to create reliable solutions for Microsoft 365 if changes are applied suddenly to Graph APIs without Microsoft informing customers beforehand or if information is not available in the Microsoft documentation. I am not talking about beta APIs. Although, there are Graph APIs that have been in beta for years (e.g.: POST beta/teams) and could be considered in some cases production APIs. I am talking about v1.0/production APIs ๐Ÿ™

Last Monday, June 15th, a colleague from the testing team reported that parts of a solution stopped working properly:

Reason: After a debugging session, I observed that the Graph API to retrieve properties of a specific tab, stopped returning the name property which was a property used in different parts of my solution.

Fix: Fixing this bug wasn't challenging since the same value stored in the name property was also available in the displayName property. Basically, the solution was to replaced the name property with the displayName property.

Problem: After some research, I figured out that the name property was deprecated since 2018. Unfortunately, this information was removed from the Microsoft Graph tab documentation already in 2018. Moreover, at least until end of 2019, the Graph documentation about retrieving tab properties included the name property as part of the response example. According to the Graph tab documentation, this is what the tab request and response used to be:
GET https://graph.microsoft.com/v1.0/teams/{id}/channels/{id}/tabs/{id}
{
  "id": "tabId",
  "name": "My Contoso Tab",
  "teamsAppId": "06805b9e-77e3-4b93-ac81-525eb87513b8",
  "configuration": {
    "entityId": "2DCA2E6C7A10415CAF6B8AB6661B3154",
    "contentUrl": "https://www.contoso.com/Orders/.../tabView",
    "websiteUrl": "https://www.contoso.com/Orders/...",
"removeUrl": "https://www.contoso.com/Orders/.../uninstallTab"
}, "webUrl": "https://teams.microsoft.com/..."
}
I mean, there was no indication that the name property wasn't secure to use!!!

I have already seen one discussion on GitHub about this change! Basically, there are people trying to understand and fix this issue. I hope this blog post brings some clarity to this problem and helps you fixing this bug.

Monday, June 15, 2020

Awarded the Microsoft MVP in Office Development - 2020/2021

I was on a team meeting today, when suddenly the doorbell rang and I started asking myself: Is this the package I have been waiting for or are kids playing ding dong ditch out there again? I decided to open the door and boy... Such a positive surprise! It was the ♫ Mister Postman  (FedEx dude) with a package for me. The content of the package you might ask? Nothing other than the MVP award!

The Most Valuable Professional (MVP) award is a landmark in my career. This is definitely time to say thank you to people who have helped me, motivated me and inspired me over the last few years:

- My Wife: Thank you for being part of my life and for supporting me. This is our achievement!!!
- Yannick Plenevaux: I see you as my mentor. Thank you very much for the support you gave me!
- Chris O'Brien: Although your sessions are always very well attended and you get a lot of questions afterwards, you still find time for every question. Thank you for being such an example and inspiring me to start blogging!
- Vesa Juvonen and Waldek Mastykarz: You guys have been doing an awesome community work. The Microsoft 365 community is very strong because of your commitment. I am very proud to be part of it! #SharingIsCaring
- Jeremy Thake and Paul Schaeflein: Microsoft 365 Developer Podcast is another example of community contribution. Thank you for teaching me and motivating me with your podcast!
- Valo: At the time of this written, we have 11 MVP's working for Valo. The power behind this company is tremendous because we work as a team and everyone is equal. I am very proud to be a member of Valo. Special thanks to Henrik Blรฅfield and my colleagues from Valo Teamwork team. It is a pleasure to work with you guys! I've learned a lot from you ๐Ÿ˜€
- Thanks everyone for all the likes and comments in the social media! You guys rock!!!


Now, it is time to put this MVP award on a special place the kids can't reach and celebrate this achievement with family, friends, colleagues and the community.

Wednesday, May 27, 2020

Speaking at Valo Online Summit EU 2020

I'm looking forward to speaking at Valo Online Summit EU 2020 on June 3-4, 2020.

The Valo Online Summit EU is an event exclusively dedicated to Valo Partners but this year the keynote ending the actual Summit will be open to everyone. Jeff Teper, the father or SharePoint, Corporate Vice President of Microsoft, will speak in this free keynote about the latest innovations and experiences in the Microsoft 365 world on June 4th at 5 p.m. (CET). This is a big opportunity for Valo partners, their customers and other community members to join this free keynote and learn directly from Jeff! 


I will also contribute to the Valo Online Summit's success with a session about "Extending the Valo Teamwork power using SharePoint Site Designs and Site Scripts". Valo Teamwork helps customers to find and manage Microsoft 365 groups, Microsoft teams and SharePoint sites. My session covers an integration between Valo Teamwork and SharePoint Site Designs. The presentation's history is based on the idea of creating project workspaces with predefined configuration.

I am very happy to be a part of the speakers team! Let's get in touch if you're also going to participate in this event!

Event information:

Thursday, May 7, 2020

Identifying if a Teams personal app runs from Teams Web or Teams Desktop Client

I’ve recently built a Teams personal app that required a different authentication mechanism when running from Teams desktop client. It took me a while to figure out how to distinguish between Teams desktop client and Teams web. As far as I’ve researched, there is no supported way yet to clearly identify where a personal app is running. Hence, I decided to write this blog post which shows how to identify if a Teams personal app runs from Teams web or Teams desktop client.



In my case, I tried to embed a SharePoint page as a Teams personal app. This page had a SharePoint Framework web part which did not run from Teams desktop client without a special authentication algorithm. For different reasons, it was not feasible to utilize this new authentication logic in both scenarios: Teams web and Teams desktop client. Well, it was clear. I had to find a way around this!

After intensive platform comparison, I observed two special characteristics. The document interface (loaded web page representation in the browser) and the navigator interface (identity of the browser):

1. document.referrer

When a Teams personal app runs from Teams web, the referrer can contain a value like: https://teams.microsoft.com/_. When the same personal app runs from Teams desktop client the referrer will then be: https://teams.microsoft.com/iframe-container.html. However, the referrer might change depending on operations executed from the embedded page. Hence, the referrer property is not reliable and didn’t help me in that case. 

2. navigator. userAgent

When a Teams personal app runs from Teams desktop client the userAgent always contain the Teams agent. This is a value which consists of Teams/{version} such as Teams/1.3.00.12058. In case of Teams web, the userAgent property doesn't contain the Teams agent. That made the tweak in my case! 


Summary:

Checking if the value "Teams/" existed in the userAgent property, helped me to identify when my personal app was running from Teams web or Teams desktop client. I am constantly looking for an out of the box solution and hope that Microsoft will provide it soon!

I hope this blog post helps you not spending hours trying to understand the uniqueness of those two platforms.

Wednesday, April 29, 2020

Speaking at Microsoft 365 Saturday Madrid Virtual 2020



I'm looking forward to speaking at Microsoft 365 Saturday Madrid Virtual 2020 on April 30, 2020.

Microsoft 365 Saturday Madrid Virtual (M365MAD) was created by the Microsoft 365 community from Madrid as a digital alternative to the famous SharePoint Saturday Madrid which must be postponed to October 2020 because of the current pandemic we are facing worldwide.

M365MAD is an online and free event which will be broadcasted from YouTube. International and local speakers will share their knowledge and experience about different areas of Microsoft 365. For instance, I will contribute to M365MAD 2020 as a speaker with a session about Site Designs and Site Scripts.

I want to thank the organizers for setting up this event and for having me as a speaker.

Related links:
• Agenda: https://microsoft-365-virtual-summit-spain.sessionize.com/schedule
• YoutTube channel: https://www.youtube.com/channel/UCY1cqnq_HqNlV9SGV5VzDjQ

Monday, April 27, 2020

Understanding the TeamsLogon page

Whenever you create a Teams personal app or a Teams tab, a Teams app package will be required. In case of SharePoint Framework (SPFx), SharePoint generates this app package automatically when the app gets synchronized to Microsoft Teams for instance by using the Sync to Teams ribbon button in the SharePoint app catalog. By default, the Teams app package contains the required images that let your app looks shine in Teams, but also a manifest file which contains important configuration about the app.

The generated Teams app package is convenient, but it does have its limitations - for instance it doesn't include localization or other kind of customization you might want to have. The alternative is to generate the app package manually. Microsoft already provides a very good guidance which you can find here. Therefore, I won't cover that in this blog post. Rather, I want to deep dive on the TeamsLogon page (/_layouts/15/TeamsLogon.aspx) which is part of those app manifests. The findings described here are the output of tests I have done on my test environment and on customers feedback.

TeamsLogon's URL structure:

Along with the request URL
https://{teamSiteDomain}{teamSitePath}/_layouts/15/teamslogon.aspx
two query string parameters are required to request the TeamsLogon page successfully:
- spfx
- dest

TeamsLogon page in action:

Let's forget SPFx for a moment! Maybe you just want to display a SharePoint page as a Teams personal app. The mere fact that SharePoint and Teams run in Microsoft 365 doesn't result in bypassing authentication to access SharePoint resources from a Microsoft Teams app. By default, SharePoint pages won't load inside Microsoft Teams apps. It requires additional configuration! Lucky us, since this is why the TeamsLogon page is therefor.

In simple words, the TeamsLogon page is a bridge between Microsoft Teams and SharePoint that makes the access from Microsoft Teams apps to SharePoint possible. Behind the scenes, this page uses an access token generated by the microsoftTeams SDK to authenticate against SharePoint using the SP.OAuth.NativeClient/Authenticate API. A successful POST call to this API, sets the SPOIDCRL cookie which is used for authentication in SharePoint on behalf of the signed in user. After authentication is completed, TeamsLogon page redirects the user to the page specified in the dest (destination) query string parameter. Moreover, depending if the destination page is a classic or a modern one, the spfx query string parameter will apply some layout modifications to the rendered page.

I've created a simple graphic which describes the TeamsLogon page's process:


dest query string parameter in detail:

The query string parameter dest plays a very important role in this authentication process. It contains the destination URL which will be loaded after authentication is completed. In addition to that, the value provided here will be used to build the base URL, which will be used as part of the SP.OAuth.NativeClient/Authenticate request. This is how the logic to build the base URL based on the dest value looks:

var baseUrl = decodeURIComponent(
window.location.href.substring(window.location.href.indexOf(destQueryParam) + destQueryParam.length));

Important: you can add query string parameters to the destination URL. If you want to have more than one query string parameter in the destination URL, it is important to encode the connecting & character. Otherwise, additional query string parameters will be recognized as being part of the TeamsLogon's URL and not as part of the destination page. This is how the encoded & character looks:

https://{teamSiteDomain}{teamSitePath}/_layouts/15/teamslogon.aspx?spfx=true&dest={teamSitePath}?forceLocale={locale}%26theme={theme}

spfx query string parameter in detail:

This parameter renders SharePoint modern pages inside Teams without navigation, footer and header. Basically, it renders only the page's content. Additionally, spfx requires a boolean value which must be always true. Setting it to false, results in the very famous Sorry, something went wrong error which happens when loading the page:


Also, those changes done by the spfx query string parameter won't apply to classic pages. Basically, navigation, header, footer etc. won't be hidden. If only the page's content should be displayed, additional CSS hack is required. I would personally advise against that and suggest you to use only modern pages inside Teams apps. This is what a classic page looks like when loaded inside Teams:


Final words:

Thank you for your reading this blog post. I hope it clarifies the meaning of the TeamsLogon page!

Thursday, April 16, 2020

List of errors you might face in the SharePoint API Management page

The SharePoint API Management page allows administrators to manage (approve, reject and remove) permissions requested/granted at tenant-level. However, the experience of error in this SharePoint admin area is simply terrible. From my point of view, a good error message should be an educational experience. Unfortunately, this is exactly the opposite in the SharePoint API Management page ๐Ÿ˜”

This blog post is a continuation of my previous post about a specific error message from the API management page which was causing confusion out there. Since Microsoft hasn't enhanced the error message's content and I have faced other encrypted errors when using the API management page, I decided to list all errors from the API management page in a dedicated blog post which I can maintain over the time.

The list below describes the possible error messages that you might get depending on the operation you want to execute. I also added possible solutions in order to help you getting rid of those inconvenient errors:

Case: 
Approve a requested permission as a SharePoint administrator
Error message:
[HTTP]:500 - [CorrelationId]:53ee2d9f-a0f9-2000-078d-b1ec5183945b [Version]:16.0.0.20001
Solution:
The logged in user must be a global administrator in order to approve requested permissions

Case: 
Reject a requested permission as a SharePoint administrator
Error message:
[HTTP]:403 - [CorrelationId]:5cee2d9f-801d-2000-6cd2-3db457dd64f0 [Version]:16.0.0.20001
Solution:
The logged in user must be a global administrator in order to reject requested permissions

Case: 
Remove a granted permission as a SharePoint administrator
Error message:
Insufficient privileges to complete the operation
Solution:
The logged in user must be a global administrator in order to remove granted permissions

Case: 
Approve a requested permission
Error message:
[HTTP]:400 - [CorrelationId]:a340499f-6054-2000-7120-91cbdfcc05f3 [Version]:16.0.0.20001
Solution:
Ensure the permission/scope wasn't misspelled. E.g.: A permission such as User.ReadWrit.All isn't valid! In this case, the requested permission would be User.ReadWrite.All. Consider correcting the permission and retrying the operation. Also, have a look at the Microsoft Graph permissions reference page for information about supported permissions: https://docs.microsoft.com/en-us/graph/permissions-reference

Case: 
Approve a requested permission
Error message:
[HTTP]:400 - [CorrelationId]:223e499f-00e1-2000-03d0-e19a7de3a6c5 [Version]:16.0.0.20001
Solution:
Ensure the API name/resource wasn't misspelled. E.g.: An API name such as Microsft Graph isn't valid! In this case, the specific resource secured with Azure AD would be Microsoft Graph. However, it is more common to face this error when dealing with  Azure AD enterprise applications. Consider correcting the API name and retrying the operation.

Case: 
Approve a requested permission
Error message:
[HTTP]:400 - [CorrelationId]:a53f499f-3046-2000-6cd2-30c17ac0aab4 [Version]:16.0.0.20001
Solution:
Ensure the API name isn't the application ID or the object ID of the resource secured with Azure AD. Only the display name of the application is supported! Consider correcting the API name and retrying the operation.


Helpful link:

This Microsoft documentation explains very well how to connect to Azure AD-secured APIs. It explains this process based on SharePoint Framework solutions. Check it out for more information!

Wednesday, March 11, 2020

Quick Tip: Understanding the unknown user issue in Microsoft Teams


Yesterday, some Office 365 customers started seen an unwelcome user in their Teams. Along with existing owners, an Unknown User was automatically added to the Team as an owner what was/is causing confusion!

In this case, the Unknown User represents the service principal account that was used to create the Team. So far, it wasn't displayed in Teams as a member or owner, but after a Microsoft update things badly changed ๐Ÿ™

You can image the side affects of this issue: community reaction, customers opening tickets and so on. Definitely, something we want get fixed as soon as possible. However, Microsoft quickly reacted to this issue. If you have a look at your tenant's Service Health page, you will notice an advisory about the Unknown user in Teams case (TM206109). Consider having a look at it if you are looking for detailed information about it.

Long story short, the problem was caused by some recently update (code change) done by Microsoft. According to the message from the Service Health, they are now working on a hotfix for this issue. Here is how the advisory message looks:


It's comforting to know that Microsoft is already taking care of this glitch. I'm looking forward to getting that service principal account out of my Teams ๐Ÿ˜Š


Edited: 13.03.2020:

According to Microsoft, they've completed reverting the code change and confirmed that impact has been mitigated.

If you are still seen the unknown user in Microsoft Teams, consider contacting the Microsoft support.

Sunday, February 23, 2020

Quick tip: Improving performance when retrieving a user using Microsoft Graph

There are different ways to look for a user using Microsoft Graph. In this blog post, I will cover three different approaches which might impact performance of your application. Especially, if you are dealing with Office 365 tenants which has thousands of users (40k, 500k, more?):

◾ Get a user based on the user principal name or user ID:
E.g.: https://graph.microsoft.com/v1.0/users/adams@m365x214355.onmicrosoft.com
◾ Filter by user principal name or user ID:
E.g.: https://graph.microsoft.com/v1.0/users?$filter=userPrincipalName eq 'adams@m365x214355.onmicrosoft.com'
◾ Get all users using paging then do conditional check
E.g: https://graph.microsoft.com/v1.0/users

Let's take a look at the advantage and disadvantage of the API to retrieve a user based on an identifier. E.g.: https://graph.microsoft.com/v1.0/users/adams@m365x214355.onmicrosoft.com

๐Ÿ’š Fast! I've tested this API in different Office 365 tenants containing 40k to approx. 500k users. There is no significant delay.
๐Ÿ’” This API throws an exception if the specified user doesn't exist in the Office 365 tenant! That can become a big problem if you use this API to search for a user based on user input. If the value to look for was misspelled it throws an exception. That is actually a good way for the API to communicate that the user doesn't exist, but you have to handle this exception. Otherwise, your application will break. BTW, this is how the exception looks:

GET: https://graph.microsoft.com/v1.0/users/adams@m365x214355.onmicrosoft.co

{
    "error": {
        "code": "Request_ResourceNotFound",
        "message": "Resource 'adams@m365x214355.onmicrosoft.co' does not exist or one of its queried reference-property objects are not present.",
        "innerError": {
            "request-id": "e4b98e04-cc46-4ea1-920d-13c61b6ab88f",
            "date": "2020-02-17T12:18:12"
        }
    }
}

This is how error handling could be implemented. In this example, I'm using the Graph SDK and C#:

Additionally, we can look for a user by using the OData query paramenter "filter". E.g.: https://graph.microsoft.com/v1.0/users?$filter=userPrincipalName eq 'adams@m365x214355.onmicrosoft.com' Here the advantage and disadvantage of this API:

๐Ÿ’š Good internal error handling! It won't throw an exception if the user wasn't found. Instead it returns an empty array. The Graph SDK returns null
๐Ÿ’š Fast! I've tested this API in different Office 365 tenants containing 40k to approx. 500k users. There is no significant delay! It was a couple of milliseconds faster than the first approach, but that is definitely not game changing
๐Ÿ’” Nothing! ๐Ÿ˜€ At least I didn't find anything negative during my tests/work with this approach

Now, lets have a look at the approach "Get all users using paging then do conditional check".

๐Ÿ’š Flexibility! You have full control over the objects. For instance, it is possible to create complex conditional checks
๐Ÿ’” Performance! This high level of flexibility has a very high price. During my tests, it took one minute to go through all users in a tenant with 40k users. In a tenant with 500k users, it took more than 20 minutes to walk through all users. For sure, you could and should just stop the loop once you have found the specified user which will probably reduce the amount of time. Since this API doesn't return all tenant users within the same call, you may have to do several calls to find the user you are looking for which can impact performance negatively


Summary

If you want to search for a user, consider using the first or second approach since they are good about performance. That might be cases where the "Get all users using paging" approach will be useful. But if you don't need it, consider replacing this logic with one of the other approaches. Otherwise, your application will suffer performance problems when installed in tenant with big amount of users.

Sunday, February 2, 2020

Speaking at SharePoint Saturday Bremen 2020



I'm looking forward to speaking at SharePoint Saturday in Bremen - Germany on February 29, 2020.

I've already had the chance to attend the SPS Bremen 2018. The event's venue is just unique since it takes place on a vintage car gallery. Along with expert sessions and networking you also have the opportunity to enjoy nice Porsches and other very special cars. The atmosphere inside this place is simply magic.

This year we have a good number of German and English sessions, held by international and local speakers. For instance, I will held a session about my experience in SharePoint Site Designs and Site Scripts. I'm looking forward to seeing you there ๐Ÿ˜€

Thanks Daniel Wessels, Marco Fischer and Tomislav Karafilov for giving me the chance to contribute to this awesome event as a speaker. It’s going to be my fourth speech at a SharePoint/Office 365 Saturday event. Let's get in touch if you're also going to participate in SPS Bremen!

Bremen, I'm coming ๐Ÿ˜‰


Event information:
Home page - https://www.spsevents.org/event/bremen2020/
Schedule - https://www.spsevents.org/event/bremen2020/schedule/
Registration - https://www.eventbrite.de/e/sharepoint-saturday-north-germanybremen-2020-registration-78550000165

Wednesday, January 22, 2020

Quick Tip: Getting [HTTP]:500 when approving permissions in the API Management page

This blog post covers an issue that I've followed on Github. Basically, a SharePoint user faced this error message [HTTP]:500 - [CorrelationId]:4e7c8a66-9bbe-4c66-95f1-bc4afd1a51ed [Version]:16.0.0.19708 while trying to approve permissions in the API Management page which is located in the SharePoint Admin Center.

Background information: The API Management page allows administrators to manage (approve, reject and remove) permissions requested/granted at tenant-level.

BUT, what kind of administrators are we talking about? Can a SharePoint administrator manage those requested permissions? Or are only Global administrators allowed to do that?

In that issue, the permission level (SharePoint administrator) of the logged in user was causing the problem. Although SharePoint administrators have access to the API Management page, they can't approve, reject or remove those requested/granted permissions. You must be a Global Administrator if you want to manage API permissions at tenant-level. From my point of view, that makes a lot of sense, since you are dealing here with permissions to access resources that goes beyond SharePoint. Nevertheless, the [HTTP]:500 error message (just to remember, HTTP 500 stands for internal server error) confuses people and should be replaced with an user friendly feedback message or a SharePoint administrator should not be able to access the API Management page.

Depending on the operation you are trying to execute, you will face a different error message. I've listed below the possible error messages for the different cases:

• Approve a requested permission as a SharePoint administrator[HTTP]:500 - [CorrelationId]:53ee2d9f-a0f9-2000-078d-b1ec5183945b [Version]:16.0.0.19527
• Reject a requested permission as a SharePoint administrator[HTTP]:403 - [CorrelationId]:5cee2d9f-801d-2000-6cd2-3db457dd64f0 [Version]:16.0.0.19527 - Access denied. You do not have permission to perform this action or access this resource
• Remove a granted permission as a SharePoint administratorInsufficient privileges to complete the operation

As you can see, the operations "reject" and "remove" already output a more informative error message. That could also be enhanced with "You must be a Global Administrator if you want to manage API permissions at tenant-level". The operation "approve" outputs an internal server error message which makes no sense in this case. The very good part of this history is that this issue has already reached Microsoft since Vesa Juvonen (Principal Program Manager from SharePoint Engineering) has helped solving this problem. I believe that Microsoft will provide a better and consistent error message experience across all the operations that can be executed from the API Management page. Thanks Vesa for being so fast with the support!

Link to the issue on Github.

Saturday, January 11, 2020

Quick Tip: Three PowerShell commands that help you keeping Site Designs and Site Scripts limitation under control

The management of Site Designs and Site Scripts is a really important aspect to consider in SharePoint. Especially because of its limitation! It means that you can't have more then 100 Site Designs and 100 Site Scripts in your Office 365 tenant. If you have reached the limitation and you try to add new Site Designs or Site Scripts to your environment, SharePoint returns error messages that aren't optimal for a good user experience. In other words, depending on your IT background you are not going to understand it immediately. Before you start getting those exceptions, you should consider deleting unused Site Designs or Site Scripts if you no longer need them. Here is how the exception looks when reached the amount of Site Designs or Site Scripts allowed per each tenant:

Add-SPOSiteScript: Specified argument was out of the range of valid values.

Add-SPOSiteDesign: Specified argument was out of the range of valid values.

In this blog post, I just want to show short PowerShell scripts that I've been using to avoid the wild goose chase with my Site Designs and Site Scripts.

Note that I'm using the SharePoint Online Management Shell for the examples below.

1. Know how many Site Designs and Site Scripts you have:

(Get-SPOSiteDesign | Measure).Count
(Get-SPOSiteScript | Measure).Count

2. Delete a Site Design or a Site Script based on its unique identifier:

Remove-SPOSiteDesign -Identity $siteDesignId
Remove-SPOSiteScript -Identity $siteScriptId

3. Delete all Site Designs or Site Scripts at once:

Get-SPOSiteDesign | foreach { Remove-SPOSiteDesign -Identity $_.Id }
Get-SPOSiteScript | foreach { Remove-SPOSiteScript -Identity $_.Id }

That's it for today ๐Ÿ˜ƒ Hope this blog post helps you keeping the Site Designs and Site Scripts' limitation under control!

Friday, January 3, 2020

Quick Tip: Length limits for Office 365 Group properties

Creating or updating Office 365 Groups can become a guessing game if you get the InvalidLength error code ๐Ÿ˜ฎ First of all, it is really nice that Microsoft has started providing those details in the error response. I believe, they will improve the details information over the time, so it will be easier to understand the reason for bad requests. Anyway, the InvalidLength error message doesn't indicate the supported length for the Office 365 group properties. It results in trial and error sessions in order to figure out what the supported length limit is.

These are the error messages that you get, when trying to create a group which contains properties with invalid length:

• Invalid value specified for property 'description' of resource 'Group'.
• Invalid value specified for property 'displayName' of resource 'Group'.
• Invalid value specified for property 'mailNickname' of resource 'Group'.

After doing some research, I listed below the supported length for those properties:

• description: 1024 characters
 displayName: 256 characters
• mailNickname: 64 characters

That's it for today! I hope this blog post helps you to bypass those kind of exceptions!