Showing posts with label microsoft graph. Show all posts
Showing posts with label microsoft graph. Show all posts

Thursday, August 10, 2023

How to paginate large results sets for SharePoint items using the Microsoft Graph Search API

If you want to paginate over a large set of results for some reason using the Microsoft Graph Search API, you can employ the logic mentioned for the SharePoint API at https://learn.microsoft.com/en-us/sharepoint/dev/general-development/pagination-for-large-result-sets. Note that this option applies to OneDrive and SharePoint items, not necessarily other content sources available via the Graph Search API (not tested).

Use a basic JSON template like below for your search requests, or modify to add other parameters needed for your request.

{
"requests": [
{
"entityTypes": [
"driveItem"
],
"from": 0,
"size": 500,
"query": {
"queryString": "contoso indexdocid>**LASTID**"
},
"fields": [
"indexdocid"
],
"sortProperties": [
{
"name": "[DocId]",
"isDescending": "false"
}
]
}
]
}

Where **LASTID** is 0 on the initial request. Once you get results back, pick the value of indexdocid of the last result, and use that as **LASTID** on the next request. In the below screenshot you would use 2377359 for the seconds request. Continue this logic until you stop getting results, and you should have iterated all files (driveItems) containing the term contoso for the above sample.




Thursday, January 20, 2022

Sensible defaults – avoiding error on first navigation to the Files tab in a Teams channel

I won’t take credit for the solution myself, as this was shared to me by the awesome Remi Blom-Ohlsen, who is too timid to share great tips with the world himself :)

We’ve all been there, and if you create solutions for customers involving Teams you are bound to have encountered the issue. After the team is created and you immediately navigate to the Files tab in a channel, you get an error message.

image

Navigate to the Posts tab and back, and all is working.

image

When creating Teams using out of the box user interfaces you still have to live with this. If you however use a custom ordering and provisioning solution you can via code fix this nuance for your users :)

If you are patient and wait a tiny bit, the folder will be created for you. For those who are impatient and want to ensure everything is the best way possible for end user, they can continue to read.

If your provisioning solution is using C# you can ensure the channel folder is creating using code like this. And the crux here is the FilesFolder property on the channel object.

public static async Task<DriveItem> InitTeamDrive(string GroupId, GraphServiceClient GraphClient = null)
{
	var graphClient = <get your client here>;
	var channels = await graphClient.Teams[GroupId].Channels.Request().GetAsync();

	foreach (var channel in channels)
	{
		if (channel.DisplayName == "General")
		{
			var drive = await graphClient.Teams[GroupId].Channels[channel.Id].FilesFolder.Request().GetAsync();
			return drive;
		}
	}
	return null;
}
The similar direct graph call would be.
GET https://graph.microsoft.com/v1.0/teams/<group id>/channels/<channel id>/filesFolder

Monday, January 4, 2021

Microsoft Graph: Encoding and decoding the drive id

image

If you have worked with the Microsoft Graph API and SharePoint items you might have encountered URL’s which include a drive id. Where you in SharePoint typically work with URL’s for a site and the document library, a drive id is an encoded representation of a document library location.

The following Graph REST request will list all items in a specific document library:

GET https://graph.microsoft.com/v1.0/drives/b!VvpCx03990mC5Lb5YxH0SUA9TgZHvEZImra6PMjrvbx85KUwT1BMTbhen6I6ffXL/root/children

Fetching the item for a file you use this signature to retrieve the file itself where item id is part of the listing: GET /drives/{drive-id}/items/{item-id}

With some reverse engineering you easily can figure out the drive-id is a prefixed base64 encoded string composed of the site id, web id and list id for a particular library.

Using PowerShell here’s a few lines converting the drive id string to the correct guid’s and back:

$driveId = "b!VvpCx03990mC5Lb5YxH0SUA9TgZHvEZImra6PMjrvbx85KUwT1BMTbhen6I6ffXL"   
$encodedDriveId = $driveId.Substring(2).Replace('_','/').Replace('-','+')
$bytes = [Convert]::FromBase64String($encodedDriveId)
$siteIdBytes = [byte[]]$bytes[0..15]
$siteIdGuid = New-Object Guid @(,$siteIdBytes) #site id

$webIdBytes = [byte[]]$bytes[16..31]
$webIdGuid = New-Object Guid @(,$webIdBytes) #web id

