Functions/Import-FabricItem.ps1


<#
    .SYNOPSIS
        Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source.
 
    .PARAMETER fileOverrides
        This parameter let's you override a PBIP file without altering the local file.
 
    .PARAMETER path
        The path to the PBIP files. Default value is '.\pbipOutput'.
 
    .PARAMETER workspaceId
        The ID of the Fabric workspace.
 
    .PARAMETER filter
        A filter to limit the search for PBIP files to specific folders.
 
    .EXAMPLE
        Import-FabricItems -path 'C:\PBIPFiles' -workspaceId '12345' -filter 'C:\PBIPFiles\Reports'
 
        This example imports PBIP files from the 'C:\PBIPFiles' folder into the Fabric workspace with ID '12345'. It only searches for PBIP files in the 'C:\PBIPFiles\Reports' folder.
 
    .NOTES
        This function requires the Invoke-FabricAPIRequest function to be available in the current session.
        This function was originally written by Rui Romano.
        https://github.com/RuiRomano/fabricps-pbip
    #>


Function Import-FabricItem {
    <#
    .SYNOPSIS
        Imports items using the Power BI Project format (PBIP) into a Fabric workspace from a specified file system source.
 
    .PARAMETER fileOverrides
        This parameter let's you override a PBIP file without altering the local file.
    #>

    [CmdletBinding()]
    param
    (
        [string]$path = '.\pbipOutput'
        ,
        [string]$workspaceId
        ,
        [string]$filter = $null
        ,
        [hashtable]$fileOverrides
    )

    # Search for folders with .pbir and .pbidataset in it

    $itemsFolders = Get-ChildItem  -Path $path -recurse -include *.pbir, *.pbidataset

    if ($filter) {
        $itemsFolders = $itemsFolders | where-object { $_.Directory.FullName -like $filter }
    }

    # Get existing items of the workspace

    $items = Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items" -Method Get

    write-output "Existing items: $($items.Count)"

    # Datasets first

    $itemsFolders = $itemsFolders | Select-Object  @{n = "Order"; e = { if ($_.Name -like "*.pbidataset") { 1 } else { 2 } } }, * | sort-object Order

    $datasetReferences = @{}

    foreach ($itemFolder in $itemsFolders) {
        # Get the parent folder

        $itemPath = $itemFolder.Directory.FullName

        write-output "Processing item: '$itemPath'"

        $files = Get-ChildItem -Path $itemPath -Recurse -Attributes !Directory

        # Remove files not required for the API: item.*.json; cache.abf; .pbi folder

        $files = $files | Where-Object { $_.Name -notlike "item.*.json" -and $_.Name -notlike "*.abf" -and $_.Directory.Name -notlike ".pbi" }

        # There must be a item.metadata.json in the item folder containing the item type and displayname, necessary for the item creation

        $itemMetadataStr = Get-Content "$itemPath\item.metadata.json"
        if ($fileOverrides) {
        $fileOverrideMatch = $fileOverrides.GetEnumerator() | where-object { "$itemPath\item.metadata.json" -ilike $_.Name } | select-object -First 1

        if ($fileOverrideMatch) {
            $itemMetadataStr = $fileOverrideMatch.Value
        }
    }
        $itemMetadata = $itemMetadataStr | ConvertFrom-Json
        $itemType = $itemMetadata.type

        if ($itemType -ieq "dataset") {
            $itemType = "SemanticModel"
        }
        

        $displayName = $itemMetadata.displayName

        $itemPathAbs = Resolve-Path $itemPath

        $parts = $files | ForEach-Object {

            #$fileName = $_.Name
            $filePath = $_.FullName
            if ($fileOverrides) {
            $fileOverrideMatch = $fileOverrides.GetEnumerator() | Where-Object { $filePath -ilike $_.Name } | select-object -First 1
            }
            if ($fileOverrideMatch) {
                $fileContent = $fileOverrideMatch.Value

                # convert to byte array

                if ($fileContent -is [string]) {
                    $fileContent = [system.Text.Encoding]::UTF8.GetBytes($fileContent)
                }
                elseif (!($fileContent -is [byte[]])) {
                    throw "FileOverrides value type must be string or byte[]"
                }
            }
            else {
                if ($filePath -like "*.pbir") {

                    $pbirJson = Get-Content -Path $filePath | ConvertFrom-Json

                    if ($pbirJson.datasetReference.byPath -and $pbirJson.datasetReference.byPath.path) {

                        # try to swap byPath to byConnection

                        $reportDatasetPath = (Resolve-path (Join-Path $itemPath $pbirJson.datasetReference.byPath.path.Replace("/", "\"))).Path

                        $datasetReference = $datasetReferences[$reportDatasetPath]

                        if ($datasetReference) {
                            # $datasetName = $datasetReference.name
                            $datasetId = $datasetReference.id

                            $newPBIR = @{
                                "version"          = "1.0"
                                "datasetReference" = @{
                                    "byConnection" = @{
                                        "connectionString"          = $null
                                        "pbiServiceModelId"         = $null
                                        "pbiModelVirtualServerName" = "sobe_wowvirtualserver"
                                        "pbiModelDatabaseName"      = "$datasetId"
                                        "name"                      = "EntityDataSource"
                                        "connectionType"            = "pbiServiceXmlaStyleLive"
                                    }
                                }
                            } | ConvertTo-Json

                            $fileContent = [system.Text.Encoding]::UTF8.GetBytes($newPBIR)

                        }
                        else {
                            throw "Item API dont support byPath connection, switch the connection in the *.pbir file to 'byConnection'."
                        }
                    } else {
                        $fileContent = Get-Content -Path $filePath -AsByteStream -Raw
                    }
                }
                else {
                  
                    $fileContent = Get-Content -Path $filePath -AsByteStream -Raw
                }
            }
            
            $partPath = $filePath.Replace($itemPathAbs, "").TrimStart("\").Replace("\", "/")
            write-host "Processing part: '$partPath'"
            $fileEncodedContent = [Convert]::ToBase64String($fileContent)

            Write-Output @{
                Path        = $partPath
                Payload     = $fileEncodedContent
                PayloadType = "InlineBase64"
            }
        }

        $itemId = $null

        # Check if there is already an item with same displayName and type

        $foundItem = $items | Where-Object { $_.type -ieq $itemType -and $_.displayName -ieq $displayName }

        if ($foundItem) {
            if ($foundItem.Count -gt 1) {
                throw "Found more than one item for displayName '$displayName'"
            }

            Write-output "Item '$displayName' of type '$itemType' already exists." -ForegroundColor Yellow

            $itemId = $foundItem.id
        }

        if ($null -eq $itemId) {
            write-output "Creating a new item"

            # Prepare the request

            $itemRequest = @{
                displayName = $displayName
                type        = $itemType
                definition  = @{
                    Parts = $parts
                }
            } | ConvertTo-Json -Depth 3

            $createItemResult = Invoke-FabricAPIRequest -uri "workspaces/$workspaceId/items"  -method Post -body $itemRequest

            $itemId = $createItemResult.id

            write-output "Created a new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green

            Write-Output $itemId
        }
        else {
            write-output "Updating item definition"

            $itemRequest = @{
                definition = @{
                    Parts = $parts
                }
            } | ConvertTo-Json -Depth 3
            Invoke-FabricAPIRequest -Uri "workspaces/$workspaceId/items/$itemId/updateDefinition" -Method Post -Body $itemRequest

            write-output "Updated new item with ID '$itemId' $([datetime]::Now.ToString("s"))" -ForegroundColor Green

            Write-Output $itemId
        }

        # Save dataset references to swap byPath to byConnection

        if ($itemType -ieq "semanticmodel") {
            $datasetReferences[$itemPath] = @{"id" = $itemId; "name" = $displayName }
        }
    }

}