# Add worker permissions

To grand the corrects permissions to the created worker follow the steps below:

Copy the script below into a file, save the file as add-permission

{% stepper %}
{% step %}

### Copy code and save file.&#x20;

Copy the code and save the file to grand-worker-permissions.ps1&#x20;

```ps1
#!/usr/bin/env pwsh
<#
.SYNOPSIS
    Assigns Microsoft Graph application permissions to a Worker's managed identity.

.DESCRIPTION
    Uses Microsoft.Graph.Authentication module for interactive browser login (supports
    Conditional Access / MFA). All Graph calls use Invoke-MgGraphRequest.

.PARAMETER TenantId
    Your Azure AD Tenant ID.

.PARAMETER WorkerName
    The App Service name (matches the system-assigned managed identity display name).

.PARAMETER Permissions
    Array of Microsoft Graph application permission names to assign.
    Defaults to the full set required by IntuneAssistant Worker.

.EXAMPLE
    # Use defaults (all required permissions)
    .\grand-worker-permissions.ps1 -TenantId "6a80764a-..." -WorkerName "my-worker"

.EXAMPLE
    # Assign only specific permissions
    .\grand-worker-permissions.ps1 -TenantId "6a80764a-..." -WorkerName "my-worker" `
        -Permissions @('Mail.Send', 'Group.Read.All')

.NOTES
    Requires: Application Administrator or Global Administrator role in Azure AD.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true, HelpMessage = "Your Azure AD Tenant ID")]
    [ValidateNotNullOrEmpty()]
    [string]$TenantId,

    [Parameter(Mandatory = $true, HelpMessage = "App Service name (= managed identity display name)")]
    [ValidateNotNullOrEmpty()]
    [string]$WorkerName,

    [Parameter(Mandatory = $false, HelpMessage = "Graph application permissions to assign")]
    [string[]]$Permissions = @(
        # Mail
        'Mail.Send',
        # Identity & directory
        'Group.Read.All',
        'GroupMember.Read.All',
        'User.ReadBasic.All',
        'RoleManagement.Read.Directory',
        'AuditLog.Read.All',
        'Application.Read.All',
        'AppRoleAssignment.Read.All',
        # Device management
        'DeviceManagementConfiguration.Read.All',
        'DeviceManagementScripts.Read.All',
        'DeviceManagementRBAC.Read.All',
        'DeviceManagementApps.Read.All',
        'DeviceManagementServiceConfig.Read.All',
        'DeviceManagementManagedDevices.Read.All',
        # Policy & monitoring
        'Policy.Read.ConditionalAccess',
        'ConfigurationMonitoring.ReadWrite.All'  # non-standard — skipped if not found
    )
)

$ErrorActionPreference = 'Stop'

# ─────────────────────────────────────────────────────────────────────────────
# CONSTANTS
# ─────────────────────────────────────────────────────────────────────────────
$MsGraphAppId = '00000003-0000-0000-c000-000000000000'  # Microsoft Graph
$GraphBaseUrl = 'https://graph.microsoft.com/v1.0'

# ─────────────────────────────────────────────────────────────────────────────
# ENSURE MODULE (authentication only — not the full Graph SDK)
# ─────────────────────────────────────────────────────────────────────────────
$moduleName = 'Microsoft.Graph.Authentication'
if (-not (Get-Module -ListAvailable -Name $moduleName)) {
    Write-Host "Module '$moduleName' not found. Installing..." -ForegroundColor Yellow
    Install-Module $moduleName -Scope CurrentUser -Force -AllowClobber
    Write-Host "  ✓ Installed" -ForegroundColor Green
}
Import-Module $moduleName -ErrorAction Stop

# ─────────────────────────────────────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────────────────────────────────────
function Invoke-Graph {
    param(
        [string]$Path,
        [string]$Method = 'GET',
        [hashtable]$Body
    )
    $params = @{
        Uri        = "$GraphBaseUrl$Path"
        Method     = $Method
        OutputType = 'PSObject'
    }
    if ($Body) { $params.Body = ($Body | ConvertTo-Json -Depth 5) }
    return Invoke-MgGraphRequest @params
}

