What’s in the Session Context for an SDK Session and How to Use it

As the Microsoft 365 community continues on its journey to transform applications from older APIs to the Microsoft Graph, I continue to hear people struggling with permissions. In terms of PowerShell, the transition is stark. Many PowerShell modules, including those initially developed for use in on-premises deployments, exist to automate administrator functions and assume that the people who sign into a session with these modules hold the administrative role of managing the target workload.

The Exchange Online, SharePoint Online, and Teams modules work in this manner. Sign in with an administrator account and you can interact with the workload. Sign in with a “normal” user account, and you can’t.

Things are very different when working with the Microsoft Graph PowerShell SDK. Apart from some of the foibles exhibited by the SDK, you’re working with Graph APIs through PowerShell, and Graph APIs scope permissions to the lowest set required to do work. Any connection to the Graph involves running the Connect-MgGraph cmdlet to retrieve an access token which contains claims to Graph permissions. The access token also contains information about Entra ID administrator roles held by the account or app. Details of the permissions and roles can be found by examining the access token, while the Get-MgContext cmdlet reveals the session context, including authentication details.

The Two Types of SDK Sessions

The Microsoft Graph PowerShell SDK supports two types of sessions:

  • Delegated access is what happens when someone runs Connect-MgGraph in an interactive session. Delegated means that only delegated permissions are available during the session. In other words, the signed-in user can access whatever data permitted by the set of Graph permissions held by the service principal of the Microsoft Graph command line tools app PLUS whatever Entra ID administrator roles the account holds. The crossover between delegated permissions and administrator roles creates an extra level of complexity to understand. The SDK uses increment and dynamic user consent in its sessions. In other words, if the SDK doesn’t hold a required permission, you can add the permission(s) to the Scopes parameter for the Connect-MgGraph cmdlet to seek consent to use the permission during the session (and if you sign in with an administrator role, to add the permission to the set of delegated permissions registered for the SDK service principal).
  • App-only access occurs when a specific Entra ID app runs Connect-MgGraph. To connect, the app states its identifier, the tenant identifier, and either a secret or certificate registered for the app. Instead of the delegated permissions held by the SDK, Entra ID generates an access token containing claims for the permissions granted to the app. These are application permissions, not delegated permissions. The big difference is that application permissions allow access to data across the tenant, such as items in user OneDrive for Business accounts to report sharing permissions or to clean-out suspicious items from user mailboxes.

Figure 1 shows the result of running Get-MgContext for a delegated interaction session. Many permissions are available, but they are delegated permissions that depends on the access available to the current signed-in user.

Get-MgContext output for a delegated interaction session
Figure 1: Get-MgContext output for a delegated interaction session

Figure 2 shows the session context (sometimes called the authentication context) reported for an app-only session. In this case, it’s an interactive session, but you’d see the same for other kinds of situations, such as running an Azure automation runbook. Only two permissions are available. We can see that the app was authenticated using a certificate thumbprint.

The session context reported for an app-only interactive session with the Graph SDK
Figure 2: The session context reported for an app-only interactive session with the Graph SDK

Using the SDK Session Context in Scripts

The information available in the session context can be very useful in scripts. The most obvious element is the user principal name for the signed-in account, which is available in (Get-MgContext).Account. If necessary, you can retrieve other details about the user by running Get-MgUser:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$User = Get-MgUser -UserId (Get-MgContext).Account
$User = Get-MgUser -UserId (Get-MgContext).Account
$User = Get-MgUser -UserId (Get-MgContext).Account

