When Working with the Microsoft Graph PowerShell SDK
By now, you should be aware that Microsoft plans to retire the Azure AD and MSOL PowerShell modules at the end of 2022 or soon after. The cmdlets in the retired modules will continue to function afterwards, but they won’t have any support. Microsoft suggests that you upgrade scripts to use cmdlets from the Microsoft Graph PowerShell SDK. The actual conversion is often a matter of finding a matching cmdlet in Microsoft’s cmdlet map and switching to it. However, other complexities get in the way before cmdlets will run smoothly. Figuring out the right Microsoft Graph API permissions to use to access data is just one of those complexities.
Least Permission Model
Permission handling differs significantly between the Azure AD PowerShell module and the Microsoft Graph PowerShell SDK. When you sign in using the Connect-AzureAD cmdlet, you can use all the administrative permissions owned by the account you sign in with. However, the Graph SDK operates on a least permission model, which means that you must request permissions to perform actions, even when connecting with a highly-permissioned account.
The first step in updating a script from Azure AD cmdlets to SDK cmdlets is to consider what permissions the script needs to perform its processing. This rule applies no matter how you use SDK cmdlets – interactively, through a background job which uses certificate-based authentication, or in an Azure Automation runbook. Unlike permissions inherited from signed-in accounts, the permissions used by the SDK are granted to the service principal used to run SDK cmdlets. For interactive sessions, the service principal is the Microsoft Graph
The question then arises how to find the Microsoft Graph API permissions necessary to perform an action. You can accomplish the goal in four ways:
Guess and Over-permission
The first method is a guess and hope approach. In other words, grant all the permissions that a script might conceivably use or keep on adding permissions until the code runs. The danger here is that you end up with a heavily-permissioned service principal that becomes an attractive target for attackers. If you use interactive sessions to run SDK cmdlets, a fair chance exists that the Microsoft Graph PowerShell service principal will acquire many permissions over time and end up in a heavily-permissioned state (Figure 1). We’ll return to this issue later.
Use the Graph Explorer to Highlight Graph Permissions
Next, if you run a query in the Graph Explorer, the explorer shows you the permissions required to run the query in the Modify permissions tab (Figure 2). The set of permissions shown include every valid permission which you could use, so you need to select the most appropriate permission. In this case, the query is to fetch the set of user accounts in the tenant (one of the basic set of user account operations), so the User.Read.All permission is a good choice.
Cybersecurity Risk Management for Active Directory
Discover how to prevent and recover from AD attacks through these Cybersecurity Risk Management Solutions.
Read the Graph documentation
All SDK cmdlets originate from Graph queries. Microsoft uses a process called AutoRest to process available Graph queries and create SDK cmdlets. This process happens monthly to create a new version of the SDK. The process also creates automated documentation, but the machine-generated text is often obtuse and difficult to follow, which is why I often revert to the underlying Graph documentation. Take the example of listing user accounts. A quick search brings us to the List users page, which lists the required permissions for both delegated (used to access data as the signed-in user) and application (used for background processes). In this case, the page confirms that User.Read.All is a good choice.
Use SDK Help to Identify Graph Permissions
The SDK includes two cmdlets to help developers figure out what permissions they need to perform actions.
- Find-MgGraphPermission: Lists the delegated and application permissions for different actions.
- Find-MgGraphCommand: Lists the cmdlets available to interact with different types of objects, including the required permissions. The cmdlet works by taking the URI for an object to find the available commands.
For example, let’s assume that I want to interact with organization information stored in Azure AD. I could start by running the Find-MgGraphPermission cmdlet:
Find-MgGraphPermission organization | ? {$_.PermissionType -eq "Application"} | Format-List Name, Description Name : Organization.Read.All Description : Allows the app to read the organization and related resources, on behalf of the signed-in user. Related resources include things like subscribed skus and tenant branding information. Name : Organization.ReadWrite.All Description : Allows the app to read and write the organization and related resources, on behalf of the signed-in user. Related resources include things like subscribed skus and tenant branding information.
The command pipes its output to filter and display application permissions (the same delegated permissions are available). From the output, it’s obvious that we should use the Organization.Read.All permission to read organization information (like the tenant name and identifier), and Organization.ReadWrite.All should we need to update a writeable setting.
For brevity, I show only the permission name and description above. Here’s the full output for the Group.ReadWrite.All permission, needed to update the properties of Azure AD groups, including Microsoft 365 groups.
Id : 62a82d76-70ea-41e2-9197-370581804d09 PermissionType : Application Consent : Admin Name : Group.ReadWrite.All Description : Allows the app to create groups, read all group properties and memberships, update group properties and memberships, and delete groups. Also allows the app to read and write group calendar and conversations. All of these operations can be performed by the app without a signed-in user.
You could try a modified version of the commands to see what permissions are needed to interact with Users, Groups, Azure AD (Directory), Apps (Application), and so on.
The Find-MgGraphCommand cmdlet passes part of a Graph URL to see what SDK cmdlets are available for that URL. A URL is something like Users or Groups to list cmdlets available for dealing with sets of objects. If you add {id}, it means that you want to see the cmdlets available to process individual objects.
For example, let’s see what cmdlets are available to process individual groups. The command and its output are shown below:
Find-MgGraphCommand -uri "/groups/{id}" | ? {$_.APIVersion -eq "v1.0"} | Fl Command, Method, Permissions Command : Get-MgGroup Method : GET Permissions : {Directory.AccessAsUser.All, Directory.Read.All, Directory.ReadWrite.All, Group.Read.All...} Command : Remove-MgGroup Method : DELETE Permissions : {Directory.AccessAsUser.All, Group.ReadWrite.All} Command : Update-MgGroup Method : PATCH Permissions : {Directory.AccessAsUser.All, Directory.ReadWrite.All, Group.ReadWrite.All}
The Graph has two endpoints, V1.0 and beta. The V1.0 endpoint is the production version while the beta endpoint is under development. In many cases, you’ll use cmdlets with the beta endpoint because they provide added functionality. For example, if you run the Get-MgUser cmdlet against the V1.0 endpoint, it doesn’t return any license information for accounts, but it does when run against the beta endpoint.
In this case, I’ve filtered the output to show just the commands available in the V1.0 endpoint, and we can see that three cmdlets are available to list a group, delete a group, and update the properties of a group. We can also see that the Group.Read.All permission is sufficient to read group information, but we need Group.ReadWriteAll to delete or update a group.
Scoping
Once you’ve found out which Microsoft Graph API permissions are needed for a script to perform whatever actions it takes care of, you state the set of required permissions when connecting to the Graph in the Scopes parameter for the Connect-MgGraph cmdlet. For example:
$RequiredScopes = ("Organization.ReadWrite.All”, "Directory.Read.All") Connect-MgGraph -Scope $RequiredScopes
For interactive sessions, if the Microsoft Graph PowerShell service principal doesn’t have consent to use the requested permissions, you’ll be prompted for consent (Figure 3).
If you allow the app to use the permission, the session can use the delegated version of the permission for the duration of the session. If you grant consent on behalf of the organization, Azure AD adds the permission to the service principal, and it becomes available for all sessions thereafter. Consent granted to the service principal is for a delegated permission, meaning that the user is limited to whatever data is permitted by the administrative roles assigned to their account. As Microsoft explains in their documentation, “the effective permissions of your app are the least-privileged intersection of the delegated permissions the app has been granted (by consent) and the privileges of the currently signed-in user.” In other words, if your account holds an administrator role, you’ll be able to access more data than if your account doesn’t.
After connecting, you can check what permissions are active by running:
(Get-MgContext).Scopes
Accumulated Graph Permissions
The constant accumulation of permissions by the Microsoft Graph PowerShell service principal is something to guard against. If you want to remove all the permissions from the service principal, you can do so through the Azure AD admin center (as in Figure 1), or you can remove the service principal. When this happens, the SDK detects that the service principal is missing the next time someone attempts to sign in and recreates it (the AppId for the service principal is always 14d82eec-204b-4c2f-b7e8-296a70dab67e).
To create the service principal, connect to the Graph with the Application.ReadWrite.All permission and run these commands:
The Remove-MgServicePrincipal cmdlet won’t prompt for confirmation. Fortunately, soon Azure AD will prove the ability to restore soft-deleted service principal objects, just in case you make a mistake.
$SDKServicePrincipalId = (Get-MgServicePrincipal -all | ? {$_.DisplayName -eq "Microsoft Graph PowerShell"}).Id Remove-MgServicePrincipal -ServicePrincipalId $SDKServicePrincipalID
More Complex, More Control
Working with the Microsoft Graph PowerShell SDK requires more attention to permissions than is the norm (up to now) with PowerShell modules. Instead of assuming administrative permissions, you must request permissions and use just the required set. It takes a little getting used to and is part of the checklist for conversion of scripts based on the Azure AD and MSOL modules. Like most other things in life, we’ll get used to permission management for connections in time.
Hello Tony & Community,
I am trying to execute Microsoft Graph that it can grab all my Enterprise Applications in my tenancy and export to CSV the application name and user and groups assigned to the groups.
I am struggling to find how to grab the user and groups properties within Enterprise Applications.
You’re looking at the service principals for the enterprise apps – correct? If so, you should find assignments for the SPs that contain the names of the assignees.
Does this help? https://office365itpros.com/2023/11/22/enterprise-app-assignments/
Hey Tony,
Do we have any enpoint where we can get all the apps which are having specific graph permisisons or a UI – like if we want to know if the app is having a mail.read or MailboxSettings.ReadWrite permissions. It would be good to have a central view. What I am getting is service principal and oauth permissions but the graph permission , application or delegated permissions an application is having.
This is what the Microsoft app governance solution does: https://office365itpros.com/2021/07/21/microsoft-preview-app-governance/
You could do something like this https://office365itpros.com/2023/06/14/app-governance-license/ too…
Dear Tony,
Thanks for explaining however I still have a question, when i have for example the graph explorer and I consent permissions with my admin account for example:
RoleManagement.ReadWrite.Directory & Directory.ReadWrite.All
After this I should be able to execute:
https://graph.microsoft.com/v1.0/directoryRoles/xxx/members/$ref
{
“@odata.id”: “https://graph.microsoft.com/v1.0/users/xxx”
}
However, this gives me the following warning:
{
“error”: {
“code”: “Authorization_RequestDenied”,
“message”: “Insufficient privileges to complete the operation.”,
“innerError”: {
“date”: “2023-10-03T19:02:59”,
“request-id”: “f7269107-4ffa-4284-8422-d998242c0be1”,
“client-request-id”: “8043f372-e76f-6678-caf0-72068806849f”
}
}
}
If I request those specific rights as the user and approve them from within the window of the user it does work. But why does it not work using the admin consent?
Admin accounts mean nothing for Graph permissions. What counts is the permissions consented to for the service principal of the app, in this case the Graph Explorer.
See https://learn.microsoft.com/en-us/graph/graph-explorer/graph-explorer-features
I am struggling trying to figure out what permissions to grant myself if I need read access to the entire tenant.
Would you happen to know this?
Thank you!
There is no concept of “entire tenant.” You decide what types of data you need to interact with and look for the Graph permission which supports access to that data. Delegate permissions support access to data available to the signed-in user while application permissions support access to all data of a specific type.
Thank you!
That response was freakishly fast.
So far I have not been able to figure what permissions I have… My user has ‘some’? access to the MS Graph command line tools, yet when I try to script something, I am getting permission denied for 9 out of 10 commands. But I am guessing my conceptual understanding isn’t quite right…
What data are you trying to access and how are you accessing the data? It might be linked to delegate versus application permissions.
Thanks a lot for your great help.
Unfortunately, all pretty stupid ‘concepts’ are just try and error, because they are no answer to real-world scripts. For example, we have Scripts with many hundred lines of code – an M$ has no useful solution to get real required access rights.
In fact, the great new world of Azure Graph makes everything less secure, because we can not spend days just to try to find the required Azure Graph permissions. Additionally, M$ does not help to identify missing access rights, e.g. by offering to enable log files that tell which command failed and which access right would be necessary.
Therefore, we usually give almost all permissions to our scripts, so that we can do our work and we do not have to play games with M$ azure.
It’s a shame that M$ does not have a “learn used access rights” mode. This would help a lot:
1) Enable the “learn used access rights” for the registered app
2) Start the code / scripts and execute all use cases, so that all script-functions are called (can easily be automated :-))
3) Review the access rights which were learned
4) Commit / reject modified access rights
I wonder if M$ has no more money to engage enough smart engineers… or do they ask ChatGPT to get their concepts?
Thanks a lot, kind regards,
Thomas
I sympathize with you. All I can report is that in my experience, once you get used to dealing with Graph permissions, they become much easier to sort out. You’ll find that a reasonably consistent set of permissions are used by most scripts. There are lots of permissions, but many of them are never used in day-to-day work.
Excellent Tony! Thank you for your comments!
Hi Tony! Thank you for a great description!
Regarding this section in your article: “Consent granted to the service principal is for a delegated permission, meaning that the user is limited to whatever data is permitted by the administrative roles assigned to their account.”
Does this mean that a user without administrative roles could not create a user while connected to the Scope that allows it, for example? Thanks!
Thanks for asking. I might have been a tad clearer, so I added:
As Microsoft explains in their documentation, “the effective permissions of your app are the least-privileged intersection of the delegated permissions the app has been granted (by consent) and the privileges of the currently signed-in user.” In other words, if your account holds an administrator role, you’ll be able to access more data than if your account doesn’t.
See https://docs.microsoft.com/en-us/graph/auth/auth-concepts#effective-permissions-in-delegated-vs-application-only-permission-scenarios for more information.