function Get-GraphAllPages {
    param([string]$Path)
    $items = [System.Collections.Generic.List[object]]::new()
    $uri = "$GraphBaseUrl$Path"
    while ($uri) {
        $resp = Invoke-MgGraphRequest -Uri $uri -Method GET -OutputType PSObject
        if ($resp.value) { $items.AddRange([object[]]$resp.value) }
        $uri = $resp.'@odata.nextLink'
    }
    return $items
}

# ─────────────────────────────────────────────────────────────────────────────
# BANNER
# ─────────────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host "  Worker Managed Identity — Graph Permission Setup" -ForegroundColor Cyan
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host "  TenantId   : $TenantId"
Write-Host "  WorkerName : $WorkerName"
Write-Host "  Permissions: $($Permissions.Count) requested"
Write-Host ""

# ─────────────────────────────────────────────────────────────────────────────
# STEP 1: INTERACTIVE LOGIN
# ─────────────────────────────────────────────────────────────────────────────
Write-Host "[1/5] Opening browser for interactive login..." -ForegroundColor Yellow
Write-Host "      (MFA and Conditional Access are fully supported)" -ForegroundColor Gray

Connect-MgGraph `
    -TenantId $TenantId `
    -Scopes 'Application.Read.All', 'AppRoleAssignment.ReadWrite.All' `
    -NoWelcome

$ctx = Get-MgContext
Write-Host "  ✓ Signed in as : $($ctx.Account)" -ForegroundColor Green
Write-Host "  ✓ Tenant       : $($ctx.TenantId)" -ForegroundColor Gray

# ─────────────────────────────────────────────────────────────────────────────
# STEP 2: FIND MANAGED IDENTITY SERVICE PRINCIPAL
# ─────────────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "[2/5] Searching for managed identity: '$WorkerName'..." -ForegroundColor Yellow

$encoded    = [uri]::EscapeDataString("displayName eq '$WorkerName'")
$spResp     = Invoke-Graph -Path "/servicePrincipals?`$filter=$encoded&`$select=id,displayName,appId,servicePrincipalType"
$allMatches = @($spResp.value)

if ($allMatches.Count -eq 0) {
    Write-Error @"

No service principal found with displayName '$WorkerName'.

Make sure:
  1. The App Service exists in this tenant
  2. System-assigned managed identity is ENABLED
     Azure Portal → App Service → Identity → System assigned → Status: On
  3. The name matches exactly (case-sensitive)
"@
    Disconnect-MgGraph | Out-Null
    exit 1
}

$managedIdentity = $allMatches |
    Where-Object { $_.servicePrincipalType -eq 'ManagedIdentity' } |
    Select-Object -First 1
if (-not $managedIdentity) { $managedIdentity = $allMatches[0] }

Write-Host "  ✓ Found: $($managedIdentity.displayName)" -ForegroundColor Green
Write-Host "    Object ID : $($managedIdentity.id)" -ForegroundColor Gray
Write-Host "    Type      : $($managedIdentity.servicePrincipalType)" -ForegroundColor Gray

if ($managedIdentity.servicePrincipalType -ne 'ManagedIdentity') {
    Write-Warning "Type is not 'ManagedIdentity' — verify system-assigned identity is enabled on the App Service."
}

# ─────────────────────────────────────────────────────────────────────────────
# STEP 3: FIND MICROSOFT GRAPH SP + REQUIRED APP ROLES
# ─────────────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "[3/5] Finding Microsoft Graph service principal and app roles..." -ForegroundColor Yellow

$graphFilter = [uri]::EscapeDataString("appId eq '$MsGraphAppId'")
$graphResp   = Invoke-Graph -Path "/servicePrincipals?`$filter=$graphFilter&`$select=id,displayName,appRoles"
$graphSp     = @($graphResp.value)[0]

if (-not $graphSp) {
    Write-Error "Microsoft Graph service principal not found in tenant '$TenantId'."
    Disconnect-MgGraph | Out-Null
    exit 1
}
Write-Host "  ✓ Found Microsoft Graph (Object ID: $($graphSp.id))" -ForegroundColor Green

$roleMap = [ordered]@{}
foreach ($permName in $Permissions) {
    $role = $graphSp.appRoles | Where-Object {
        $_.value -eq $permName -and $_.allowedMemberTypes -contains 'Application'
    }
    if ($role) {
        $roleMap[$permName] = $role.id
        Write-Host "  ✓ Located : $permName" -ForegroundColor Gray
    } else {
        Write-Warning "  Skipped  : '$permName' — not found as application permission in Microsoft Graph"
    }
}

if ($roleMap.Count -eq 0) {
    Write-Error "None of the requested permissions could be located in Microsoft Graph."
    Disconnect-MgGraph | Out-Null
    exit 1
}

# ─────────────────────────────────────────────────────────────────────────────
# STEP 4: CHECK EXISTING ASSIGNMENTS
# ─────────────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "[4/5] Checking existing assignments on managed identity..." -ForegroundColor Yellow

$existing = Get-GraphAllPages -Path "/servicePrincipals/$($managedIdentity.id)/appRoleAssignments"
Write-Host "  Found $($existing.Count) existing app role assignment(s)" -ForegroundColor Gray

# ─────────────────────────────────────────────────────────────────────────────
# STEP 5: ASSIGN MISSING PERMISSIONS
# ─────────────────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "[5/5] Assigning missing permissions..." -ForegroundColor Yellow

$assigned = 0; $alreadyHad = 0; $failed = 0

foreach ($permName in $roleMap.Keys) {
    $roleId          = $roleMap[$permName]
    $alreadyAssigned = $existing | Where-Object {
        $_.resourceId -eq $graphSp.id -and $_.appRoleId -eq $roleId
    }

    if ($alreadyAssigned) {
        Write-Host "  ○ Already assigned : $permName" -ForegroundColor Cyan
        $alreadyHad++
        continue
    }

    try {
        Invoke-Graph `
            -Path "/servicePrincipals/$($managedIdentity.id)/appRoleAssignments" `
            -Method POST `
            -Body @{
                principalId = $managedIdentity.id
                resourceId  = $graphSp.id
                appRoleId   = $roleId
            } | Out-Null

        Write-Host "  ✓ Assigned         : $permName" -ForegroundColor Green
        $assigned++
    }
    catch {
        Write-Host "  ✗ Failed           : $permName — $($_.Exception.Message)" -ForegroundColor Red
        $failed++
    }
}