$listIdBytes = [byte[]]$bytes[32..47]
$listIdGuid = New-Object Guid @(,$listIdBytes) #list/library id

$bytes = $siteIdGuid.ToByteArray() + $webIdGuid.ToByteArray() + $listIdGuid.ToByteArray()
$driveId = "b!" + [Convert]::ToBase64String($bytes)

The item-id part is a bit more tricky and looks to be some sort of base32 encoding of a SharePoint item’s unique id. As I haven’t figured out the mechanics you can still access the item by route of the list item unique id:

GET https://graph.microsoft.com/v1.0/drives/{drive-id}/list/items/{unique-id}/driveItem

Example: https://graph.microsoft.com/v1.0/drives/b!VvpCx03990mC5Lb5YxH0SUA9TgZHvEZImra6PMjrvbx85KUwT1BMTbhen6I6ffXL/list/items/7D0E814C-0798-4966-A20C-6DE4F27F2027/driveItem

Of course, if you already have the site id, web id, list id and unique item id, you can access a drive item using the following syntax as mentioned in my file preview post.

GET https://graph.microsoft.com/v1.0/sites/{site-id},{web-id}/lists/{list-id}/items/{item- id}/driveItem

Photo by https://unsplash.com/@maurosbicego

Thursday, March 26, 2020

Searching within Office 365 Groups or Teams content

image

There are at least three ways to limit the search results within a group or a team using the keyword query language (KQL). These are cases where the managed property used exists on all items for the site/group.

Path

If you know the URL of the groups site you can use a path filter, and remember the trailing slash (/) to avoid edge case inclusion of other data.

path:https://contoso.sharepoint.com/teams/myteam/

Site Id

If you know the underlying site id of the Groups site.

SiteID:<guid>

Group Id

If you know the Group Id, meaning the AAD id of the group, you can use this as well.

GroupId:<guid>

Bonus: Search within all the groups a user is member of

And here’s the kicker to effectively scope results to all the groups a user is member of. Say you have pulled out a list of all the groups a user is member of via the Microsoft Graph API:

https://graph.microsoft.com/v1.0/me/memberOf/$/microsoft.graph.group
?$filter=groupTypes/any(a:a eq 'unified')

Armed with all the group ids, you can issue a SharePoint Search REST query using a wildcard (*) as the query text and the following FQL in the refinementfilters property.

GroupId:or(<guid1>,<guid2>,….)

GroupId:or(81eb706e-2904-4ac6-89f4-a0cf9d59d1c4,79958190-024b-4c62-ab55-65dc9a066cac)

If you are a member of many groups, then I suggest using a POST payload instead of GET, and refinementfilters is not limited by 4k either (at the time of this writing) :)

See https://www.techmikael.com/2013/07/limiting-search-results-by-exact-time.html for an explanation of refinement filter usage.

Note: How the above syntax will work using the Microsoft Graph Search API which is in beta, is not known at this point.

Photo by Mike Szczepanski @Unsplash

Wednesday, January 22, 2020

Retrieving thumbnails/previews for SharePoint files and pages via Microsoft Graph API’s

Back in 2011 I wrote a post about thumbnail previews in search via the Fast Search for SharePoint which relied on Office Web Apps to provide the actual thumbnails. Fast forward to 2020, visualizing a page or document in a roll-up scenario is still valid, and fortunately todays service allows much better resolution on the thumbnails.

The API itself is coming via the Microsoft Graph and works on driveItems and is well documented at https://docs.microsoft.com/en-us/graph/api/driveitem-list-thumbnails.

If you want to test this out yourself, download the SPFx sample from https://github.com/SharePoint/sp-dev-fx-webparts/blob/main/samples/js-msgraph-thumbnail/README.md to get started.





Digging into the API’s, the approach I have chosen to go from a SharePoint item to a Graph item is to use the site id, list id and item id – all id’s easily available per SharePoint item, either via REST API’s or the search API, using the calling signature below:

<endpoint>/sites/<site id>/lists/<list id>/items/<item id>
/driveItem/thumbnails/0/<custom size>/content

The above URL can be used directly in an <img> tag, removing the need for multiple calls in order to get the actual image.

Token Description
endpoint graph.microsoft.com/v1.0 (https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)

