Edit

Tutorial - Update item identity to Service Principal using PowerShell

The following tutorial shows how to update an item to a service principal using PowerShell.

Prerequisites

The following prerequisites are required.

  • Register an Entra ID application and note:
    • Tenant ID
    • Client ID
    • Client Secret
  • The app must have Power BI Service permissions Tenant.ReadWrite.All and an admin must have consented them.

Screenshot showing the tenat level permissions.

  • The SPN must have either Member, Contributor, or Admin access in Fabric to the workspace. The user needs to have write permission to an artifact and all children.

Screenshot showing the manage access of the Fabric workspace.

  • Service principals can call Fabric public APIs must be enabled in the Fabric portal Admin settings.

Screenshot showing Serice principals can call Fabric public APIs.

  • The Workspace ID, Item ID, and the client secret which will be used in the script.

Important

The client secret is used in the script for testing purposes. It is NOT recommended to store the client secret in the script in a production environment.

What this script does

This script does the following:

  • Signs in (via az login) and gets a token for https://api.fabric.microsoft.com/.
  • Calls the Assign Item Identity API.
  • Polls the operation until it returns the final assignmentStatus.

Background

In this tutorial we use a simple Workspace named Contoso_Dev_Test. This workspace contains one Lakehouse named My_Lakehouse.

Screenshot showing our workspace.

You can see in the screenshot that currently the owner of the Lakehouse is the Fabric Administrator. We want to update this so that the owner is our service principal.

Change ownership to a service principal

The following script is an example of how you can change a Microsft Fabric's items ownership to a service principal.

Note

Replace the ids with ids from your environment.

# ============================================
# Configuration
# ============================================

# Workspace and item you want to update
$workspaceId = "00000000-0000-0000-0000-000000000000"   # TODO: replace
$itemId      = "11111111-1111-1111-1111-111111111111"   # TODO: replace

# Entra app (service principal) settings
$tenantId     = "22222222-2222-2222-2222-222222222222"   # TODO: replace
$clientId     = "33333333-3333-3333-3333-333333333333"   # TODO: replace
$clientSecret = "YOUR-CLIENT-SECRET-HERE"                # TODO: secure this

# Fabric resource and base URL
$fabricResource = "https://api.fabric.microsoft.com"
$baseUrl        = "https://api.fabric.microsoft.com/v1"

# ============================================
# Get access token as the SPN (client credentials flow)
# ============================================

Write-Host "Acquiring token for service principal..." -ForegroundColor Cyan

$tokenRequestBody = @{
    client_id     = $clientId
    client_secret = $clientSecret
    scope         = "$fabricResource/.default"
    grant_type    = "client_credentials"
}

$tokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $tokenRequestBody -ContentType "application/x-www-form-urlencoded"

if (-not $tokenResponse.access_token) {
    throw "Failed to acquire access token for the service principal."
}

$accessToken = $tokenResponse.access_token
Write-Host "Token acquired." -ForegroundColor Green

# ============================================
# Call Update Item Identity API as the SPN
# ============================================

$assignUrl = "$baseUrl/workspaces/$workspaceId/items/$itemId/identities/default/assign?beta=true"


$requestBody = @{
    assignmentType = "Caller"
} | ConvertTo-Json

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type"  = "application/json"
}

Write-Host "Calling Assign Item Identity API as the service principal..." -ForegroundColor Cyan
Write-Host "POST $assignUrl" -ForegroundColor DarkGray

$response = Invoke-WebRequest -Method Post -Uri $assignUrl -Headers $headers -Body $requestBody -ErrorAction Stop

if ($response.StatusCode -ne 202) {
    Write-Warning "Expected 202 Accepted but got $($response.StatusCode)."
    Write-Host "Raw response:" -ForegroundColor Yellow
    $response.RawContent
    return
}

# Extract long-running operation headers
$operationLocation = $response.Headers["Location"]
$operationId       = $response.Headers["x-ms-operation-id"]
$retryAfter        = $response.Headers["Retry-After"]

# Headers may return as arrays — take the first element
if ($operationLocation -is [array]) { $operationLocation = $operationLocation[0] }
if ($operationId -is [array])       { $operationId = $operationId[0] }
if ($retryAfter -is [array])        { $retryAfter = $retryAfter[0] }

# Ensure retryAfter is a number
if (-not $retryAfter) {
    $retryAfter = 5
} else {
    $retryAfter = [int]$retryAfter
}
# ============================================
# Poll the long-running operation
# ============================================

$maxAttempts = 30
$attempt     = 0
$finalResult = $null

do {
    $attempt++
    Write-Host "[$attempt/$maxAttempts] Waiting $retryAfter seconds before polling..." -ForegroundColor DarkGray
    Start-Sleep -Seconds $retryAfter

    Write-Host "Polling operation status..." -ForegroundColor Cyan
    $opResponse = Invoke-RestMethod -Method Get -Uri $operationLocation -Headers $headers -ErrorAction Stop

    $status = $opResponse.status

    if (-not $status) {
        # Assume this is the final result payload.
        $finalResult = $opResponse
        Write-Host "Operation returned final result (no explicit status field)." -ForegroundColor Green
        break
    }

    Write-Host "Current operation status: $status" -ForegroundColor Yellow

    if ($status -in @("Succeeded", "Failed", "Canceled")) {
        $finalResult = $opResponse
        break
    }

} while ($attempt -lt $maxAttempts)

if (-not $finalResult) {
    throw "Operation didn't complete within $maxAttempts attempts."
}

# ============================================
# Print assignment status
# ============================================

Write-Host ""
Write-Host "Final assignment status:" -ForegroundColor Green

if ($finalResult.assignmentStatus) {
    $assignment = $finalResult.assignmentStatus
} elseif ($finalResult.result -and $finalResult.result.assignmentStatus) {
    $assignment = $finalResult.result.assignmentStatus
} else {
    Write-Warning "Could not find assignmentStatus in the final result. Dumping full payload."
    $finalResult | Format-List *
    return
}

$assignment | Format-Table itemId, parentItemId, status -AutoSize



Verifying ownership change.

Once you run the script, you should see output similar to the following:

Screenshot showing script execution.

Ownership has been successfully assigned to the SPN. You can verify this in the UI by looking at the workspace. You will see the ownership is now AssignApp.

Screenshot show UI ownership change.

You can also verify this in PowerShell by running the following:

Invoke-RestMethod -Uri "https://api.fabric.microsoft.com/v1/workspaces/{workspaceId}/items/{itemId}?include=defaultIdentity" -Headers @{ Authorization = "Bearer $token" } -Method Get

Screenshot shows ownership change via PowerShell.

The ownership is now the service principal.