Microsoft 365 Development – Approach to build application

In recent time, Office 365 has taken a leap and became Microsoft 365. This is a bigger platform for Microsoft to sell and provide better features and benefits to customers.

With the hardware and software coming together as packaged offering, customers and end users can get more benefits from this solution. The developers in Office 365 world can utilize the capabilities of Azure to build and integrate solutions with Office 365.

As you know there are many possible solution to a problem and the best solution depends on the scenario you have. In SharePoint words – It depends :).

Now, what is best solution in my scenario. Well you have to define and develop as how you would like to develop the solution now and grow in future. I am going to use term Microservices from Azure to define as how to build better solutions for Office 365.

From Microsoft Microservices pages, the difference between traditional approach of layers v/s the Microservice application approach as below –

From <https://azure.microsoft.com/en-au/blog/microservices-an-application-revolution-powered-by-the-cloud/>

As on Microsoft Page, the definition of Microservice is –

Microservice applications are composed of small, independently versioned, and scalable customer-focused services that communicate with each other over standard protocols with well-defined interfaces.

Key characteristics of micro-services design are –

  1. Written in any programming language and use any framework
  2. Allows code and state to be independently versioned, deployed, and scaled
  3. Interacts with other microservices over well-defined interfaces and protocols
  4. Remains consistent and available in the presence of failures

Now, you might be wondering as how this applies in Office 365 Development world.

Let’s take the example of world known problem of Office 365 Groups governance. Organizations would like to use governance on creation and usage of Office 365 groups. The major concern areas were/are –

  1. Who can create groups?
  2. Naming convention of group.
  3. Ownership of group and defining members
  4. Archiving office 365 groups

Apart from that, Since Office 365 groups a mix of services including OneNote, Planner, SharePoint and more. The specific concern is around SharePoint site setup and use. SharePoint has been in market and popular for collaboration purpose. Organization may want to control the things on SharePoint site also. To provide solution which meet all above concern, you can create a provisioning solution which could look like below –

Options
SharePoint List –> New Item event to trigger Azure Web Job for group provisioning –> Create group and manage SharePoint site
SP List –> Flow approval on item create –> Item approve –> Flow to create group –> Add owner and members
SP List –> Flow to trigger Azure function –> Azure Function to create groups –> return the Group to Flow –> Call Another Azure Function to manage members –> Notify requestor via Flow
SP List –> Flow to push request in Azure Queue –> Azure Function to read Queue and process request (this could be 1 for Group creation and 1 for adding members) –> Notify the completion–> Notification via Graph Email via Azure Function to requestor.
SPFx web part –> Provision using Graph Endpoint –> Provide output of operation to user

As you see, for 1 problem, there could be multiple solutions and each solution could be connected via multiple channels. To me, this creates services on demand and I can approach multiple services on demand basis to build my solution. The solution building blocks provide better re-usability and independence on calling layer to adapt to Micro-services design/concept.

Please let me know your comments as how you think Office 365 Dev is taking shape in current time and where will it land in future.

Advertisements

Microsoft 365 Roadmap – RSS Feed improvement Feedback

Recently, Microsoft moved Office 365 roadmap to merge with other products and bring under one umbrella of Microsoft 365 which include Windows and EMS.

Microsoft made the roadmap available on URL – https://www.microsoft.com/en-us/microsoft-365/roadmap?filters=.

The purpose of M365 roadmap is to let everyone know what’s cooking inside MS and what to expect as change. Many organizations are facing the challenge to know the new feature release and how to prepare for rolling out changes.

Microsoft promised to enable the RSS feed for M365 Roadmap (which was missing from O365 roadmap) so that you can consume RSS feed easily and be ready for what’s coming.

I was able to find the behind API behind the scene and was able to use it in previous blog post to read the content and store in a SharePoint list. Today, I found that RSS link is also working and you can access the RSS feed from https://www.microsoft.com/en-us/microsoft-365/RoadmapFeatureRSS .

When I was reading the information and comparing with what’s published v/s what’s in feed item, my observation is as below –

Item in roadmap via web Item in RSS feed