tenant.sharepoint.com/_api/v2.0 (https://docs.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph)
site id Site object id – GUID.
list id List object id – GUID.
item id ListItem object id or unique id – Integer/GUID.
custom size See Requesting custom thumbnail sizes in the official Microsoft Graph documentation.

If you choose to get the drive item in a different way, that’s all good, as the thumbnail API itself would be the same. In addition to using custom sizes for the preview, there are also some default sizes you can use, all documented.

The above approach have been verified for most common file types (except Excel) as well as for modern pages.

Thursday, September 27, 2018

Example of wrapper to ease usage of Graph calls in SPFx

silhouette of trees and purple lightning

Waldek Mastykarz wrote a good post about not passing the web part context all around your React components, which is good advice. And as Waldek pointed out after reviewing my code I pass the full context and not just the GraphClient. So ideally, passing just what you need is better, but I’m lazy at times :) And it gives you the reader an opportunity to improve my code :)

I tend to create static helper classes, and here’s one approach to ease calling Graph API’s throughout your solution.

The wrapper class is quite simple, and I’ve created helper methods for GET, POST, PATCH, DELETE.

To use this you would first initialize the class in your main web part code.

public async render(): Promise<void> {
   await MSGraph.Init(this.context);
   ...
}

and somewhere in your code if you wanted to get Group data for a group you could use something like this:

import { MSGraph } from '../services/MSGraph';

...

let groupId = this.props.context.pageContext.legacyPageContext.groupId;
let graphUrl = `/groups/${groupId}`;
let group = await MSGraph.Get(graphUrl);

Photo by Jeremy Thomas at Unsplash

Wednesday, July 25, 2018

Using Microsoft Graph to get a PDF preview of a file in SharePoint by file path

The viewfinder of a camera shows a photo of the sunset.
Photo by Glenn Carstens-Peters on Unsplash

There are multiple ways to get a PDF version of a file, so I figured I’d show how you via a path to a file in SharePoint can use the Microsoft Graph API to get a PDF version of that file. I’ll be using the Graph drive item conversion API for this.

A sample URL could look something like this: https://contoso.sharepoint.com/sites/asite/FooLib/lala/Document.docx

[Update]

After posting the question on Stack Overflow I received an answer from Vadim Gremyachev which takes it down to one API call.

Basically he clued me onto how you can create a sharing token for the item URL which is actually the file id. Code for this is listed in the Graph Sharing API docs.

First you base64 encode the URL, replace some characters and prefix with u!, then access the files via the /sharing API. The below code is using PowerShell to construct the token.

$url = 'https://contoso.sharepoint.com/sites/asite/FooLib/lala/Document.docx'
"u!"+[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($url)).TrimEnd('=').Replace('/','_').Replace('+','-')

u!aHR0cHM6Ly9jb250b3NvLnNoYXJlcG9pbnQuY29tL3NpdGVzL2FzaXRlL0Zvb0xpYi9sYWxhL0RvY3VtZW50LmRvY3g

Armed with the token the result API call is:

https://graph.microsoft.com/v1.0/shares/u!aHR0cHM6Ly9jb250b3NvLnNoYXJlcG9pbnQuY29tL3NpdGVzL2FzaXRlL0Zvb0xpYi9sYWxhL0RvY3VtZW50LmRvY3g/driveItem/content?format=pdf

[Original post]

In order to get to the actual file two API calls are needed, one to fetch the drive (library) id, and one to fetch the file.

Note: This solution will not work on the root site collection as I make assumptions on the number of parts of a URL. The following file formats are supported: csv, doc, docx, odp, ods, odt, pot, potm, potx, pps, ppsx, ppsxm, ppt, pptm, pptx, rtf, xls, xlsx.

Deconstructing the file URL

Splitting the URL on slashes we get the parts needed to get the id of the document library and the id of the file.

0 https:
1
2 contoso.sharepoint.com
3 sites
4 pub
5 FooLib
6 lala
7 Document.docx

Part 2 is the tenant hostname, part 3+4 is the site path, part 5 is the document library, and part 6 and out is the item path relative to the document library.

Getting the drive id (id of document library)

Using the sample URL above we combine the sites and drives API’s in one query:

/v1.0/sites/{hostname}:{server-relative-path}:/drives

resulting in the following query where we select id and url

https://graph.microsoft.com/v1.0/sites/contos.sharepoint.com:/sites/asite:/drives?$select=id,weburl