Scripts often need to check if a connection exists and if the necessary permissions to work with data are available. The Get-MgContext cmdlet works by returning information about the session object ([Microsoft.Graph.PowerShell.Authentication.GraphSession]::Instance.AuthContext.) used to maintain the connection between the PowerShell session and Graph services. It’s usually faster to check the session object directly instead of retrieving it with Get-MgContext. This code shows how to use the session context to check if a Graph session is available and if the current session has permission to use the set of required scopes. If either a session or the required permission is unavailable, the code runs Connect-MgGraph.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[array]$RequiredScopes = "AuditLog.Read.All", "Directory.Read.All", "Group.ReadWrite.All", "User.Read.All", "Analytics.Read"
[array]$ScopesNotFound = @()
[array]$ScopesNeeded = @()
# Do we have a connection? If not, try and connect with the required scopes
$Context = [Microsoft.Graph.PowerShell.Authentication.GraphSession]::Instance.AuthContext | Out-Null
If ($null -eq $Context) {
Try {
Write-Host "No Graph connection available - attempting to connect..."
Connect-MgGraph -NoWelcome -Scopes $RequiredScopes -ErrorAction Stop
Write-Host "Successfully connected to Graph with required permissions"
} Catch {
Write-Host "Failure to connect to the Graph with required permissions. Exiting..."
Break
}
} Else {
# A connection is available. Check if it has the required scopes
ForEach ($Scope in $RequiredScopes) {
If ($Scope -notin $Context.Scopes) {
$ScopesNotFound += $Scope
}
}
If ($ScopesNotFound) {
$ScopesNeeded = $ScopesNotFound -join ", "
Write-Host ("The following scopes are not available: {0}" -f ($ScopesNeeded -join ", ")) -ForegroundColor Red
Try {
Connect-MgGraph -Scopes $ScopesNeeded -NoWelcome -ErrorAction Stop
Write-Host "Successfully connected to Graph with required scopes"
} Catch {
Write-Host "Failed to connect to Graph with required scopes"
Break
}
}
}
[array]$RequiredScopes = "AuditLog.Read.All", "Directory.Read.All", "Group.ReadWrite.All", "User.Read.All", "Analytics.Read" [array]$ScopesNotFound = @() [array]$ScopesNeeded = @() # Do we have a connection? If not, try and connect with the required scopes $Context = [Microsoft.Graph.PowerShell.Authentication.GraphSession]::Instance.AuthContext | Out-Null If ($null -eq $Context) { Try { Write-Host "No Graph connection available - attempting to connect..." Connect-MgGraph -NoWelcome -Scopes $RequiredScopes -ErrorAction Stop Write-Host "Successfully connected to Graph with required permissions" } Catch { Write-Host "Failure to connect to the Graph with required permissions. Exiting..." Break } } Else { # A connection is available. Check if it has the required scopes ForEach ($Scope in $RequiredScopes) { If ($Scope -notin $Context.Scopes) { $ScopesNotFound += $Scope } } If ($ScopesNotFound) { $ScopesNeeded = $ScopesNotFound -join ", " Write-Host ("The following scopes are not available: {0}" -f ($ScopesNeeded -join ", ")) -ForegroundColor Red Try { Connect-MgGraph -Scopes $ScopesNeeded -NoWelcome -ErrorAction Stop Write-Host "Successfully connected to Graph with required scopes" } Catch { Write-Host "Failed to connect to Graph with required scopes" Break } } }
[array]$RequiredScopes = "AuditLog.Read.All", "Directory.Read.All", "Group.ReadWrite.All", "User.Read.All", "Analytics.Read"
[array]$ScopesNotFound = @()
[array]$ScopesNeeded = @()