As you can view the information highlighted in above screens, I will details it out as below –

  1. In RSS feed, the rather having items directly, it has a channel and then items (which are again flat with other info). I am not sure What’s the intent behind having a channel as root item in RSS feed behind. May be publishing more roadmap of other products via same feed.
  2. On web, you can read status (in above examples it is ‘In Development’). While the RSS feed item does not have proper node info about this status field. It is shown as category.
  3. Each feature could impact more than one product at any time. The related product information is available on web as Products or Tags information. But again in the RSS feed, this information is again listed as category node. Which is repeating and could be confusing when user is processing this using any tool. If Microsoft can fix the first category as Status and a collection of Tags/Products together so that it is easy to process using tools like Microsoft Flow.
  4. The other important information is when this new features is getting released. This information is available on web as Release information. This is very important for Change managers, Trainers or Communication manager who prepare for upcoming changes. In RSS feed this information is missing completely which could be more costly to organizations. Enabling or making this information in RSS feed will be super useful for users.

These are my personal observations and making this recommendation based on my previous work experience and interaction with customers.

Microsoft Flow – Read Microsoft 365 Roadmap and manage changes

Context –

For any organization using Office 365, the biggest challenge is identifying upcoming changes and preparing for the adoption of it. The Microsoft’s biggest productivity platform is a cloud bases SaaS (Software as a Service) offering. This means that you can control for a limited time to disable features. Microsoft is doing more investment with AI, Machine Learning and Graph connected services to make smart workplace.

How does Microsoft share update?

Microsoft generally release blog updates about new capabilities. Based on the Microsoft/Office 365 ring/release cycle your organisation has opted in, you will get the features released in your tenant. Microsoft has been changing product hierarchy and shuffled the announcement from central blog to multiple product blogs. It’s not practical for any organization to follow multiple channels to know about changes.

To make it easy, Microsoft provided two options as below –

Option 1: Sending message in message centre in Office 365 tenant admin centre. Refer to this Flow Blog to use this method.

Option 2: Follow the roadmap to know what is being planned.

Using Flow to track any changes in roadmap information:

I created the following flow to get the road-map items to SharePoint List. Why SharePoint list?

You could track the changes happening in the road map items and push the changes further. Microsoft Flow fully support triggers and actions on SharePoint list, which further can be used to send email or share as conversation in Teams.

I use a SharePoint list as below to store the content of this Flow.

Column {click to edit) Type
Title Single line of text
Feature ID Single line of text
Description Multiple lines of text
Status Choice
More Info Hyperlink or Picture
Estimated Release Single line of text
Added to Roadmap Date and Time
Last Modified Date and Time
Roadmap Status Single line of text
Tags Single line Of text

For this Flow, you don’t need authentication or any specific setup.

Conclusion and Next Steps –

Now you have control on source information using Flow for Office 365, you can build you internal Change and Adoption space. Let me know if you have any specific Flow question.

Microsoft Graph API – Difference between Teams and Groups end points

I have been trying to map my brain thinking to talk what I have learnt around Microsoft Teams and Group API. Recently microsoft graph team has updated a lot of end points. If you look at the change log URL – https://developer.microsoft.com/en-us/graph/docs/concepts/changelog#july-2018, you will find that a lot of beta end points which were created on groups like channels, are now available around teams.

So what’s the problem?

There is no problem. I say this is great to fix end points and make them according to what make sense.

You, as developer or end user, might know by now that Groups and Teams share common group. Let’s see how Microsoft Graph API handle this.

Difference –

Microsoft Teams is a chat based workspace. Based on how it is designed, there are channels to separate conversations. The conversations are Chat Threads which has root message and replied messages. Each of the message is Chat Message.

While on the other hand, Groups are designed to work with Exchange. Said that, Groups messages are Conversation thread and include posts also. Groups does not have container to separate conversations like channels.

Similarities –

Groups and Teams are two sides of one coin. They both share Notes, Plans, Drive (SharePoint site), Calendar and photo together.

When there is so much similarity then why am I writing this blog?