The output of this call are all the libraries in the site.

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives(id,webUrl)",
    "value": [
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_psgYyKuXH2VR7fGsvWPyBOt",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/Documents"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_pv8T5clDnpiRZq2uVmXgGRU",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/FooLib"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_psUQF8PSnx9T7aXwvRalLc_",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/PublishingImages"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_pv01hj6qcWyR5wulob7Lk7-",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/Pages"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_pvEaXdch-3DToEk0qR4g-xx",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/SiteCollectionDocuments"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_ptwBh2OaBQOTbJMXT5jLKwi",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/SiteCollectionImages"
        },
        {
            "id": "b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_pv-q5N0D8gWSLB-0MY7_RS3",
            "webUrl": "https://contoso.sharepoint.com/sites/asite/Translation%20Packages"
        }
    ]
}

Ideally you would use a $filter query to pick out just the library you want, but this is not supported for the drives endpoint, so you need to post-filter yourself.

By filtering out the item which has a webUrl  matching part 2,3 and 4 combined you have the library you are looking for.

Getting the PDF URL for the file

With the id of the document library in hand, it’s time for the next query which will return the URL of the PDF version in a 302 Location header.

/v1.0/drives/{drive-id}/root:/{item-path}:/content?format=pdf

Using the drive id from the previous call together with the document path I end up with the following URL

https://graph.microsoft.com/v1.0/drives/b!H11aFSof8062NsPf4rr-qE3OKQpUIjVEp7PzqdeT_pv8T5clDnpiRZq2uVmXgGRU/root:/FooLib/lala/Document.docx:/content?format=pdf

If you look at the Location header in the returned response you will find something similar to:

https://northeurope1-mediap.svc.ms/transform/pdf?provider=spo&inputFormat=docx&cs=N2FiNzg2….

This is a pre-authenticated URL which can be called directly from anywhere without the need to logging in, and the URL is valid for a few minutes only.

Friday, May 11, 2018

Caution when using $expand with Microsoft Graph

Using the $expand parameter with calls to the Microsoft Graph is very handy. In one API call you can retrieve both the object itself and additional properties.

Two examples are fetching a user and the direct reports, or a group and it’s members.

https://graph.microsoft.com/beta/users/061353c3-af75-4767-9b19-a5bceed85f53?$expand=directReports

https://graph.microsoft.com/v1.0/groups/79958190-024b-4c62-ab55-65dc9a066cac?$expand=members

image

The caution is that when you expand a property which has a collection of values, you will only get the first 20 items returned. This means that if you work in an organization with more than 20 people in it, you should not use $expand if you need all the values, but resort to two calls instead, one for the item, and one for the property you want to expand.

Summary

While using expand is very handy, it’s almost always better to break it into two API calls to avoid having issues if you can expect more than 20 items.

Thursday, January 25, 2018

Automating Office 365 Groups Lifecycle/Expiration management

[This post is based on the state of API’s January 24th, 2018]

image


One of the most awesome preview features today for Office 365 is the ability to set an expiration policy for the Office 365 Groups. Cleaning up old and unused content is something all organizations want, but people are inherently afraid of deleting content and thus become document hoarders. I truly believe this feature might help organizations finally rid themselves of old crap cluttering up all the good stuff.

Note: Groups expiration policy is a premium feature requiring all users in affected groups to have an Azure AD Premium license, and in my opinion this might be a good reason to invest AAD premium.

Currently each tenant support one expiration policy, and you have to decide if this policy should apply to all Office 365 Groups or only to a select number. I expect more policies to be possible in the long run, but one step at a time.

If you opt-in to include all Office 365 Groups in the expiration policy you are in the clear, and really don’t have to read the rest as it’s all automated for you :)

Wednesday, December 6, 2017

Considerations when creating an Office 365 Group using app-only tokens

image

When you create a new Office 365 Group using the Microsoft Graph using app-only tokens, the Group will not have an owner – which is expected as you are creating the group as an application.

After the creation of the group you need to explicitly add an owner, and make sure you also add the same person as a member.

Summarized steps:

  1. Create the group
  2. Add a person as owner
  3. Add the person in 2) as a member

Wednesday, November 29, 2017

Retrieving Teams and Yammer endpoints for an Office 365 Group via the Microsoft Graph

image

