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.

The Microsoft Graph PowerShell service principal can accumulate permissions


Microsoft Graph permissions
Figure 1: The Microsoft Graph PowerShell service principal can accumulate permissions

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.

The Graph Explorer lists the Graph permissions needed for an action
Figure 2: The Graph Explorer lists the Graph permissions needed for an action

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).

How to Figure Out What Microsoft Graph Permissions You Need
Figure 3: Requesting consent to use a Graph permission

If you allow the app to use the permission, the session can use the delegated permission for the duration of the session. Essentially, delegated permission means that the user can access data that they own but cannot access the data belonging to other users. However, it’s also possible that access to data can be gained through administrative roles assigned to an 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.

If you grant consent on behalf of the organization, Entra ID adds the permission to the service principal, and it becomes available for all sessions thereafter. Consent granted to the service principal is for an application permission, meaning that the user can access data from across the organization.

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.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Zain

    Hi Tony! Thank you for a great description!
    I’m executing Graph API “https://graph.microsoft.com/beta/users” using power automate workflow and it works fine. But when I try to filter my data i.e, “https://graph.microsoft.com/beta/users?$filter=mail eq ‘xyz@email.com'”, it throws an error saying “Insufficient privileges to complete the operation”. Kindly guide me in this regard. Thanks.

    1. Avatar photo
      Tony Redmond

      Are you trying to access someone else’s account? If so, you need application permission, not the delegated permission assigned by default.

      1. Zain

        Thank you for your response Tony.
        Yes I’m trying to fetch specific user’s data for a project. Can you please explain the application permission thing as I’m new to it or you may refer to some post or blog.

  2. Rahul K

    Hi Tony,

    Recently we have had a case, where we quickly want to know, which all API permission a specific user has and from where. Is it possible to get this, many thanks in advance!

      1. Rahul

        If user can run below command,
        Invoke-PowerBIRestMethod -Url ‘groups/{groupid}/datasets/{datasetid}’ -Method DELETE, which means a user got the permission scope – Dataset.ReadWrite.All.

        So, if I want to know, who all users can do this.

          1. Rahul

            Thanks, Tony for your quick responses, yes initially I thought so, but this user can delete dataset where it has no access.

          2. Avatar photo
            Tony Redmond

            Given that I can’t see what’s happening in your tenant, you should file a support call with Microsoft and have them check things out.

  3. Satish Rabari

    How can we retrieve the calendar of an external user using the Microsoft Graph API?
    As an invited guest user in my tenant, I can successfully log in using the tenant application. However, external guest users are unable to make successful calls to the Azure API for retrieving the calendar list, whereas standard member users can call it successfully.

    I am getting following errors:

    {
    “error”: {
    “code”: “AuthOMMissingRequiredPermissions”,
    “message”: “The AadGuestPft token doesn’t contain the permissions required by the target API for calling app ‘00000003-0000-0000-c000-000000000000’.”,
    “innerError”: {
    “oAuthEventOperationId”: “bbfaa161-3680-4790-a0fc-f2d36f87ac6d”,
    “oAuthEventcV”: “P2yQe5CjjZSldssPbDc6vQ.1”,
    “errorUrl”: “https://aka.ms/autherrors#error-InvalidGrant”,
    “requestId”: “fdfb03dc-fb78-e966-15c7-63bb0390c94d”,
    “date”: “2024-01-19T12:40:26”
    }
    }
    }

    1. Avatar photo
      Tony Redmond

      Guest members don’t have mailboxes, so they don’t have calendars… But what do you mean by retrieving the calendar of an external user? Do you mean their calendar on their host tenant?

  4. Paul Carter

    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.

    1. Avatar photo
      Tony Redmond

      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.

  5. SP

    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.

  6. Mathijs

    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?

  7. Giusseppe

    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!

    1. Avatar photo
      Tony Redmond

      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.

      1. Giusseppe

        Thank you!
        That response was freakishly fast.

  8. DanZi

    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…

    1. Avatar photo
      Tony Redmond

      What data are you trying to access and how are you accessing the data? It might be linked to delegate versus application permissions.

  9. Thomas

    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

    1. Avatar photo
      Tony Redmond

      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.

  10. Fernando Gualano

    Excellent Tony! Thank you for your comments!

  11. Fernando Gualano

    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!

    1. Avatar photo
      Tony Redmond

      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.

Leave a Reply