The common services works with both products, but in Graph API following are the key observations –

  1. Teams and Group share same ID.
  2. Microsoft Teams end point has
    1. Channel –> Chat thread –> Chat message
    2. Apps
    3. Operations [new end point without any documentation]
  3. Groups has end points for below services and more [more part cover Groups specific end points]
    1. Notes
    2. Drive
    3. Plans
    4. Calendar
    5. Photo

The following image shows how the API endpoints are designed and how the messages have similarities in two systems.

Teams With Graph

Microsoft Graph API – User End Point v1.0 vs Beta

I was working recently on user endpoint in Microsoft Graph API. As a general rule, I was working with v1.0 or General Available end point. Then I switched to see what’s new in beta end point. To my surprise, it has tons of information difference between both end points. The following table show the properties available in each end point –

V1.0 – https://graph.microsoft.com/v1.0/users Beta – https://graph.microsoft.com/beta/users
{
“id”: “87d349ed-44d7-43e1-9a83-5f2406dee5bd”,
“businessPhones”: [
“+1 425 555 0109”
],
“displayName”: “Adele Vance”,
“givenName”: “Adele”,
“jobTitle”: “Product Marketing Manager”,
“mail”: “AdeleV@M365x214355.onmicrosoft.com”,
“mobilePhone”: null,
“officeLocation”: “18/2111”,
“preferredLanguage”: “en-US”,
“surname”: “Vance”,
“userPrincipalName”: “AdeleV@M365x214355.onmicrosoft.com”
}
{
“id”: “87d349ed-44d7-43e1-9a83-5f2406dee5bd”,
“deletedDateTime”: null,
“accountEnabled”: true,
“ageGroup”: null,
“businessPhones”: [
“+1 425 555 0109”
],
“city”: “Bellevue”,
“createdDateTime”: “2017-07-29T03:03:26Z”,
“companyName”: null,
“consentProvidedForMinor”: null,
“country”: “United States”,
“department”: “Sales & Marketing”,
“displayName”: “Adele Vance”,
“employeeId”: null,
“givenName”: “Adele”,
“jobTitle”: “Product Marketing Manager”,
“legalAgeGroupClassification”: null,
“mail”: “AdeleV@M365x214355.onmicrosoft.com”,
“mailNickname”: “AdeleV”,
“mobilePhone”: null,
“onPremisesDomainName”: null,
“onPremisesImmutableId”: null,
“onPremisesLastSyncDateTime”: null,
“onPremisesSecurityIdentifier”: null,
“onPremisesSamAccountName”: null,
“onPremisesSyncEnabled”: null,
“onPremisesUserPrincipalName”: null,
“passwordPolicies”: “DisablePasswordExpiration”,
“passwordProfile”: null,
“officeLocation”: “18/2111”,
“postalCode”: “98004”,
“preferredDataLocation”: null,
“preferredLanguage”: “en-US”,
“proxyAddresses”: [
“SMTP:AdeleV@M365x214355.onmicrosoft.com”
],
“refreshTokensValidFromDateTime”: “2017-09-12T21:08:09Z”,
“showInAddressList”: null,
“imAddresses”: [
“AdeleV@M365x214355.onmicrosoft.com”
],
“isResourceAccount”: null,
“state”: “WA”,
“streetAddress”: “205 108th Ave. NE, Suite 400”,
“surname”: “Vance”,
“usageLocation”: “US”,
“userPrincipalName”: “AdeleV@M365x214355.onmicrosoft.com”,
“userType”: “Member”,
“extension_fe2174665583431c953114ff7268b7b3_Education_ObjectType”: “teacher”,
“extension_fe2174665583431c953114ff7268b7b3_Education_TeacherNumber”: “106”,
“assignedLicenses”: [
{
“disabledPlans”: [],
“skuId”: “c7df2760-2c81-4ef7-b578-5b5392b571df”
}
],
“assignedPlans”: [
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “Adallom”,
“servicePlanId”: “8c098270-9dd4-4350-9b30-ba4703f3b36b”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “SharePoint”,
“servicePlanId”: “e95bec33-7c88-4a70-8e19-b10bd9d0c014”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “SharePoint”,
“servicePlanId”: “5dbe027f-2339-4123-9542-606e4d348a72”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “exchange”,
“servicePlanId”: “efb87545-963c-4e0d-99df-69c6916d9eb0”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “MicrosoftCommunicationsOnline”,
“servicePlanId”: “0feaeb32-d00e-4d66-bd5a-43b5b83db82c”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “MicrosoftOffice”,
“servicePlanId”: “43de0ff5-c92c-492b-9116-175376d08c38”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “YammerEnterprise”,
“servicePlanId”: “7547a3fe-08ee-4ccb-b430-5077c5041653”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “RMSOnline”,
“servicePlanId”: “bea4c11e-220a-4e6d-8eb8-8ea15d019f90”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “ProjectWorkManagement”,
“servicePlanId”: “b737dad2-2f6c-4c65-90e3-ca563267e8b9”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “PowerBI”,
“servicePlanId”: “70d33638-9c74-4d01-bfd3-562de28bd4ba”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “MicrosoftCommunicationsOnline”,
“servicePlanId”: “3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “MicrosoftCommunicationsOnline”,
“servicePlanId”: “4828c8ec-dc2e-4779-b502-87ac9ce28ab7”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “Sway”,
“servicePlanId”: “a23b959c-7ce8-4e57-9140-b90eb88a9e97”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “exchange”,
“servicePlanId”: “34c0d7a0-a70f-4668-9238-47f9fc208882”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “exchange”,
“servicePlanId”: “9f431833-0334-42de-a7dc-70aa40db46db”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “exchange”,
“servicePlanId”: “4de31727-a228-4ec3-a5bf-8e45b5ca48cc”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “TeamspaceAPI”,
“servicePlanId”: “57ff2da0-773e-42df-b2af-ffb7a2317929”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “PowerAppsService”,
“servicePlanId”: “9c0dab89-a30c-4117-86e7-97bda240acd2”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “ProcessSimple”,
“servicePlanId”: “07699545-9485-468e-95b6-2fca3738be01”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “Deskless”,
“servicePlanId”: “8c7d2df8-86f0-4902-b2ed-a0458298f3b3”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “exchange”,
“servicePlanId”: “8e0c0a52-6a6c-4d40-8370-dd62790dcd70”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “MicrosoftStream”,
“servicePlanId”: “6c6042f5-6f01-4d67-b8c1-eb99d36eed3e”
},
{
“assignedDateTime”: “2017-07-29T03:03:25Z”,
“capabilityStatus”: “Enabled”,
“service”: “OfficeForms”,
“servicePlanId”: “e212cbc7-0961-4c40-9825-01117710dcb1”
}
],
“deviceKeys”: [],
“onPremisesExtensionAttributes”: {
“extensionAttribute1”: null,
“extensionAttribute2”: null,
“extensionAttribute3”: null,
“extensionAttribute4”: null,
“extensionAttribute5”: null,
“extensionAttribute6”: null,
“extensionAttribute7”: null,
“extensionAttribute8”: null,
“extensionAttribute9”: null,
“extensionAttribute10”: null,
“extensionAttribute11”: null,
“extensionAttribute12”: null,
“extensionAttribute13”: null,
“extensionAttribute14”: null,
“extensionAttribute15”: null
},
“onPremisesProvisioningErrors”: [],
“provisionedPlans”: [
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “MicrosoftCommunicationsOnline”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “MicrosoftCommunicationsOnline”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “SharePoint”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “SharePoint”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “MicrosoftCommunicationsOnline”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “exchange”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “exchange”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “exchange”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “exchange”
},
{
“capabilityStatus”: “Enabled”,
“provisioningStatus”: “Success”,
“service”: “exchange”
}
]
}

