Skip to content

Instantly share code, notes, and snippets.

@JustinGrote
Last active November 6, 2024 10:21
Show Gist options
  • Save JustinGrote/656a01c1e047df940d577698ba62e3f2 to your computer and use it in GitHub Desktop.
Save JustinGrote/656a01c1e047df940d577698ba62e3f2 to your computer and use it in GitHub Desktop.
Get a list of application and delegated permissions for a service principal, similar to what the Azure Portal shows
#requires -version 7 -module Microsoft.Graph.Applications
using namespace Microsoft.Graph.PowerShell.Models
using namespace System.Collections.Generic
enum MicrosoftGraphServicePrincipalType {
Application
Delegated
}
class MgServicePrincipalPermission {
[MicrosoftGraphServicePrincipal]$ServicePrincipal
[string]$Id
[MicrosoftGraphServicePrincipalType]$Type
[string]$User
[string]$ResourceName
[string]$Permission
[string]$PermissionDisplayName
[string]$Description
[Nullable[DateTime]]$CreatedDateTime
}
Update-TypeData -TypeName 'MgServicePrincipalPermission' -DefaultDisplayPropertySet 'ServicePrincipal', 'ResourceName', 'User', 'Permission' -Force | Out-Null
function Get-MgServicePrincipalPermission {
<#
.SYNOPSIS
Retrieves the permissions assigned to a service principal, providing a similar output to what is on the Azure Portal Screen.
.EXAMPLE
Get-MgServicePrincipal -Search 'displayname:MyServicePrincipal' -Con eventual -Cou count | Get-MgServicePrincipalPermission
#>
[CmdletBinding(DefaultParameterSetName = 'Object')]
param(
[Parameter(ParameterSetName = 'Id', Position = 0, Mandatory, ValueFromPipelineByPropertyName)]
[Alias('Id')]
[string]$ServicePrincipalId,
[Parameter(ParameterSetName = 'Object', Position = 0, Mandatory, ValueFromPipeline)]
[Microsoft.Graph.PowerShell.Models.MicrosoftGraphServicePrincipal]$ServicePrincipal
)
begin {
#We use this to cache app info for permission lookups
[Dictionary[string, MicrosoftGraphServicePrincipal]]$spCache = @{}
}
process {
$ErrorActionPreference = 'Stop'
$ServicePrincipal ??= Get-MgServicePrincipal -ServicePrincipalId $ServicePrincipalId
#Add a ToString to the serviceprincipal so it summarizes correctly in the formatting
$ServicePrincipal | Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.DisplayName } -Force
#When using Mandatory above, it becomes an empty string instead of null, so null conditional cannot be used.
if ([string]::IsNullOrEmpty($ServicePrincipalId)) {
$ServicePrincipalId = $ServicePrincipal.Id
}
$appPermissions = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId
| Where-Object DeletedDateTime -EQ $null
foreach ($app in $appPermissions) {
$spCache[$app.ResourceId] ??= Get-MgServicePrincipal -ServicePrincipalId $app.ResourceId
[MicrosoftGraphAppRole]$role = $spCache[$app.ResourceId].AppRoles
| Where-Object Id -EQ $app.AppRoleId
if (-not $Role) { throw "No matching permission found for AppRoleID $($app.AppRoleId). This is a bug" }
[MgServicePrincipalPermission]@{
ServicePrincipal = $ServicePrincipal
Id = $app.Id
Type = 'Application'
User = '[Application]'
ResourceName = $app.ResourceDisplayName
Permission = $role.Value
PermissionDisplayName = $role.DisplayName
Description = $role.Description
CreatedDateTime = $app.CreatedDateTime
}
}
$delegatedPermissions = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $ServicePrincipalId
foreach ($permission in $delegatedPermissions) {
if (-not $spCache[$permission.ResourceId]) {
$spCache[$permission.ResourceId] = Get-MgServicePrincipal -ServicePrincipalId $permission.ResourceId
| Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.DisplayName } -Force -PassThru
}
$resource = $spCache[$permission.ResourceId]
foreach ($scope in $permission.Scope.split(' ')) {
$role = $resource.AppRoles | Where-Object Value -EQ $scope
[MgServicePrincipalPermission]@{
ServicePrincipal = $ServicePrincipal
Id = $scope.Id
Type = 'Delegated'
User = $permission.ConsentType -eq 'AllPrincipals' ? 'All' : $permission.PrincipalId
ResourceName = $resource.DisplayName
Permission = $scope
PermissionDisplayName = $role.DisplayName
Description = $role.Description
CreatedDateTime = $null
}
}
}
}
}
@JustinGrote
Copy link
Author

Try running using namespace Microsoft.Graph.PowerShell.Models before running the function. I also updated the function so it shouldn't error if this is missing now.

@rdantas9
Copy link

Try running using namespace Microsoft.Graph.PowerShell.Models before running the function. I also updated the function so it shouldn't error if this is missing now.

InvalidOperation:
Line |
8 | [Dictionary[string,MicrosoftGraphServicePrincipal]]$spCache = @{}
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Unable to find type [Dictionary].
Get-MgServicePrincipalPermission: Cannot index into a null array.

@JustinGrote
Copy link
Author

Are you running this in PowerShell 5.1? Try PowerShell 7, also try dot sourcing the script rather than cut-paste. For whatever reason you are not getting the namespace things at the top of the script to register correctly.

@rdantas9
Copy link

Are you running this in PowerShell 5.1? Try PowerShell 7, also try dot sourcing the script rather than cut-paste. For whatever reason you are not getting the namespace things at the top of the script to register correctly.

I'm running PowerShell 7.3.5. I'm trying to run Powershell script in another VM.

@JustinGrote
Copy link
Author

@rdantas9 works fine for me, not sure how you're invoking it that is causing the namespace errors.
image

@rdantas9
Copy link

@rdantas9 works fine for me, not sure how you're invoking it that is causing the namespace errors. image

I was able to run your PowerShell script properly, but I've replaced line 12 from MicrosoftGraphServicePrincipal to Microsoft.Graph.PowerShell.Models.MicrosoftGraphServicePrincipal.

Thanks for your patience and support!

@mark3grahams
Copy link

Hi, is 'Id = $app.Id' meant to be there in line 52 under $delegatedPermissions? If so, could you explain why?

@JustinGrote
Copy link
Author

Probably a cut/paste oopsie from the foreach loop above it, probably should be $scope.id

@mark3grahams
Copy link

Thanks. Appreciate all your work.

@JustinGrote
Copy link
Author

I've updated the script to include the original service principal as a reference while still reporting the name, and use a custom type for the permissions for better formatting.
image

@smcallister594
Copy link

Thanks @JustinGrote :)) Saved me reinventing the wheel here. Modified it a bit to list all the permissions in a gridview so I can select and delete Application permissions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment