
ChatGPT is getting better at these, damn! PS don’t blame me, I’m an engineer, not a designer!
A few weeks back, I hit a frustrating problem while shipping a Fabric project. I set up Fabric Deployment Pipelines, Microsoft’s next-gen pipelines that can push more item types than classic Power BI deployment pipelines. It all felt familiar and modern… until I tried to trigger the pipeline using the API and realised one critical piece was missing: updating the Power BI app. That capability exists in the new UI and in Power BI deployment pipelines, but not (yet) in Fabric Deployment Pipelines. And because a workspace can only belong to one pipeline at a time, you can’t have both. Lovely.
In this post, I’ll share the not-pretty-but-works workaround I’m using to get fully automated deployments and app updates, until Microsoft adds Fabric pipelines native support for app updates.
The workaround
To restate the problem: Fabric Deployment Pipelines can’t (yet) update a Power BI app when you trigger them through the API. Power BI Deployment Pipelines can, but a workspace can only sit in one pipeline at a time, so either a Fabric or a PowerBI deployment pipeline.
The trick is to temporarily move your Dev and Prod workspaces into a short-lived PowerBI deployment pipeline, run Deploy all with update app enabled, then put everything back in your original Fabric pipeline.
At a high level:
-
Create a temporary Power BI deployment pipeline
-
Find your Dev and Prod workspaces by name
-
Unassign them from the Fabric pipeline
-
Assign them to the temporary Power BI pipeline
-
Deploy all(Dev → Prod) with app update
-
Unassign from Power BI and reassign to Fabric deployment pipeline
-
Delete the temporary pipeline
It’s not pretty, but it’s safe, automated, and reliable until Microsoft wires app updates into Fabric’s pipeline API. Let’s look at the PowerShell that does this.
Prerequisites & assumptions
Before you run this flow, make sure you have:
-
A bearer token that works for your tenant against both Fabric and Power BI APIs (the script accepts it as input). If you prefer SPN login or MSAL, swap out the token section. The rest stays the same.
-
Dev and Prod workspaces already created and compatible (capacity and region expectations still apply).
-
A principal (user or service principal) that can:
• read Fabric workspaces
• manage Power BI pipelines
• deploy pipeline content
Tutorial: implementing the flow step by step
How I’d like to approach this is with some semi-pseudo code first. After explaining the steps, I’m showing a full working script that has
1) Inputs & auth
Pass in:
-
BearerToken
-
DevWorkspaceName, ProdWorkspaceName
-
Optional DeploymentNote
2) Find your workspaces (Fabric API)
We query GET /workspaces once and filter by displayName to grab the Dev/Prod IDs.
# 1) Resolve workspaces by name (Fabric)
$workspaces = Invoke-Json GET "$FabricBase/workspaces"
$devWs = $workspaces.value | Where-Object { $_.displayName -eq $DevWorkspaceName }
$prodWs = $workspaces.value | Where-Object { $_.displayName -eq $ProdWorkspaceName }
if (-not $devWs -or -not $prodWs) { throw "Workspace not found. Dev='$DevWorkspaceName' Prod='$ProdWorkspaceName'." }
$devId = $devWs.id
$prodId = $prodWs.id
Write-Host "Dev: $DevWorkspaceName ($devId)"
Write-Host "Prod: $ProdWorkspaceName ($prodId)"
3) Snapshot original assignments (Power BI API)
We list Power BI pipelines and their stages, recording where Dev/Prod were assigned (pipelineId + stage order). We’ll use this to restore afterward.
$orig = @{
DevPipelineId = $null; DevStageOrder = $null;
ProdPipelineId = $null; ProdStageOrder = $null
}
$pipelines = (Invoke-Json GET "$PbiBase/pipelines").value
foreach ($p in $pipelines) {
$stages = (Invoke-Json GET "$PbiBase/pipelines/$($p.id)/stages").value
foreach ($s in $stages) {
if ($s.workspaceId -eq $devId) { $orig.DevPipelineId = $p.id; $orig.DevStageOrder = $s.order }
if ($s.workspaceId -eq $prodId) { $orig.ProdPipelineId = $p.id; $orig.ProdStageOrder = $s.order }
}
}
4) Create a temporary pipeline
We POST /pipelines with a timestamped name. This gives us a clean, short-lived home to perform the app update.
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$tempName = "temp-app-update-$stamp"
$temp = Invoke-Json POST "$PbiBase/pipelines" @{ displayName = $tempName; description = "Temporary pipeline for app update" }
$tempId = $temp.id
Write-Host "Created temporary pipeline: $tempName ($tempId)"
5) Unassign from current pipeline(s)
If Dev or Prod were already in a pipeline, we POST /pipelines/{id}/stages/{order}/unassignWorkspace so we can reassign them to our temporary pipeline.
6) Assign to the temporary pipeline (Dev=0, Prod=1)
We POST /pipelines/{id}/stages/{order}/assignWorkspace twice: Dev → stage 0, Prod → stage 1.
$deployBody = @{
sourceStageOrder = $DevOrder
note = $DeploymentNote
options = @{ allowCreateArtifact = $true; allowOverwriteArtifact = $true }
updateAppSettings = @{ updateAdppInTargetWorkspace = $true }
}
$deploy = Invoke-Json POST "$PbiBase/pipelines/$tempId/deployAll" $deployBody
$opId = $deploy.id
Write-Host "Deploy operation: $opId"
7) Deploy with app update
We POST /pipelines/{id}/deployAll with:
-
sourceStageOrder = 0
-
options.allowCreateArtifact = true
-
options.allowOverwriteArtifact = true
-
updateAppSettings.updateAdppInTargetWorkspace = true
-
We then poll /operations/{operationId} until it completes.
# Poll until finished
do {
Start-Sleep -Seconds 10
$op = Invoke-Json GET "$PbiBase/pipelines/$tempId/operations/$opId"
Write-Host "Status: $($op.status)"
} while ($op.status -in @('NotStarted','Executing'))
if ($op.status -ne 'Succeeded') { throw "Deployment failed: $($op.status)" }
Write-Host "App update deployment succeeded."
8) Restore original assignments
If Dev/Prod were part of a pipeline before, we simply reassign them back to those original pipeline/stage pairs.
if ($orig.DevPipelineId) { Assign-Workspace $orig.DevPipelineId $orig.DevStageOrder $devId }
if ($orig.ProdPipelineId) { Assign-Workspace $orig.ProdPipelineId $orig.ProdStageOrder $prodId }
9) Delete the temporary pipeline
We DELETE /pipeline/{id} so nothing lingers.
Invoke-Json DELETE "$PbiBase/pipelines/$tempId" | Out-Null
Write-Host "Temporary pipeline deleted."
Why this is worth it
This workaround keeps Fabric Deployment Pipelines as your “real” deployment mechanism, but uses Power BI deployment pipelines briefly for the one thing Fabric’s pipeline API cannot do yet: updating the app.
You end up with:
-
fully automated deployments
-
consistent app updates
-
a clean rollback to the original pipeline configuration
-
no long-lived extra pipelines to manage
Until Microsoft closes the gap, this is a reliable way to keep your delivery pipeline truly end-to-end.
Hi, I’m Bastiaan 👋🏼 Founder of datalyft, a small Dutch company helping companies transform their raw data into real value. I write about the Modern Data Workflow, where I explore tools & processes to supercharge your data capabilities. Follow me for more!