top of page
Search

How to Find Required Graph API Permissions for any PowerShell Script

  • Writer: Gabriel Delaney
    Gabriel Delaney
  • Sep 13
  • 4 min read

I was helping someone who had written a PowerShell script against Entra with the Graph SDK. It worked perfectly for him during testing, but as soon as he shared it with others on his team, the script broke. His teammates had the same Entra roles but didn’t have all the same Graph scopes. He already had many of the scopes he needed from his day-to-day use so he needed some help identifying what specific scopes his teammates you need.


You’re probably familiar with Find-MgGraphCommand. It’s the foundation of this script and what i recommended to Jr. Engineer. It’s a pretty useful cmdlet. It works well for checking one cmdlet at a time. But once your scripts start to grow, looking them up line by line is slow. Which is why I wrote Get-GraphScriptPermissions. The script scales that out, it parses an entire file, uses regex to find all the Graph SDK cmdlets, and reports the effective least-privileged scopes they need. The hope is to have a tool that simplifies identifying prerequisites to run scripts built with the Graph SDK.


Let’s walk through install, the code, and how to use it.


Install

The script is available from the PowerShell Gallery:

Install-Script -Name Get-GraphScriptPermissions

Parameters

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true,Position=0)]
    [ValidateScript({Test-Path -Path $_})]
    [string]$ScriptPath,
    [Parameter(Mandatory=$false,Position=1)]
    [string]$OutputPath

)

This sets the inputs. You pass in the script you want to analyze, and optionally an output path if you want to export the results to CSV.


Finding Graph Cmdlets

Function Find-GraphCmdletString {
    [CmdletBinding()]
    [OutputType([system.object[]])]
    Param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [object]$Content
    
    )
    Begin {
        $exclusions = (Get-Command -Module Microsoft.Graph.Authentication).Name
        $approved_verbs = (Get-Verb).Verb
        $pattern = "($($approved_verbs -join '|'))-Mg\\w+"
        $line_number = 0
    } Process {
        foreach ($line in $content) {
            $line_number++
            If (!$line) { continue }
            $line = $line -replace '\\s*#.*$',''
            if ($line.Trim().StartsWith("#")) { continue }

            $cmdlet_matches = ($line | Select-String -Pattern $pattern -AllMatches).Matches.Value
            foreach ($cmdlet in $cmdlet_matches) {
                If ($cmdlet -in $exclusions) { continue }
                $obj = [ordered] @{}
                $obj["Cmdlet"] = $cmdlet
                $obj["Line"] = $line.Trim()
                $obj["LineNumber"] = $line_number
                [pscustomobject]$obj
            }
        }
    }
}

This function scans the script line by line and looks for Graph SDK cmdlets. The regex is built dynamically using Get-Verb so it always matches valid PowerShell verbs, instead of relying on a static list.


Getting Permissions

Function Get-GraphCmdletPermissions {
    [CmdletBinding()]
    [OutputType([System.Object])]
    Param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$Cmdlet,
        [Parameter(Mandatory=$false,Position=1)]
        [string]$ApiVersion = "v1.0"
    
    )
    Begin {
        # Create the parameters for Find-MgGraphCommand
        $find_cmd_params = @{}
        $find_cmd_params["Command"] = $cmdlet
        $find_cmd_params["ApiVersion"] = $apiVersion

        # Initialize the has_scope variable
        $has_scope = $false

        # Get the Graph context
        $context = Get-MgContext
        
        # If the context is not authenticated with Microsoft Graph, throw a warning
        If ($context) {
            $current_scopes = $context.Scopes
        
        } 
    } Process {
        Try {
            # Get the permissions for the cmdlet
            $permissions = (Find-MgGraphCommand @find_cmd_params ).Permissions | Where-Object { 
                $_.FullDescription -notlike "Allows you*" -and $_.FullDescription -notmatch "\byour\b" 
            
            }     
        } Catch {
            Write-Error "$($_.Exception.Message)" -ErrorAction Stop
        
        }

        # Check if the current scopes have any of the permissions
        Foreach ($scope in $current_scopes) {
            If ($scope -in $permissions.Name) {
                $has_scope = $true
                Break
            
            }
        }

        # Create the object
        $obj = [ordered] @{}
        $obj["Cmdlet"] = $cmdlet
        $obj["LeastPrivilegedEffectivePermission"] = If ($permissions) { $permissions[0].Name } Else { "None" }
        $obj["Description"] = If ($permissions) { $permissions[0].Description } Else { "None" }
        $obj["Permissions"] = If ($permissions) { ($permissions.Name | Select-Object -Unique) -join ", " } Else { "None" }
        $obj["HasScope"] = $has_scope

    } End {
        # Return the object
        [pscustomobject]$obj
    
    }
}

