Microsoft Graph is the key to access data in Microsoft 365. When working with SharePoint, we have some access with Graph, but there is still a lot missing. In my case I needed a list of all SharePoint sites with their owners for governance reasons. Here I show how you can get this data today with the Graph API.
Tools to communicate with the Graph API and the docs
To use Microsoft Graph, we can use any conceivable methods that can call the Graph API. The Graph API is well documented on https://learn.microsoft.com/en-us/graph/api/overview. Try it out at Graph Explorer.
Note: The beta endpoint includes APIs that are currently in preview and aren't yet generally available, they are just for testing and providing feedback, but not for production use.
In my example, I use Azure Logic Apps because you can get results very quickly with it. Of course, Azure Functions, PowerShell, and other ways can be used as well.
List SharePoint sites
Here, we find a method to read all SharePoint sites at List sites. The call is straight forward:
https://graph.microsoft.com/v1.0/sites
Application permissions
The documentation informs, that this method only works with App permissions (which makes sense for my purpose, since I want to get a list of all existing SharePoint sites in the tenant). So, I had to create an Azure AD App first and I created the application with the Sites.Read.All app permissions and a secret, as here.
You can find the steps at Register an application with the Microsoft identity platform.
With this Authentication, we can run the query. The Authority is https://login.microsoft.com, and the Audience is https://graph.microsoft.com. Here, I use an Azure Logic App, and read the App Id and the App secret from a Key Vault, as here.
Note, that the query returns an empty list "value": [], if you do not use an application, but a user.
Try it out
When we run the query, the (truncated) result looks like this: We get a value key that includes an array with one element per site.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites",
"value": [
...
{
"createdDateTime": "2017-11-05T22:03:27Z",
"id": "<mytenant>.sharepoint.com,a24a348f-2a24-4af0-af54-64b101ca1298,33043504-0431-4d0f-b19e-e94daad2094e",
"name": "Demo",
"webUrl": "https://<mytenant>.sharepoint.com/sites/Demo",
"displayName": "Demo",
"siteCollection": {
"hostname": "<mytenant>.sharepoint.com"
},
"root": {}
},
...
]
}
The result includes all SharePoint sites: SPO sites, One Drive sites, and Teams sites.
Get the owner workaround
Unfortunately, the Site resource does not expose an Owner property (until now). I hope this will be available in future. So we need to find a workaround.
Well, the workaround I found, was to get the owner of the default library in the SharePoint site. We assume, the default library owner is the site owner, and we can ask Graph for that with a GET, as here:
https://graph.microsoft.com/v1.0/sites/<siteid>?$select=*,drive&$expand=drive
In a foreach-loop, I replace the <siteid> with the ID of each site to get the data.
There are different types of SharePoint sites, so the result will be different for each type, as follows.
SharePoint site
Basically, the (truncated) result should look as here. This returns a bunch of other useful information, but the relevant information for me is in that data stream is the owner / user property (marked in yellow).
{
"@odata.context": https://graph.microsoft.com/v1.0/$metadata#sites(*,drive())/$entity,
"createdDateTime": "2017-11-05T22:03:27Z",
...
"siteCollection": {
"hostname": "<tenantid>.sharepoint.com"
},
"drive@odata.context": https://graph.microsoft.com/v1.0/$metadata#sites('<tenantid>.sharepoint.com%2C<someid>')/drive/$entity,
"drive": {
"createdDateTime": "2017-11-04T21:38:53Z",
"id": "<someid>",
...
"driveType": "documentLibrary",
"createdBy": {
"user": {
"displayName": "System account"
}
},
"lastModifiedBy": {
"user": {
"email": "user1@mytenant.org",
"id": "a7a4507a-954c-45a0-b5ec-d37f4af72c03",
"displayName": "User One"
}
},
"owner": {
"user": {
"email": "user1@mytenant.org",
"id": "a7a4507a-954c-45a0-b5ec-d37f4af72c03",
"displayName": "User One"
}
},
"quota": { ... }
}
}
Here we get the user email address, the id, and the displayName of the user in the drive properties.
M365 Group (Microsoft Team)
The owner of a site could be a group, as here:
{
...
"drive": {
"owner": {
"group": {
"email": "Group1",
"id": "2975e880-b0cb-465c-9c7a-601f9c1c1701",
"displayName": "Group One"
}
},
...
}
}
So, depending on the result, we now know who is the owner of the site. If the owner is a group, we have to go to the groups endpoint, and get the owners of that group using the id. The owners are returned in the values object in an array.
https://graph.microsoft.com/v1.0/groups/<groupid>/owners
Here, I can continue with a lookup of the owners and continue to process the info.
Note: If I am using the same app, I would need to add the Groups.Read.All permission to use it my flow.
One Drive site
If the site is a One Drive storage, the result looks as here:
{
"@odata.context": https://graph.microsoft.com/v1.0/$metadata#sites(*,drive())/$entity,
"id": "<tenantid>-my.sharepoint.com,<someid>",
"lastModifiedDateTime": "2023-07-24T14:12:02Z",
"name": "<firstname>_<lastname>_<mytenant>_<domain>",
"webUrl": https://<tenantid>-my.sharepoint.com/personal/<firstname>_<lastname>_<mytenant>_<domain>,
"displayName": "<firstname> <lastname>",
"root": {},
"siteCollection": {
"hostname": "<tenantid>-my.sharepoint.com"
},
"drive@odata.context": https://graph.microsoft.com/v1.0/$metadata#sites('<tenantid>-my.sharepoint.com%2C<someid>')/drive/$entity,
"drive": {
"createdDateTime": "2013-09-23T06:34:25Z",
"description": "",
"id": "<someid>",
"lastModifiedDateTime": "2023-07-24T08:27:14Z",
"name": "OneDrive",
"webUrl": https://<tenantid>-my.sharepoint.com/personal/<firstname>_<lastname>_<mytenant>_<domain>/Documents,
"driveType": "business",
"createdBy": {
"user": {
"displayName": "System account"
}
},
"lastModifiedBy": {
"user": {
"email": "user1@mytenant.org",
"id": "a7a4507a-954c-45a0-b5ec-d37f4af72c03",
"displayName": "<firstname> <lastname>"
}
},
"quota": { ... }
}
}
This result looks different, since we do not get an owner with userid or a groupid. We see the drive / name property is OneDrive. Here, we could assume, the owner is the user who is returned in the drive / lastModifiedBy / user object. Otherwise, we eventually would need the displayName, or the name property and to process that data to get a useful email address out of "User One" to get the email address user1@mytenant.org. At least, this is only the case for the One Drive data. Eventually someone else has a better workaround here?
Summary
This method is eventually not ideal, but it´s a workaround (until the Graph delivers that information hopefully). You can also see the wish list and requests and vote for specific features at https://techcommunity.microsoft.com/, search for "sharepoint". Also, there is more info and other useful discussions in the web, as from our colleague Sandy about Can I get a list of all site collections in a tenant using Power Automate?, etc.
Alternatively, you can use a SharePoint App and the SharePoint REST API, and libraries as CSOM, or JSOM. Find a basic sample using the CSOM library here.
I hope this workaround helps to get owners of SharePoint sites with Microsoft Graph!