I’m working on a solution listing all things Groups from Office 365. That be Office 365 Groups with Outlook, Office 365 Groups with Yammer, Office 365 Groups with Teams, or just plain old Yammer Groups standing by themselves. The last one you cannot do via the Graph so let’s just skip that one for now.

I recently wrote about how you can list all O365 Groups which have a Yammer association, but I did not cover how to get the link to Yammer – as I had no clue how. I have now clued myself in, and will share my findings.

Monday, November 13, 2017

How to list all Office 365 Groups which are Yammer enabled via the Microsoft Graph

image

Office 365 Groups are great, both from a user perspective, and they are also fun to work with as a developer. This time around I wanted to list all Office 365 Groups which have been created via Yammer, as a means to differentiate the type of Office 365 Groups for users.

I started to dig around on properties on the Group object at the /beta endpoint and found the following for a Yammer originating Office 365 Group.

"resourceBehaviorOptions": [
    "YammerProvisioning"
]

Turns out you can use above information in a filter on the /beta/groups endpoint. Using a GET query where you filter on both Unified groups and the YammerProvisioning attribute, will yield the list you are looking for. You can probably omit the groupTypes filter, but it sure doesn’t hurt.

https://graph.microsoft.com/beta/groups?$filter=groupTypes/any(g:g eq 'Unified') and resourceBehaviorOptions/any(r:r+eq+'YammerProvisioning')

There is still no API on the Microsoft Graph to create Yammer enabled Office 365 Groups, but at least we’re one step closer.

Permission wise you need Group.Read.All as either delegated or app only to query for Groups, which means you can easily do this using the GraphHttpClient for SharePoint Framework.

Tuesday, November 7, 2017

Fetching custom attributes from Azure Active Directory via the Microsoft Graph

This post was inspired by Juan Carlos González who asked a question about retrieving custom/extension attributes from Azure AD via the Microsoft Graph.

image

Custom or extension attributes in on-premises active directory is nothing new, and many have set up synchronizing these to Azure AD as well – which makes sense. Once the attributes are in place, you might want to use them in applications as well, and in todays day and age, using the Microsoft Graph API is the way we play.

Custom attributes are not retrievable directly by their name like for example userPrincipalName.

https://graph.microsoft.com/v1.0/me/?$select=userPrincipalName

Instead they are named with extension_<randomid>_attribute, which means we need to figure out what this random id is. As far as I know you cannot list it via the Graph, but using Azure AD PowerShell it’s doable. The below sample shows a custom attribute named division on my user object.

Connect-AzureAD
$aadUser = Get-AzureADUser -ObjectId me@madcow.dog
$aadUser|select -ExpandProperty ExtensionProperty

Key                                                     Value
---                                                     -----
extension_e96266002973421daef990ab9be89e86_division     64

By looking at the result we have the prefix we need which works just fine in a graph query.

https://graph.microsoft.com/v1.0/me/?$select=userPrincipalName,extension_e96266002973421daef990ab9be89e86_division

image

Tuesday, September 19, 2017

Introducing a generic (mostly) all purpose extensibility schema for the Microsoft Graph

If you’ve followed my blog lately you know I’m experimenting with using Graph Extensions from the Microsoft Graph to persist metadata for resources in the graph, more specifically I’m storing metadata about different types of Office 365 Groups.

image

I decided to go with schema extensions over open extensions as this allows me to query and filter on my schema properties, instead of just reading and writing the properties. If you plan on only reading and writing data, I recommend using open extensions – why? Read on!

Wednesday, September 13, 2017

SharePoint Framework helper class for calling the Microsoft Graph

I’m creating a couple of web parts using SharePoint Framework in my current project so I figured I’d share a small helper class I created for performing GET requests against the Microsoft Graph. The code is written in TypeScript and handles single item results as well as paging on array results.

MSGraph.ts

import { GraphHttpClient, GraphHttpClientResponse } from '@microsoft/sp-http';

export class MSGraph {
    public static async Get(graphClient: GraphHttpClient, url: string) {
        let values: any[] = [];
        while (true) {
            let response: GraphHttpClientResponse = await graphClient.get(url, GraphHttpClient.configurations.v1);
            // Check that the request was successful
            if (response.ok) {
                let result = await response.json();
                let nextLink = result["@odata.nextLink"];
                // Check if result is single entity or an array of results
                if (result.value && result.value.length > 0) {
                    values.push.apply(values, result.value);
                }
                result.value = values;
                if (nextLink) {
                    url = result["@odata.nextLink"].replace("https://graph.microsoft.com/", "");
                } else {
                    return result;
                }
            }
            else {
                // Reject with the error message
                throw new Error(response.statusText);
            }
        }
    }
}