As you notice, there are additional set of properties available. I can group them up in below headings and then they make perfect sense to be present with each user –

  1. Personal Details
  2. Organization details
  3. Subscription SKUs assigned
  4. On-premises Extension attributes
  5. Cloud services provisioned

Looking at the properties available in Graph Beta end point, I can certainly say that based user profiles properties, you can generate content on demand using the properties. This could help in building a better modern workplace personalised to user based on properties value.

Microsoft Flow – JSON Hack with Graph API data

I was working with Flow on a specific scenario and I found some hacks to be played when you are doing JSON parsing and looking to use that data in subsequent actions.

Scenario – I am reading Azure AD users via MS Graph API to keep a list of profile in any other system, let’s say in a SharePoint List.

Solution Approach – Build a Flow which uses Graph API to read all users.
Graph API URL – https://graph.microsoft.com/v1.0/users
To build a Flow with Graph API, please refer to video – Flow With Microsoft Graph API by Ashish Trivedi
SharePoint List setup – Create a custom list with additional columns as defined below

Column Name Column Type
Title Single line of text
First Name Single line of text
Last Name Single line of text
Job Title Single line of text
Display Name Single line of text
User Principal Name Single line of text

Issue(s) – When you hit the API in Graph Explorer (https://developer.microsoft.com/en-us/graph/graph-explorer), you can see the value returned. The same values will be returned in Flow also. Now, when you use action JSON parsing, based on the blog, first returned JSON object is used to template the remaining JSON model. And this is the PROBLEM of FLOW.
Case 1: the first object has all properties, while the next one does not have all properties values. This causes Flow to fail when Flow reads second object. Error Message will appear as ‘”message”: “Invalid type. Expected String but got Null.”‘
Case 2: Now, let’s give the worst case scenario in sample payload to generate the schema for the JSON. When you do that, it parses the JSON well and can read all values. When I did this last night, I was not able to select the parsed JSON properties against SP list item properties. But today while writing this blog, I can’t replicate that. Very good work Flow team.

Case 1: When first object has all property values Case 2: when first object does not have all property values
{
“type”: “object”,
“properties”: {
“@@odata.context”: {
“type”: “string”
},
“value”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“id”: {
“type”: “string”
},
“businessPhones”: {
“type”: “array”,
“items”: {
“type”: “string”
}
},
“displayName”: {
“type”: “string”
},
“givenName”: {
“type”: “string”
},
“jobTitle”: {
“type”: “string”
},
“mail”: {
“type”: “string”
},
“mobilePhone”: {
“type”: “string”
},
“officeLocation”: {},
“preferredLanguage”: {
“type”: “string”
},
“surname”: {
“type”: “string”
},
“userPrincipalName”: {
“type”: “string”
}
},
“required”: [
“id”,
“businessPhones”,
“displayName”,
“givenName”,
“jobTitle”,
“mail”,
“mobilePhone”,
“officeLocation”,
“preferredLanguage”,
“surname”,
“userPrincipalName”
]
}
}
}
}
{
“type”: “object”,
“properties”: {
“@@odata.context”: {
“type”: “string”
},
“value”: {
“type”: “array”,
“items”: {
“type”: “object”,
“properties”: {
“id”: {
“type”: “string”
},
“businessPhones”: {
“type”: “array”
},
“displayName”: {
“type”: “string”
},
“givenName”: {},
“jobTitle”: {},
“mail”: {},
“mobilePhone”: {},
“officeLocation”: {},
“preferredLanguage”: {},
“surname”: {},
“userPrincipalName”: {
“type”: “string”
}
},
“required”: [
“id”,
“businessPhones”,
“displayName”,
“givenName”,
“jobTitle”,
“mail”,
“mobilePhone”,
“officeLocation”,
“preferredLanguage”,
“surname”,
“userPrincipalName”
]
}
}
}
}