This function wraps Find-MgGraphCommand and trims the results. The filter removes “me-only” permissions (identified by descriptions like your profile) so the output focuses on scopes that are effective in automation.


Main Logic

$results = [System.Collections.Generic.List[System.Object]]::new()
$script_content = Get-Content -Path $scriptPath
$graph_cmdlets = $script_content | Find-GraphCmdletString
$grouped = $graph_cmdlets | Group-Object -Property Cmdlet

foreach ($group in $grouped) {
    $perm_info = Get-GraphCmdletPermissions -Cmdlet $group.Name
    $obj = [ordered] @{}
    $obj["Cmdlet"] = $group.Name
    $obj["LineNumbers"] = ($group.Group.LineNumber -join ", ")
    $obj["LeastPrivilegedEffectivePermission"] = $perm_info.LeastPrivilegedEffectivePermission
    $obj["Description"] = $perm_info.Description
    $obj["Permissions"] = $perm_info.Permissions | Select-Object -Unique
    $obj["HasScope"] = $perm_info.HasScope
    [void]$results.Add([pscustomobject]$obj)
}

if ($outputPath) {
    try {
        $results | Export-Csv -Path $outputPath -NoTypeInformation -Force
        Write-Output "Results exported to $outputPath"
    } catch {
        Write-Warning "Failed to export results to $outputPath. $($_.Exception.Message)"
    }
}

$results

The script groups cmdlets by name so each one is only checked once. That’s important for performance on larger files. Line numbers are merged so you still see every spot where the cmdlet is used.


Example Input Script

Here’s a simple script that disables a user. Why are we getting the user’s manager here? Don’t ask questions.

Get-MgUserManager -UserId test.user@contoso.com
Update-MgUser -UserId test.user@contoso.com -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId test.user@contoso.com

Using the Script

With everything in place, you can now run the analyzer. Point it at a script using the -ScriptPath parameter:

.\Get-GraphScriptPermissions.ps1 -ScriptPath .\demo.ps1

If you want to save the results:

.\Get-GraphScriptPermissions.ps1 -ScriptPath .\demo.ps1 -OutputPath .\report.csv

Analyzer Output

Cmdlet                             : Get-MgUserManager
LineNumbers                        : 1
LeastPrivilegedEffectivePermission : User.Read.All
Description                        : Read all users' full profiles
Permissions                        : User.Read.All, User.ReadWrite.All
HasScope                           : True

Cmdlet                             : Update-MgUser
LineNumbers                        : 2
LeastPrivilegedEffectivePermission : User.ReadWrite.All
Description                        : Read and write all users' full profiles
Permissions                        : User.ReadWrite.All, DeviceManagementServiceConfig.ReadWrite.All, DeviceManagementManagedDevices.ReadWrite.All, DeviceManagementConfiguration.ReadWrite.All, User.ManageIdentities.All, User.EnableDisableAccount.All, DeviceManagementApps.ReadWrite.All
HasScope                           : True

Cmdlet                             : Revoke-MgUserSignInSession
LineNumbers                        : 3
LeastPrivilegedEffectivePermission : User.ReadWrite.All
Description                        : Read and write all users' full profiles
Permissions                        : User.ReadWrite.All
HasScope                           : True

Limitations

This script is meant to simplify identifying Graph API permissions for PowerShell scripts, but there are a few caveats to keep in mind:

  • Least privilege is inferredThe script makes an assumption that the first permission that is returned is least privileged. It has been the case in all of my testing but I have not tested every cmdlet in the SDK.

  • Find-MgGraphCommand coverage is incompleteThe Microsoft Graph PowerShell SDK does not consistently return permissions for every cmdlet. Some commands simply don’t surface any scope information, which limits what the analyzer can report. That’s a Microsoft gap, not something the tool can solve.

  • Block comments aren’t parsed yetCmdlets that are inside <# … #> block comments may still show up in results. Inline # comments are handled, but multiline comments are not.


Wrap Up

This script doesn’t replace Find-MgGraphCommand. It builds on it. Instead of looking up one cmdlet at a time, you can run it against an entire file and get a consolidated report. It makes it easier to share scripts, confirm scopes up front, and avoid trial-and-error.

See the Install section above to grab it from the Gallery or GitHub if you want to try it out.

Thanks for reading The Tolkien Black Guy’s Substack! Subscribe for free to receive new posts and support my work.

 
 

Recent Posts

See All
Post: Blog2_Post

©2022 by thetolkienblackguy. Proudly created with Wix.com

bottom of page