You can use the code as follows where I have extended the properties to include the web part context object in order to pass in graphHttpClient object.

import { MSGraph } from './MSGraph';

...

try {
  let groups = await MSGraph.Get(this.props.context.graphHttpClient, "v1.0/groups?$top=5");
} catch (error) {
  console.error(error);
}

Monday, September 11, 2017

An approach to working with Schema Extensions in the Microsoft Graph

I wrote a post last week about my issues with custom metadata and the Microsoft Graph. The week ended leaving me on the fence on which way to go. However, a couple of days off has sorted my brain a little bit, and I’ve had dialogs with the Graph team on these three Stack Overflow questions:

I think I’ve finally landed on using Schema Extensions and the approach below seems like something I can work with.

Thursday, September 7, 2017

Which schema to use in the Microsoft Graph for custom metadata: Schema Extensions or Open Extensions? Or maybe neither? I need help!

[Read my follow-up post at: http://www.techmikael.com/2017/09/an-approach-to-working-with-schema.html]

I’m trying to build out a solution for custom metadata for Office 365 Groups and it’s not that easy to do the right thing. My scenario is some worker process setting data, so I’m running in an app only context, not user delegated.

SNAGHTMLb983c97
If you don’t know what I’m talking about, Schema Extensions and Open Extensions are ways to store additional metadata to objects in the Microsoft Graph. Objects can for example be a user account, an e-mail message in Exchange or an Office 365 Group object. You persist the metadata with the object, not in some outside store.

Using Open Extensions is by far the easiest, as you can define your extension and the key/value pairs of data arbitrary, using the permissions of the object you are extending. The caveat however is that you cannot filter on these metadata. Say you added metadata to e-mail messages saying it’s customer related. Using Open Extension you cannot query to retrieve only those messages tagged. For me, not being able to filter is a blocker.

This is where Schema Extensions come in. Schema Extensions are typed schemas and you can indeed filter on them. But here are the issues I have experienced so far – I might be wrong about some of these, and base them off my testing. There is a whole lot more information about schema extensions on the old Azure AD Graph docs – which may or may not have solutions to my issues.
  • Schema Extensions cannot be created using app only permissions, so you cannot really automate it, and you might need a separate ADAL app to create it from the one you use for other automation.
  • A schema can not be used before it transitions from InDevelopment to Available.
  • You cannot delete a schema which has transitioned into being Available.
  • You cannot delete properties, only add new ones once a schema is made Available.
  • You cannot add a schema which is InDevelopment to for example an Office 365 Group object, making development harder.
  • Unless you have a verified domain of com, edu, net, org, you will end up with a random prefix for your schema extension. Which means you cannot easily filter to find the schema you created, but need to loop over all to find the one ending in your name. Unless you choose to record the name, but that won’t work for a generic solution across tenants.
In order to get the most flexible solution you might want to put metadata you need to filter on in a schema extension, and all others as open extension data. But this increases complexity as data are stored in two ways. Not the most ideal way to go, but a possibility.

Summary

While storing metadata in the Microsoft Graph seems like a good idea, as it reduces the number of moving parts, it might not be the best solution for every scenario. The ALM scenario for me right now is a bit in the wind, and perhaps storing the data outside the Microsoft Graph is a better and more flexible approach – even though I have to add more moving parts to the solution.

What’s your take?

Tuesday, May 16, 2017

The Office Graph is dead, long live the Microsoft Graph!

Yesterday Microsoft announced two new Insights endpoint (in beta) to the Microsoft Graph in addition to the trending one which was already there. The new endpoints are:
  • Used - returns the most relevant documents that a user viewed or accessed
  • Shared - files shared with or by a specific user
All great news, but at the same time Microsoft announced discontinuation of the Office Graph GQL API’s – effectively killing the last remains of what was formerly known as the Office Graph. This means as of August 31st 2017, calls using GQL on the SharePoint search API will no longer work.

By June 1st you need to add the parameter EnableLegacySPOGraph=true to your GQL calls to extend the life until the final cut off date August 31st.

rip graphReaper and halo’s!

Monday, March 20, 2017

Controlling invitation of external members to an Office 365 group programmatically via the Microsoft Graph

Microsoft has a support article explaining how to change the allow or deny policy of inviting external members to an Office 365 Group via PowerShell. This is all nice, but it doesn’t scale well in an automation scenario.

In my current project the default setting is to allow externals outside the organization to be invited, but through a custom groups order form, you can choose to block external parties – which is useful for internal only projects.

So how do you go about this?

image

Prerequisites

As I’m doing automation using Office 365 app tokens via the Microsoft Graph, the following scopes are needed when working with Groups and Directory objects (and remember to admin consent) :
  • Group.ReadWrite.All
  • Directory.ReadWrite.All
  • User.Read.All (needed when adding owners/members)

Applying the template

I followed the support article for PowerShell which tells you to apply a template named Group.Unified.Guest which has the id 08d542b9-071f-4e16-94b0-74abb372e3d9 to the group, setting AllowToAddGuests to true or false, depending on if you want to allow external members or not.

A word of caution: The functionality of working with directory objects via the Microsoft Graph is only available at the beta end-point at the time of writing. But as long as it works I’m good to go, and I’ll monitor if the API changes and when it moves to GA.

The code uses Office PnP PowerShell to connect to the Microsoft Graph, creates a new Office 365 group, retrieves the app access token which is then used in a manual web request to apply a policy to the newly created group. Adapting the sample to your scenario should be easy enough, as it’s the web request applying the actual policy. If you're curious, we're using Azure web jobs to poll the order list, and uses PnP PowerShell like below for the automation.

#Connect to the Graph
Connect-PnPMicrosoftGraph -AppId $appId -AppSecret $appSecret -AADDomain tenant.onmicrosoft.com

#Create a new group
$group = New-PnPUnifiedGroup -MailNickname $mailAlias -DisplayName $displayName -Description $description -IsPrivate:$private -Owners $owner -Members $members

#Get the access token
$token = Get-PnPAccessToken

#Prepare headers
$headers = @{"Content-Type" = "application/json" ; "Authorization" = "Bearer " + $token}

#The directory template to set the policy
$templateDeny = @"
{
  "templateId": "08d542b9-071f-4e16-94b0-74abb372e3d9",
  "values": [
    {
      "name": "AllowToAddGuests",
      "value": "False"
    }
  ]
}
"@

#Graph URL to add settings to the group
$url = "https://graph.microsoft.com/beta/groups/$($group.GroupId)/settings"

#Apply the template, and wait for a 204
Invoke-WebRequest -Method Post -Uri $url -Headers $headers -Body $templateDeny

Thursday, March 16, 2017

Microsoft Graph adds SharePoint endpoint for Groups in the beta branch

[Update] Use https://graph.microsoft.com/v1.0/groups//sites/root/weburl to get a web url as the SharePoint endpoint never made it to production.

I’m calling the Microsoft Graph with Office 365 groups these days and I stumbled upon a new SharePoint endpoint for Groups.

If you call:

https://graph.microsoft.com/beta/groups/821b0970-fae7-4640-bb22-5d091fd17a33/sharepoint/site

you will get the following information back, listing the site name and URL – a better approach compared to using the /drive/root/webUrl endpoint as you don’t have to strip anything.

{
    "@odata.context": "https://graph.microsoft.com/beta/$metadata#groups('821b0970-fae7-4640-bb22-5d091fd17a33')/sharepoint/site/$entity",
    "createdDateTime": "2017-03-16T16:55:39.19Z",
    "description": "",
    "id": "42d1e3d3-a645-4388-a217-4d88380802df,de4b0a91-5caa-4732-a530-67cfb2b0c42d",
    "lastModifiedDateTime": "2017-03-16T11:00:08Z",
    "name": "project-govtest",
    "webUrl": "https://tenant.sharepoint.com/teams/project-govtest",
    "root": {},
    "siteCollection": {
        "hostname": "tenant.sharepoint.com"
    },
    "siteCollectionId": "42d1e3d3-a645-4388-a217-4d88380802df",
    "siteId": "de4b0a91-5caa-4732-a530-67cfb2b0c42d"
}

If all you want is the url, use /sharepoint/site/webUrl.