Connect to the Graph in a Secure and Powerful Way
Updated: 1 August 2023
In a previous article, I discuss using cmdlets from the Microsoft Graph SDK for PowerShell to replace license management cmdlets from the Azure AD module in advance of their retirement on June 30, 2022 (update: Microsoft has moved this date out to March 2024). As it turns out, converting a couple of Azure AD cmdlets by replacing them with cmdlets like Get-MgUser and Set-MgUserLicense isn’t particularly difficult (if it was, I wouldn’t be able to do it). However, introducing a new module into the mix of PowerShell used in production environments always creates some questions and concerns, not least of which is security.
There’s a right and a wrong way to use the Graph SDK cmdlets in testing and production environments. In a nutshell, running the cmdlets interactively is straightforward, but could lead to a problem with an over-permissioned service principal (app). It’s all to do with the way the Graph SDK connects and the permissions it uses. Let’s examine why.
Read more: Microsoft Is Moving the Office 365 Service Communications API to the Graph
Connecting to the Graph SDK
The first step in any use of the Graph SDK is to connect to the Graph using the Connect-MgGraph cmdlet. When you run Connect-MgGraph to connect to the Graph, it’s wise to specify the identifier of the tenant to which you want to connect.
Connect-MgGraph -TenantId "828e1143-88e3-492b-bf82-24c4a47ada63"
If you don’t specify a tenant, Connect-MgGraph will choose the last tenant you signed into during a session (which might not be the one you want to connect to). I discovered this when I connected to the Graph and discovered that the data used belonged to my development tenant. I noticed then, but it’s possible that someone might miss this elsewhere, so make it a habit to connect with the tenant identifier.
A session lasts until you run Disconnect-MgGraph (see below) and can be reinitiated multiple times over days by running Connect-MgGraph. Behind the scenes, the Graph SDK keeps an encrypted token cache and will refresh the token as needed to allow you to work with Graph commands.
Permissions
After making a successful connection, the session signs in as your account. The permission scope for the connection comes from the service principal for the enterprise app registered in Entra ID for the SDK. If you’ve never signed in with the Graph SDK before, the SDK creates an enterprise app called Microsoft Graph Command Line Tools with an AppId of 14d82eec-204b-4c2f-b7e8-296a70dab67e and requests a limited set of permissions (Figure 1). If you’re an administrator, you can grant consent for these permissions on behalf of the organization. To ease the reasonable suspicions of vigilant administrators, it would be nice if the service principal showed up as verified, but that’s not currently the case.
The permissions consented for the Microsoft Graph PowerShell SDK are held by the enterprise app’s service principal. Importantly, the permissions are delegated permissions (they allow access to data that the signed-in user can access) rather than application permissions. If you want to use application permissions, which allow access to all data in the tenant, run Connect-Graph in app-only mode. This means that you specify the tenant, app, and credential to connect. In app-only mode, the SDK uses the application permissions consented to for the app. See the discussion below for more details.
Gathering New Permissions
As people begin executing Graph SDK commands using the interactive client, they will need consent for the permissions needed to run the commands. For example, to use the Get-MgUser cmdlet to retrieve a set of Entra ID accounts, a user needs permission to read directory information, so they might request the permissions using the Scope parameter when making the connect as follows:
$RequiredScopes = @("Directory.AccessAsUser.All", "Directory.ReadWrite.All") Connect-MgGraph -Scopes $RequiredScopes
Connecting with a scope which includes permissions not held by the enterprise app causes Entra ID to prompt for consent for the permissions not inherited from the service principal (Figure 2).
Like other apps which use the Graph APIs, an administrator consent to the requested permissions on behalf of the organization. This adds the extra permissions to the set held by the service principal. If an administrator does not grant consent, the requested permissions are available (subject to the administrative roles held by the signed-in account) for the session, but don’t join the set held by the service principal.
Over time, the set of delegated permissions held by the SDK’s service principal will be those granted at the initial time of consent plus any other permissions granted subsequently as people work with the interactive client. In other words, the service principal collects aggregated permissions over time. For this reason, it’s not recommended to use the Graph SDK cmdlets interactively because if you do, over time a distinct possibility exists that the service principal will become very over-permissioned and therefore becomes a security risk. This is the kind of issue highlighted by App Governance for Cloud App Security (or in the home-grown solution explained in this article). In addition, you should pay attention to apps assigned high-priority permissions because they’re the ones that might be either planted or exploited by attackers.
The only resolution for an over-permissioned service principal is its removal and recreation, at which time an administrator can grant consent for limited permissions to the new service principal. Here’s how to remove the service principal using Graph SDK cmdlets (naturally):
$Sp = Get-MgServicePrincipal -Filter "AppId eq '14d82eec-204b-4c2f-b7e8-296a70dab67e'" Remove-MgServicePrincipal -ServicePrincipalId $Sp.Id
V2.0 of the Microsoft Graph PowerShell SDK removes the need to switch profiles to use beta cmdlets. Instead, the beta cmdlets are in a separate module.
Reporting Your Connection
To check that you’re connected to the right tenant with the right profile and permissions, we can extract information about the tenant with the Get-MgOrganization cmdlet, the current connection with the Get-MgContext cmdlet, and the profile used with the Get-MgProfile cmdlet and display some useful information:
$Details = Get-MgContext $Scopes = $Details | Select -ExpandProperty Scopes $Scopes = $Scopes -Join ", " $OrgName = (Get-MgOrganization).DisplayName Clear-Host Write-Host "Microsoft Graph Connection Information" Write-Host "--------------------------------------" Write-Host " " Write-Host ("Connected to Tenant {0} ({1}) as account {2}" -f $Details.TenantId, $OrgName, $Details.Account) Write-Host "+-------------------------------------------------------------------------------------------------------------------+" Write-Host ("The following permission scope is defined: {0}" -f $Scopes) Write-Host "" Microsoft Graph Connection Information -------------------------------------- Connected to Tenant a662313f-14fc-43a2-9a7a-d2e27f4f3475 (Office 365 for IT Pros) as account Global.Administrator@office365itpros.com +-------------------------------------------------------------------------------------------------+ The following scopes are defined: Directory.AccessAsUser.All, Directory.ReadWrite.All, openid, profile, User.Read, email, Group.Read.All, Group.ReadWrite.All
The permissions listed above include those inherited from the service principal and any others requested by the user for the session.
Disconnect When You’re Done
When you’re finished interacting with the Graph, remember to close off the session by running Disconnect-MgGraph to sign the session out from the Graph. Disconnecting the session removes the encrypted token cache and prevents a session from being reinitialized.
Disconnect-MgGraph
App-Only Access for Production Use
The description above covers an interactive session. This is a good way to get to know the Graph SDK cmdlets and debug scripts in preparation for operational use. However, when you run SDK cmdlets interactively, you’re limited to delegate permissions. In other words, you can access items that you can interact with through Microsoft 365 apps, but you can’t access items owned by other users. For instance, you can read items in your mailbox but not in another mailbox. If you want to run the SDK with application permissions, you can do so with an app or in Azure Automation (using an Automation account or managed identity).
Because of the issues with consent and the service principal, Microsoft recommends that operational scripts use registered Entra ID apps with certificate-based authentication (app-only access) or a managed identity. You can certainly write and test scripts using the interactive client, but once the code is complete, it’s time to run it using its own app with a tightly scoped permission set. This approach means that you can restrict the permissions assigned to apps to only the set needed by the processing done by the script and restrict those who can use the app to a selected set of accounts. The downside of using separate apps with scoped permissions is that over time you might accumulate many registered apps in Entra ID which must be managed.
The alternative is to use Azure Automation with managed identities to run scripts that use SDK cmdlets. See this article for more information.
Some Issues for Microsoft to Solve
Having a service principal which diligently gathers permissions on an ongoing basis doesn’t seem to be a good idea. In fact, it’s a lousy idea. Microsoft needs to come up with a better way of allowing administrators to run Graph SDK cmdlets interactively without creating a security problem. Deleting the security principal when Disconnect-MgGraph is run one way to zeroize permissions, but it’s a poor solution. Allowing people to hard code usernames and passwords or use app secrets in scripts to authenticate access to the Graph is also not a direction to take given the current threat landscape.
The fact of the matter is that tenant administrators will forget about the permission accumulation issue unless they check service principals. It’s sad but true, and due to the demands of other work. People won’t run Disconnect-MgGraph either for the same reason as cmdlets like Disconnect-MicrosoftTeams and Disconnect-ExchangeOnline are ignored.
The Graph SDK is an excellent idea because it isolates PowerShell users from many of the complexities involved in using Graph APIs. It’s still a work in progress, but if Microsoft wants to make the Graph SDK a go-to tool for tenant administrators, they need to solve the permissions issue. And soon (before that deprecation deadline!)
I cant believe Microsoft completely disabled the Set-MSOLUserLIcnese options and is expecting everyone to just Graph? What Microsoft? was it to easy to use the MSOL cmdlets?
The MSOL cmdlets are based on an old (first generation) implementation of licensing technology created when Office 365 was first launched. Things are very different today. The suite supports over 400 million users and there are many more licenses and service plans in use. Microsoft moved to a new licensing plaform and that’s when the MSOL and AzureAD licensing cmdlets stoppe working.
Hi sir,
How create new user with details at a time. Please share PowerShell script.
1. Email id and license, first name ,last name ,display name, title, phone number
2.Reporting manager
3.Groups
4.MFA
I’m sure that if you did a search, you’d find many helpful articles like https://practical365.com/create-new-microsoft-365-account-powershell/ and https://practical365.com/azure-ad-account-creation-standardization/
If i have a partner center account, how can i connect and access all my customers using graph api powershell sdk?
Use Connect-MgGraph -TenantId xxx-xxx-xxx-xxx (insert tenant identifier of the tenant you want to connect to)
Hi Tony
I am new to Microsoft Graph and am hoping you could maybe assist me.
I am looking for a script to be able to remove O365 License from bulk users by importing a CSV file.
Is this possible to do.
I have found a way to add a O365 License to bulk users via a CSV file but am battling to find a way to remove the License from bulk users
Your assistance would be greatly appreciated.
Is the answer not in https://practical365.com/microsoft-365-license-graph-sdk/?
Bulk assignment is dealt with here https://practical365.com/bulk-license-assignment-with-the-microsoft-graph-powershell-sdk/. It’s just a matter of using the command to remove licenses as you process each account in the loop.
Hi Tony,
How does this work with GDAP as I receive the error AADSTS90099 when trying to connect to my client tenants?
.
AADSTS90099: The application ’14d82eec-204b-4c2f-b7e8-296a70dab67e’ (Microsoft Graph Command Line Tools) has not been authorized in the tenant ”. Applications must be authorized to access the customer tenant before partner delegated administrators can use them.
.
In the customer tenant, the Enterprise Application is there and is being used by customer accounts, but is there something that needs to be done to enable for partner GDAP admins?
The error indicates that the service principal for the Microsoft Graph Command Line Tools enterprise app has not been authorized in the tenant. I don’t know what process apps go through to allow them to run in the context of a GDAP tenant. I guess it is something covered in the GDAP documentation https://learn.microsoft.com/en-us/partner-center/gdap-introduction?WT.mc_id=M365-MVP-9501 but as I don’t have a GDAP tenant to play with, I can’t test or do anything else.
HI
im using
$Mbx = Get-ExoMailbox -Identity $Email | Select ExternalDirectoryObjectId, DisplayName, UserPrincipalName
…
# Get Token for Graph Api
$MsalResponse = Get-MsalToken -ClientId $appId -TenantId $tenantid -ClientSecret (ConvertTo-SecureString $appSecret -AsPlainText -Force) -Scopes “https://graph.microsoft.com/.default” -ForceRefresh
$GraphToken = $MsalResponse.AccessToken
# Connect Graph API
Connect-MgGraph -AccessToken $GraphToken
[array]$ContactsFolders = Get-MgUserContactFolder -UserId $Mbx.UserPrincipalName
$ContactFolder = $ContactsFolder | Where-Object {$_.DisplayName -eq “Contacts”}
but getting empty value for $ContactsFolders?
Any idea ?
Are you connecting to the Graph SDK interactively? If so, you use delegate access and can only work with your own data.
Seeing you’re getting an access token, why don’t you use the Graph API? If the client app has consent, you’ll be able to access the contact folder in any mailbox.
Hi Tony
thanks for you answer. I have registered app with full permission of application with secret key. How to use Graph API for getting contact from contact folder and manage them ? (create,update,delete contact ?) im beginner with graph api. Many thanks Pavel
Hi Tony, I’m able to create an app registration in order to access email messages. The problem for me is that once I do this, I can access any email account in the tenant. Is there a way to restrict the app to only be able to access a specific email account? I need to create a non-interactive script to download attachments from a specific email account but don’t want the the clientId/secret credentials in the script to have access to any other email account. Thanks, Brian.
You need to use RBAC for applications https://practical365.com/rbac-for-applications/ to limit access for an app to specific mailboxes.
Thank you very much! That worked and my app is now only able to read the email of a single account.
Hello Tony,
It is not possible to get Autopilot event with Connect-MgGraph in Azure Automation account.
Is this maybe something yoy have tried?
There’s a Graph API (https://learn.microsoft.com/en-us/graph/api/intune-troubleshooting-devicemanagementautopilotevent-list?view=graph-rest-beta) that you can call using Invoke-MgGraphRequest.
Hi Tony,
I want know cmdlets that is reset user password and don’t require update password when that user sign in with that password.
Also, I want do this with MgGraph.
This is my version of reset password in MgGraph
$user = “User Principal Name”
>>
>> $method = Get-MgUserAuthenticationPasswordMethod -UserId $user
>>
>> Reset-MgUserAuthenticationMethodPassword -UserId $user -AuthenticationMethodId $method.id -NewPassword “New Password”
But I can’t don’t require update password.
pls give some help for me.
Sorry for poor English. I’m japanese.
Thx
A quick check against the PowerShell chapter in the Office 365 for IT Pros eBook shows that you need to set up a password profile and then run Update-MgUser.
$NewPassword = @{}
$NewPassword[“Password”]= “!NewPassword2022!”
$NewPassword[“ForceChangePasswordNextSignIn”] = $False
Update-Mguser -UserId Terry.Hegarty@Office365itpros.com -PasswordProfile $NewPassword
Because the password profile ForceChangePasswordNextSignIn setting is False, the user is not forced to reset their password after logging in with the new password.
Thanks Tony.
It’s completely worked for me!!!
I’ll check the PowerShell chapter in the Office 365 for IT Pros eBook rn.
Anybody know any way to connect to multiple tenants at the same time with powershell graph? There’s no -Prefix parameter in Connect-MgGraph so even if you could i’m not sure how you’d differentiate between calls.
With Exchange powershell you could prefix all the commands and have Get-OrgAMailbox and Get-OrgBMailbox.
I’m looking at a case of needing to read data from one tenant and update data in another.
You can’t. You can switch from one tenant to another by running Connect-MgGraph -TenantId (always run Disconnect-MgGraph first) but there’s no notion of maintaining multiple connections to different tenants concurrently.
Tony,
Thank you for sharing, would it be possible to grant read-only (Azure Global Reader) to the MsGraph?
This is so the read-only script and lower-tier users can learn about Azure &M365 environments scripting safely.
You would be better off teaching people the fundamentals of the Graph using a development tenant https://office365itpros.com/2021/04/15/test-office-365-tenant/ That way, they can make mistakes without affecting anything too seriously. Once they get to the production tenant, you can restrict access using Graph permissions.
Hi Tony,
Thanks so much for the great write up. Regarding this section in your article:
“If an administrator does not grant consent, the requested permissions are available (subject to the administrative roles held by the signed-in account) for the session, but don’t join the set held by the service principal.”
My testing seems to indicate that an Application Administrator *does* need to grant consent when a new scope is specified (or the requesting user needs to be an Application Administrator themselves). I would love it if I could use the PS Graph SDK under my user (which at my org has User Administrator among other things but NOT Application Administrator) and not have to request admin consent every time I request a scope that my user has permissions for, but for which the Enterprise App does not.
Do you know a way I can achieve this?
No, I don’t.
But the Graph SDK is in a state of change with new versions appearing monthly, so what happens in one version might be different in the next. The important point to make is that the SDK is not really designed for interactive sessions, so I would not give “normal” users access to SDK interactive sessions because they then gain access to all the permissions held by the service principal. In many cases, dedicated registered apps with defined permissions are a better choice for lightly-permissioned administrators to work with.
Thanks again Tony, really appreciate your time and expertise.
I haven’t tested this, but I would have thought that because the permissions are delegated, even if the Enterprise App has permission for a given scope, the user requesting it would also need that permission. Or is that not the case?
Regarding your thoughts on using the PS Graph SDK interactively, it’s a fair point but if MS are looking to retire the Msol and AzureAD cmdlets I think interactive use is quite likely for one-off administrative scenarios that would be laborious via the various web interfaces. What do you think?
Yes, the permissions are delegated when you do interactive logins. Application permissions are only available when you have separate apps and can assign application permissions to those apps. So I think tenant administrators will still have the need to perform interactive queries and fix-ups, but if you’re handing things over to other people to do day-to-day maintenance, maybe it’s better to have scripts that use the registered apps?
Fair points. Lots to think about. Look forward to your future posts on the topic!
Regards,
Jamie
Hi Tony,
Nice Article! What’s the “June 30, 2022 deadline” ?
It was the original date for the retirement of the Azure AD and MSOL PowerShell modules. That date is now shifted to early 2023 with the exception of the license management cmdlets, which will stop working on 26 August 2022 when Microsoft 365 moves to a new license management platform.
HI Tony,
My powershell script is connected to Graph API using a secretkey with a registered app
Modules Microsoft.Graph.Authentication and Microsoft.Graph.Identity.DirectoryManagement are successfully imported.
use application permissions Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All for Get-MgOrganization.
I successfully could connect to Microsoft-graph but get this error
Get-MgOrganization : Authentication needed, call Connect-MgGraph
when running my script
Thanks
https://docs.microsoft.com/en-us/graph/api/organization-get?view=graph-rest-1.0&tabs=powershell
Best regards
Maybe you haven’t disconnected from your previous Graph session and the context you’re using is that session rather than the one you want to load?
Hi Tony,
Got any updates regarding the issues you raised for Microsoft to solve?
No. Things take time. Change in technology take even longer.
Hi Tony. Great article! Do you know if the app-only authentication of Graph SDK can be used without certificate? I would like to use it in a DevOps pipeline. Depending on a certificate makes the script quite challenging in this scenario.
You mean like using the Graph SDK with Azure Automation?: https://practical365.com/microsoft-graph-sdk-powershell-azure-automation/
It’s entirely possible to use app-only auth using a certificate in an Azure DevOps pipeline.
Make sure the certificate is stored in the Secure files library in your pipeline and then in your script you can authenticate using the following:
$mgCertPath = “$env:AGENT_TEMPDIRECTORY/”
$mgSecurePassword = ConvertTo-SecureString -AsPlainText -Force $mgPassword
$mgCertificate = New-Object System.Security.Cryptography.X509Certificate2($mgCertPath, $mgPassword)
Connect-MgGraph -Certificate $mgCertificate -TenantId $mgTenantId -ClientId $mgClientId
Make sure you are downloading the secure file first in your pipeline job. All files are temporarily stored in the $env:AGENT_TEMPDIRECTORY location as far as I’m aware in Azure DevOps
Something else I’ve noted, usually when using the PowerShell job in Pipelines, you would need to install the MgGraph cmdlets as part of the script. However, it appears that this is not needed.
Oops, missed a bit. $mgCertPath = “$env:AGENT_TEMPDIRECTORY/YourCertFile.pfx”
Thanks Steve… this is a bit of a journey for many people, so all advice is appreciated.
No problem at all, it’s all useful learning!
Thanks for sharing! Never thought of using secure files… I needed to add X509Certificates just before X509Certificate2
$mgCertificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($mgCertPath, $mgPassword)
That did the trick!
My alternative was to login using AccessToken. Just grab it
$accessToken = Get-AzAccessToken -ResourceUrl “https://graph.microsoft.com/”
Connect-MgGraph -AccessToken $accesstoken.Token
Rg./Irwin
Hi Tony,
Is it possible connect the module with specific account?
Something like below:
Connect-MgGraph -Scopes “User.ReadWrite.All”,”Directory.ReadWrite.All” -Credential $Credential
The Graph SDK uses a service principal to connect and automatically picks up the credentials of the signed-in account for interactive sessions. For jobs run in the background, I think you’ll need to use a registered app, but I will ask…
I received confirmation that you can’t pass an account identifier to have the SDK use it. Sorry!
Thanks for the confirmation.
I wonder if Azure Automation account possible to run command Get-MgUser?
(Planning to create licensing report as below URL advised)
https://practical365.com/create-licensing-report-microsoft365-tenant/
Azure Automation can definitely run Get-MgUser. You should be all set.
Thank you for this