Public/Deploy-ReportProject.ps1
Function Deploy-ReportProject { <# .SYNOPSIS Imports Power BI Project format (PBIP) files into a Fabric workspace. .DESCRIPTION This function deploys Power BI Project (PBIP) files (either .pbir or .pbism) from a specified file system path into a Microsoft Fabric workspace. It can create new items or update existing ones. The function handles: - Power BI Report (.pbir) files - Semantic Model (.pbism) files It extracts metadata from the project files and builds appropriate API requests to deploy to the target Fabric workspace. .PARAMETER path The file system path containing the Power BI Project files to import. Must contain either .pbir or .pbism files. .PARAMETER workspaceId The ID of the target Fabric workspace where items will be deployed. .PARAMETER itemProperties Optional hashtable to override item properties like type, displayName, or semanticModelId. Example: @{"type" = "SemanticModel"; "displayName" = "My Model"; "semanticModelId" = "12345abc-..."} .PARAMETER skipIfExists If specified, the function will not update items that already exist in the target workspace. .EXAMPLE Deploy-ReportProject -path "C:\Projects\MyReport" -workspaceId "12345678-abcd-1234-efgh-123456789012" Deploys all PBIP files from the specified path to the target workspace. .EXAMPLE Deploy-ReportProject -path "C:\Projects\MyModel" -workspaceId "12345678-abcd-1234-efgh-123456789012" ` -itemProperties @{"displayName"="Production Model"} -skipIfExists Deploys the model with a custom display name and skips updating if it already exists. .OUTPUTS Returns a hashtable with the deployed item's id, displayName and type. .NOTES For reports that reference semantic models by path, you must first deploy the semantic model and then provide its ID through the itemProperties.semanticModelId parameter. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$path , [Parameter(Mandatory)] [string]$workspaceId , [hashtable]$itemProperties , [switch]$skipIfExists ) # Search for folders with .pbir and .pbism in it $itemsInFolder = Get-ChildItem -LiteralPath $path | ? { @(".pbism", ".pbir") -contains $_.Extension } if ($itemsInFolder.Count -eq 0) { Write-Log "Cannot find valid item definitions (*.pbir; *.pbism) in the '$path'" return } if ($itemsInFolder | ? { $_.Extension -ieq ".pbir" }) { $itemType = "Report" } elseif ($itemsInFolder | ? { $_.Extension -ieq ".pbism" }) { $itemType = "SemanticModel" } else { throw "Cannot determine the itemType." } # Get existing items of the workspace #$items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get Write-Log "Existing items in the workspace: $($items.Count)" $files = Get-ChildItem -LiteralPath $path -Recurse -Attributes !Directory # Remove files not required for the API: item.*.json; cache.abf; .pbi folder $files = $files | ? { $_.Name -notlike "item.*.json" -and $_.Name -notlike "*.abf" -and $_.Directory.Name -notlike ".pbi" } # Prioritizes reading the displayName and type from itemProperties parameter $displayName = $null if ($itemProperties -ne $null) { $displayName = $itemProperties.displayName } # Try to read the item properties from the .platform file if not found in itemProperties if ((!$itemType -or !$displayName) -and (Test-Path -LiteralPath "$path\.platform")) { $itemMetadataStr = Get-Content -LiteralPath "$path\.platform" $fileOverrideMatch = $null if ($fileOverridesEncoded) { $fileOverrideMatch = $fileOverridesEncoded | ? { "$path\.platform" -ilike $_.Name } | select -First 1 if ($fileOverrideMatch) { Write-Log "File override '.platform'" $itemMetadataStr = [System.Text.Encoding]::UTF8.GetString($fileOverrideMatch.Value) } } $itemMetadata = $itemMetadataStr | ConvertFrom-Json $itemType = $itemMetadata.metadata.type $displayName = $itemMetadata.metadata.displayName Write-Log "Item type: $itemMetadata" } if (!$itemType -or !$displayName) { throw "Cannot import item if any of the following properties is missing: itemType, displayName" } $itemPathAbs = Resolve-Path -LiteralPath $path $parts = $files |% { $filePath = $_.FullName if ($filePath -like "*.pbir") { $fileContentText = Get-Content -LiteralPath $filePath $pbirJson = $fileContentText | ConvertFrom-Json $datasetId = $itemProperties.semanticModelId if ($datasetId -or ($pbirJson.datasetReference.byPath -and $pbirJson.datasetReference.byPath.path)) { if (!$datasetId) { throw "Cannot import directly a report using byPath connection. You must first resolve the semantic model id and pass it through the 'itemProperties.semanticModelId' parameter." } else { Write-Log "Report connected to semantic model: $datasetId" } if ($pbirJson.datasetReference.byPath) { $pbirJson.datasetReference.byPath = $null } Write-Log "Report connected to semantic model: $datasetId" $byConnectionObj = @{ "connectionString" = $null #"Data Source=powerbi://api.powerbi.com/v1.0/myorg/MojDev;Initial Catalog=test1;Access Mode=readonly;Integrated Security=ClaimsToken" "pbiServiceModelId" = $null "pbiModelVirtualServerName" = "sobe_wowvirtualserver" "pbiModelDatabaseName" = $datasetId "name" = "EntityDataSource" "connectionType" = "pbiServiceXmlaStyleLive" } $pbirJson.datasetReference | Add-Member -NotePropertyName "byConnection" -NotePropertyValue $byConnectionObj -Force $newPBIR = $pbirJson | ConvertTo-Json $fileContent = [system.Text.Encoding]::UTF8.GetBytes($newPBIR) } # if its byConnection then just send original else { $fileContent = [system.Text.Encoding]::UTF8.GetBytes($fileContentText) } } else { $fileContent = Get-Content -LiteralPath $filePath -AsByteStream -Raw } $partPath = if ([System.Environment]::OSVersion.Platform -eq 'Unix') { $filePath.Replace($itemPathAbs, "").TrimStart("/").Replace("\", "/") } else { $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/") } $fileEncodedContent = ($fileContent) ? [Convert]::ToBase64String($fileContent) : "" Write-Output @{ Path = $partPath Payload = $fileEncodedContent PayloadType = "InlineBase64" } } $parts | % { Write-Verbose "part: $($_.Path)" } Write-Log "Total parts: $($parts.Count)" $itemId = $null # Check if there is already an item with same displayName and type $foundItem = $items | ? { $_.type -ieq $itemType -and $_.displayName -ieq $displayName } if ($foundItem) { if ($foundItem.Count -gt 1) { throw "Found more than one item for displayName '$displayName'" } Write-Log "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow $itemId = $foundItem.id } if ($itemId -eq $null) { Write-Log "Creating a new item" # Prepare the request $itemRequest = @{ displayName = $displayName #type = $itemType definition = @{ Parts = $parts format = "PBIR" } } | ConvertTo-Json -Depth 3 Write-Log "Item request: $($parts)" foreach ($x in $parts) { Write-Log "Part: $($x.Path)" } $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/reports" -method Post -body $itemRequest #$createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/semanticModels" -method Post -body $itemRequest $itemId = $createItemResult.id Write-Log "Created a new item with ID '$itemId'" -ForegroundColor Green Write-Output @{ "id" = $itemId "displayName" = $displayName "type" = $itemType } } else { if ($skipIfExists) { Write-Log "Item '$displayName' of type '$itemType' already exists. Skipping." -ForegroundColor Yellow } else { Write-Log "Updating item definition" $itemRequest = @{ definition = @{ Parts = $parts } } | ConvertTo-Json -Depth 3 Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest Write-Log "Updated item with ID '$itemId'" -ForegroundColor Green } Write-Output @{ "id" = $itemId "displayName" = $displayName "type" = $itemType } } } |