# Do we have a connection? If not, try and connect with the required scopes
$Context = [Microsoft.Graph.PowerShell.Authentication.GraphSession]::Instance.AuthContext | Out-Null
If ($null -eq $Context) {
  Try {
    Write-Host "No Graph connection available - attempting to connect..."
    Connect-MgGraph -NoWelcome -Scopes $RequiredScopes -ErrorAction Stop
    Write-Host "Successfully connected to Graph with required permissions"
  } Catch {   
    Write-Host "Failure to connect to the Graph with required permissions. Exiting..."
    Break
  }
} Else {
# A connection is available. Check if it has the required scopes
  ForEach ($Scope in $RequiredScopes) {
  If ($Scope -notin $Context.Scopes) {
    $ScopesNotFound += $Scope
    }
  }
  If ($ScopesNotFound) {
    $ScopesNeeded = $ScopesNotFound -join ", "
    Write-Host ("The following scopes are not available: {0}" -f ($ScopesNeeded -join ", ")) -ForegroundColor Red
    Try {
      Connect-MgGraph -Scopes $ScopesNeeded -NoWelcome -ErrorAction Stop 
      Write-Host "Successfully connected to Graph with required scopes"
    } Catch {
      Write-Host "Failed to connect to Graph with required scopes"
      Break
    }
  }
}

If a script must be run by an account holding a specific Entra ID administrative role, the code must check that the signed-in user has the role. This is easily done by adapting some of the code used in a script to report role assignments. This example checks if the signed-in account holds the Exchange administrator role.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[array]$DirectoryRoles = Get-MgRoleManagementDirectoryRoleDefinition | Sort-Object DisplayName
$ExoAdminRoleId = $DirectoryRoles | Where-Object {$_.DisplayName -eq "Exchange administrator"} | Select-Object -ExpandProperty Id
[array]$ActiveAssignments = Get-MgRoleManagementDirectoryRoleAssignmentSchedule -All -filter "RoleDefinitionId eq '$ExoAdminRoleId'" | Select-Object -ExpandProperty PrincipalId
$User = Get-MgUser -UserId (Get-MgContext).Account
If ($User.Id -notin $ActiveAssignments) {
Write-Host $User.DisplayName "is not an Exchange Online administrator"
}
Tony Redmond is not an Exchange Online administrator
[array]$DirectoryRoles = Get-MgRoleManagementDirectoryRoleDefinition | Sort-Object DisplayName $ExoAdminRoleId = $DirectoryRoles | Where-Object {$_.DisplayName -eq "Exchange administrator"} | Select-Object -ExpandProperty Id [array]$ActiveAssignments = Get-MgRoleManagementDirectoryRoleAssignmentSchedule -All -filter "RoleDefinitionId eq '$ExoAdminRoleId'" | Select-Object -ExpandProperty PrincipalId $User = Get-MgUser -UserId (Get-MgContext).Account If ($User.Id -notin $ActiveAssignments) { Write-Host $User.DisplayName "is not an Exchange Online administrator" } Tony Redmond is not an Exchange Online administrator
[array]$DirectoryRoles = Get-MgRoleManagementDirectoryRoleDefinition | Sort-Object DisplayName
$ExoAdminRoleId = $DirectoryRoles | Where-Object {$_.DisplayName -eq "Exchange administrator"} | Select-Object -ExpandProperty Id
[array]$ActiveAssignments = Get-MgRoleManagementDirectoryRoleAssignmentSchedule -All -filter "RoleDefinitionId eq '$ExoAdminRoleId'" | Select-Object -ExpandProperty PrincipalId
$User = Get-MgUser -UserId (Get-MgContext).Account
If ($User.Id -notin $ActiveAssignments) { 
   Write-Host $User.DisplayName "is not an Exchange Online administrator"
}

Tony Redmond is not an Exchange Online administrator

Remember that an account might hold several roles and it’s important to check for any role that might suffice. For instance, Global administrator or user administrator are enough to report user account information.

Code Development

When I use the Microsoft Graph PowerShell SDK to work on a script, I do the initial development in an interactive session to make sure that the basic code works. It’s easier to debug the code and you can run an interactive session in Visual Studio Code while using GitHub Copilot to assist with the code. When I’m happy that the code is close to complete, I move it to app-only mode and focus on the finishing touches, including establishing exactly what permissions and roles are necessary to make the code run.

Understanding the session context for Graph SDK sessions and how delegated and application permissions work is fundamental to a successful outcome. That is if you can get the code to work…

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.

Leave a Reply