Work around –

To use best of both cases, you should apply two steps clean up –

  1. Remove the type value within the properties, so that the Flow does not know what is returned type, text or null.
  2. Remove fields from required section. Just keep the fields like ID which will be present always.

This helped me to get past all hurdles and I can get all User profile values in SP list using Flow.

[I will write about the difference between v1 and beta endpoint of Graph User API endpoint this week. Watch this space.]

WPF Application with Microsoft Graph API

As per my previous blog, we can easily integrate the Graph API in Windows Forms application.

In this article, I am going to select WPF application and connect with Graph API. The purpose of connecting with Graph API is that, it can provide a lot of information and intelligence. Apart from that, if you have Azure AD secured APIs, you can call those APIs using the authentication token. This will help to enable security in your application and service calls.

We are going to use the authentication with Azure AD v2 endpoint, which support authentication from consumer and commercial accounts using same code base. MSAL manages caching and refreshing access tokens for you, so that your application doesn’t need to.

Let’s build the sample step by step.

  1. In Visual Studio, select File > New > Project.Under Templates, select Visual C#.
  2. Select WPF App or WPF Application, depending on the version of Visual Studio version you’re using.
  3. Add MSAL to project by using Microsoft.Identity.Client nuget package including pre-release version.
  4. Adding Code to project –
  5. App.xaml.cs

    using Microsoft.Identity.Client;

    public partial class App : Application{

    private static string ClientId = “your_client_id_here”;

    public static PublicClientApplication PublicClientApp = new PublicClientApplication(ClientId);

    }

  6. MainWindow.xaml

  7. MainWindow.xaml.cs

    using Microsoft.Identity.Client;

    public partial class MainWindow : Window

    {

    string _graphAPIEndpoint = “https://graph.microsoft.com/v1.0/me“;

    string[] _scopes = new string[] { “user.read” };

    public MainWindow()

    {

    InitializeComponent();

    }

    private async void CallGraphButton_Click(object sender, RoutedEventArgs e)

    {

    AuthenticationResult authResult = null;

    try

    {

    authResult = await App.PublicClientApp.AcquireTokenSilentAsync(_scopes, App.PublicClientApp.Users.FirstOrDefault());

    }

    catch (MsalUiRequiredException ex)

    {

    System.Diagnostics.Debug.WriteLine($”MsalUiRequiredException: {ex.Message}”);

    try

    {

    authResult = await App.PublicClientApp.AcquireTokenAsync(_scopes);

    }

    catch (MsalException msalex)

    {

    ResultText.Text = $”Error Acquiring Token:{System.Environment.NewLine}{msalex}”;

    }

    }

    catch (Exception ex)

    {

    ResultText.Text = $”Error Acquiring Token Silently:{System.Environment.NewLine}{ex}”;

    return;

    }

    if (authResult != null)

    {

    ResultText.Text = await GetHttpContentWithToken(_graphAPIEndpoint, authResult.AccessToken);

    DisplayBasicTokenInfo(authResult);

    this.SignOutButton.Visibility = Visibility.Visible;

    }

    }

    }

    public async Task GetHttpContentWithToken(string url, string token)

    {

    var httpClient = new System.Net.Http.HttpClient();

    System.Net.Http.HttpResponseMessage response;

    try

    {

    var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);

    request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(“Bearer”, token);

    response = await httpClient.SendAsync(request);

    var content = await response.Content.ReadAsStringAsync();

    return content;

    }

    catch (Exception ex)

    {

    return ex.ToString();

    }

    }

    private void SignOutButton_Click(object sender, RoutedEventArgs e)

    {

    if (App.PublicClientApp.Users.Any())

    {

    try

    {

    App.PublicClientApp.Remove(App.PublicClientApp.Users.FirstOrDefault());

    this.ResultText.Text = “User has signed-out”;

    this.CallGraphButton.Visibility = Visibility.Visible;

    this.SignOutButton.Visibility = Visibility.Collapsed;

    }

    catch (MsalException ex)

    {

    ResultText.Text = $”Error signing-out user: {ex.Message}”;

    }

    }

    }

    private void DisplayBasicTokenInfo(AuthenticationResult authResult)

    {

    TokenInfoText.Text = “”;

    if (authResult != null)

    {

    TokenInfoText.Text += $”Name: {authResult.User.Name}” + Environment.NewLine;

    TokenInfoText.Text += $”Username: {authResult.User.DisplayableId}” + Environment.NewLine;

    TokenInfoText.Text += $”Token Expires: {authResult.ExpiresOn.ToLocalTime()}” + Environment.NewLine;

    TokenInfoText.Text += $”Access Token: {authResult.AccessToken}” + Environment.NewLine;

    }

    }

The authentication flow of this application is –