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!