# ─────────────────────────────────────────────────────────────────────────────
# SUMMARY
# ─────────────────────────────────────────────────────────────────────────────
Disconnect-MgGraph | Out-Null

Write-Host ""
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host "  Summary" -ForegroundColor Cyan
Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
Write-Host "  Worker         : $($managedIdentity.displayName)"
Write-Host "  Object ID      : $($managedIdentity.id)"
Write-Host "  Newly assigned : $assigned" -ForegroundColor $(if ($assigned -gt 0) { 'Green' } else { 'White' })
Write-Host "  Already had    : $alreadyHad"
Write-Host "  Failed         : $failed"   -ForegroundColor $(if ($failed -gt 0) { 'Red' } else { 'White' })

if ($failed -gt 0) {
    Write-Host ""
    Write-Host "    Failures may indicate insufficient role." -ForegroundColor Yellow
    Write-Host "    Required: Application Administrator or Global Administrator" -ForegroundColor Yellow
}

if ($assigned -gt 0 -or $alreadyHad -gt 0) {
    Write-Host ""
    Write-Host "    IMPORTANT — Exchange Online step still required for Mail.Send" -ForegroundColor Yellow
    Write-Host "    Connect-ExchangeOnline" -ForegroundColor White
    Write-Host "    Test-ApplicationAccessPolicy -Identity <sender@domain> -AppId $($managedIdentity.id)" -ForegroundColor White
}

Write-Host ""
Write-Host "  Done!" -ForegroundColor Green
Write-Host ""

exit $(if ($failed -gt 0) { 1 } else { 0 })
```

{% endstep %}

{% step %}

### Open a PowerShell window

Run the PowerShell file using the example below.&#x20;

```
.\grand-worker-permissions.ps1 -TenantId "6a80764a-..." -WorkerName "my-worker"

```

{% endstep %}
{% endstepper %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.intuneassistant.cloud/extensions/worker/add-worker-permissions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
