Friday, October 16, 2026

Power Platform ALM with GitHub Actions: Export, Source-Control and Deploy a Cloud Flow Solution (Step by Step)

Moving Power Automate flows between environments by hand (export a zip, import a zip, fix connections, repeat) does not scale and leaves no history. In this walkthrough I build a small but complete CI/CD pipeline with GitHub Actions and the official Microsoft Power Platform Actions that:

  1. Exports a solution from a Dev environment (unmanaged + managed)
  2. Unpacks it into readable source files and commits them to GitHub
  3. Packs it as managed and imports it into a Target environment
  4. Sets environment variable values per environment using a deployment settings file

Along the way I hit three real errors. I've documented each one with the cause and the fix, because that is where most of the time goes.


What you'll build

Component Purpose
Solution CICDSampleSolution Container for everything that moves between environments
Cloud flow CICD Sample - Send Notification Manual trigger → Compose → Office 365 Outlook Send an email (V2)
Environment variable cicddemo_NotificationRecipient The email To address, different per environment
Connection reference (Office 365 Outlook) Lets the flow's connection be swapped per environment
Entra ID app registration Service principal the pipeline signs in as
GitHub workflow 1 - Export Dev → Git (unpacked source)
GitHub workflow 2 - Deploy Git → Target (managed import)
 Dev environment ──(1 Export: export + unpack + commit)──► GitHub repo (main)
                                                              │
                                                              ▼
 Target environment ◄──(2 Deploy: pack managed + import + settings file)──┘

Prerequisites

Requirement Notes
Two Power Platform environments with Dataverse Source (Dev) and Target. A Developer environment works fine as a target.
Permission to create an Entra ID app registration To create the service principal
System Administrator security role in both environments You need it yourself to grant it to the app user (see Error 2)
A GitHub account Private repository is fine

Step 1: Create a publisher and solution

In make.powerapps.com → Solutions → New solution, create a publisher with your own prefix rather than using the default publisher. The prefix becomes part of every component name (for example cicddemo_NotificationRecipient), so pick it once and keep it.

New publisher

Then create the solution itself:

Field Value
Display name CICD Sample Solution
Name CICDSampleSolution (this is what the pipeline uses)
Publisher the publisher you just created

New solution

Step 2: Add an environment variable

Inside the solution: New → More → Environment variable.

Field Value
Display name Notification Recipient
Name cicddemo_NotificationRecipient
Data type Text
Default value an email address for the Dev environment

Environment variable

Step 3: Build a simple cloud flow inside the solution

New → Automation → Cloud flow → Instant, trigger Manually trigger a flow.

Instant flow

Add three steps:

  1. Compose with a static message.
  2. Send an email (V2) (Office 365 Outlook).
  3. Set To to the Notification Recipient environment variable (Dynamic content → Environment Variables), Body to the Compose Outputs.

Flow design

Tip: if the solution does not show a connection reference after you save the flow, select the flow → Advanced → Add required objects. That pulls the connection reference into the solution. Without it, the target environment has nothing to bind a connection to.

Solution components

Step 4: Create the service principal

  1. Entra admin center → App registrations → New registration. Single tenant, no redirect URI.
  2. Certificates & secrets → New client secret. Copy the value immediately; it's shown only once.
  3. In Power Platform admin center, for each environment: Settings → Users + permissions → Application users → New app user → pick the app → root business unit → System Administrator.

App registration

Step 5: Create the GitHub repository, secrets and variables

Create a private repository and add:

Type Name Value
Secret PP_CLIENT_ID Application (client) ID
Secret PP_TENANT_ID Directory (tenant) ID
Secret PP_CLIENT_SECRET Client secret value
Variable SOURCE_ENVIRONMENT_URL https://<dev-org>.crm.dynamics.com
Variable TARGET_ENVIRONMENT_URL https://<target-org>.crm.dynamics.com

Environment URLs aren't secret, so repository variables keep them readable. Only the three identity values go into secrets.

Repository variables

Step 6: Workflow 1 — Export from Dev and commit to Git

.github/workflows/export-solution.yml

name: 1 - Export solution from Source (Dev)

# Exports the unmanaged + managed solution from the source environment,
# unpacks it into source-controlled files and commits them to main.

on:
  workflow_dispatch:
    inputs:
      solution_name:
        description: "Solution unique name"
        required: true
        default: "CICDSampleSolution"

permissions:
  contents: write

env:
  SOURCE_ENVIRONMENT_URL: ${{ vars.SOURCE_ENVIRONMENT_URL }}
  SOLUTION_NAME: ${{ github.event.inputs.solution_name }}
  SOLUTION_EXPORT_DIR: out/exported
  SOLUTION_SOURCE_FOLDER: solutions

jobs:
  export-from-source:
    runs-on: windows-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Install Power Platform Tools
        uses: microsoft/powerplatform-actions/actions-install@v1

      - name: Who am I (verify service principal connection)
        uses: microsoft/powerplatform-actions/who-am-i@v1
        with:
          environment-url: ${{ env.SOURCE_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}

      - name: Publish all customizations
        uses: microsoft/powerplatform-actions/publish-solution@v1
        with:
          environment-url: ${{ env.SOURCE_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}

      - name: Export unmanaged solution
        uses: microsoft/powerplatform-actions/export-solution@v1
        with:
          environment-url: ${{ env.SOURCE_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}
          solution-name: ${{ env.SOLUTION_NAME }}
          solution-output-file: ${{ env.SOLUTION_EXPORT_DIR }}/${{ env.SOLUTION_NAME }}.zip
          managed: false

      - name: Export managed solution
        uses: microsoft/powerplatform-actions/export-solution@v1
        with:
          environment-url: ${{ env.SOURCE_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}
          solution-name: ${{ env.SOLUTION_NAME }}
          solution-output-file: ${{ env.SOLUTION_EXPORT_DIR }}/${{ env.SOLUTION_NAME }}_managed.zip
          managed: true

      - name: Unpack solution (Both)
        uses: microsoft/powerplatform-actions/unpack-solution@v1
        with:
          solution-file: ${{ env.SOLUTION_EXPORT_DIR }}/${{ env.SOLUTION_NAME }}.zip
          solution-folder: ${{ env.SOLUTION_SOURCE_FOLDER }}/${{ env.SOLUTION_NAME }}
          solution-type: Both
          overwrite-files: true

      - name: Commit unpacked solution to main
        shell: pwsh
        run: |
          git config user.name  "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add "${{ env.SOLUTION_SOURCE_FOLDER }}/${{ env.SOLUTION_NAME }}"
          if (git diff --cached --quiet) {
            Write-Host "No solution changes to commit."
          } else {
            git commit -m "Export $env:SOLUTION_NAME from source environment (run $env:GITHUB_RUN_NUMBER)"
            git push origin HEAD:main
          }
Step Action Why
Install Power Platform Tools actions-install@v1 Installs the Power Platform CLI on the runner. Must be first.
Who am I who-am-i@v1 Fails fast if the service principal can't reach the environment
Publish publish-solution@v1 Ensures unpublished edits are included
Export ×2 export-solution@v1 Unmanaged (for source) + managed (so unpack can capture both)
Unpack unpack-solution@v1 with solution-type: Both Turns the zip into reviewable XML/JSON files
Commit git in PowerShell Pushes the unpacked source to main

Run export

Step 7: Deployment settings file

The deployment settings file is how you give the target environment its own environment variable values (and optionally connections) without anyone clicking through import dialogs.

deployment-settings/CICDSampleSolution.COE.json

{
  "EnvironmentVariables": [
    {
      "SchemaName": "cicddemo_NotificationRecipient",
      "Value": "target-team@contoso.com"
    }
  ],
  "ConnectionReferences": [
    {
      "LogicalName": "new_sharedoffice365_xxxxx",
      "ConnectionId": "",
      "ConnectorId": "/providers/Microsoft.PowerApps/apis/shared_office365"
    }
  ]
}

You can generate a starter version with pac solution create-settings --solution-zip <zip> --settings-file <file> and fill in the values.

Step 8: Workflow 2 — Deploy to Target as managed

.github/workflows/deploy-solution.yml

name: 2 - Deploy solution to Target (COE)

# Packs the source-controlled solution as MANAGED and imports it into the
# target environment, applying environment variable values and connection
# references from a deployment settings file.

on:
  workflow_dispatch:
    inputs:
      solution_name:
        description: "Solution unique name"
        required: true
        default: "CICDSampleSolution"
  workflow_run:
    workflows: ["1 - Export solution from Source (Dev)"]
    types: [completed]

env:
  TARGET_ENVIRONMENT_URL: ${{ vars.TARGET_ENVIRONMENT_URL }}
  SOLUTION_NAME: ${{ github.event.inputs.solution_name || 'CICDSampleSolution' }}
  SOLUTION_SOURCE_FOLDER: solutions
  BUILD_DIR: out/build

jobs:
  deploy-to-target:
    # Run on manual trigger, or after a successful export run
    if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
    runs-on: windows-latest
    environment: COE
    steps:
      - name: Checkout repository (latest main)
        uses: actions/checkout@v4
        with:
          ref: main

      - name: Install Power Platform Tools
        uses: microsoft/powerplatform-actions/actions-install@v1

      - name: Pack managed solution
        uses: microsoft/powerplatform-actions/pack-solution@v1
        with:
          solution-folder: ${{ env.SOLUTION_SOURCE_FOLDER }}/${{ env.SOLUTION_NAME }}
          solution-file: ${{ env.BUILD_DIR }}/${{ env.SOLUTION_NAME }}_managed.zip
          solution-type: Managed

      - name: Who am I (verify target connection)
        uses: microsoft/powerplatform-actions/who-am-i@v1
        with:
          environment-url: ${{ env.TARGET_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}

      - name: Import managed solution into target
        uses: microsoft/powerplatform-actions/import-solution@v1
        with:
          environment-url: ${{ env.TARGET_ENVIRONMENT_URL }}
          app-id: ${{ secrets.PP_CLIENT_ID }}
          client-secret: ${{ secrets.PP_CLIENT_SECRET }}
          tenant-id: ${{ secrets.PP_TENANT_ID }}
          solution-file: ${{ env.BUILD_DIR }}/${{ env.SOLUTION_NAME }}_managed.zip
          use-deployment-settings-file: true
          deployment-settings-file: deployment-settings/${{ env.SOLUTION_NAME }}.COE.json
          force-overwrite: true
          publish-changes: true
          run-asynchronously: true
          max-async-wait-time: 60

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.SOLUTION_NAME }}-managed
          path: ${{ env.BUILD_DIR }}/${{ env.SOLUTION_NAME }}_managed.zip

Key points:

Setting Why
pack-solution with solution-type: Managed Builds the managed zip from the files in Git, so Git is the source of truth
use-deployment-settings-file: true Applies target-specific environment variable values on import
workflow_run trigger Deploy starts automatically after a successful export (and is skipped after a failed one)
environment: COE GitHub environment, so you can add a required-reviewer approval gate later

Why not trigger Deploy on push? The export workflow pushes with the default GITHUB_TOKEN, and pushes made with that token do not trigger other workflows. workflow_run is the clean way to chain them.


Running it

Export run — all steps green:

Export success

The unpacked solution is now in Git as readable files, so every change to the flow shows up as a diff:

Unpacked source in Git

Deploy started automatically through workflow_run:

Deploy triggered

Deploy success

The managed solution in the target environment:

Managed solution in target

Components in target

Notice two things: the flow is Off, and the owner of every component is the service principal. That leads to the final, one-time step.

Step 9: Post-deployment (one time per target environment)

The managed import created the connection reference, but no connection is bound to it yet. Because of that, the flow arrives Off. Components inside a managed solution can't be edited, so open the environment's Default Solution instead:

  1. Solutions → Default Solution → Connection references, then search for the connection reference's logical name.
  2. Open it, choose your Office 365 Outlook connection (or + New connection) and Save.

Bind connection reference

  1. Back in the managed solution, select the flow and click Turn on.

Flow turned on

This is a one-time step per target environment. After later deployments, check that the connection reference is still bound and the flow is still On. If an upgrade clears the binding, re-bind it here, or move to a connection the service principal is allowed to use and put its ID in the settings file.

Step 10: Test the deployed flow

Open the flow in the target environment and select Run → Continue → Run flow. The email arrives at the address held in the target value of the environment variable, not the Dev value. That proves the deployment settings file did its job.

Run succeeded

Run steps

Here is the full history of the Deploy workflow, including the failed run that taught me Error 3 below:

Deploy runs


Troubleshooting: the three errors I hit

Error 1 — does not have permission to access the path …/environments

Who am I error

Symptom who-am-i logs authenticated successfully, then fails saying the service principal does not have permission to access the environment
Cause The app registration and secret are correct (Entra sign-in worked), but the app is not an Application user in that Dataverse environment
Fix Add it as an Application user with System Administrator in every environment the pipeline touches

Error 2 — PrivilegeDenied when creating the Application user

Privilege denied

Symptom There was a problem adding the app to this environment… PrivilegeDenied
Cause You can only assign a security role you hold yourself. Being a Global or Power Platform administrator does not automatically make you a Dataverse System Administrator in an environment, and the Default environment is the usual place this shows up.
Fix Power Platform admin center → the environment → Membership → add yourself as System Administrator, wait a minute, then create the Application user again

Error 3 — ConnectionAuthorizationFailed (403) during import

Connection authorization failed

Symptom Import fails with An unexpected error occurred. Solutions → History in the target shows error 8004801E: the caller does not have the minimum required permission … on connection … under API 'shared_office365'
Cause I put my user-owned Outlook connection ID in the deployment settings file. The import runs as the service principal, and the service principal isn't allowed to use a connection owned by someone else. Microsoft's guidance is that the connection must be owned by, or shared with, the owner of the connection reference.
Fix Leave ConnectionId empty for per-user connectors like Office 365 Outlook, and bind the connection once in the target after the first deployment (see Step 9). Connections the service principal can own, or shareable connections, can go straight into the file.

Debugging tip: when the pipeline only says unexpected error, open Solutions → History in the target environment, select the failed import and expand More details. The real error is there.


Lessons learned

Lesson
Run who-am-i first It turns a vague import failure into a clear permissions error in seconds
The service principal needs an Application user in each environment Entra sign-in success ≠ Dataverse access
You must hold System Administrator yourself to grant it Use Membership in the admin center
Imported flows are owned by the service principal and arrive Off Plan a post-deploy step for connections and activation
Per-user connectors can't be bound by the service principal Leave ConnectionId blank, bind once in the target
Check Solutions → History for the real error The CLI often reports a generic message
Avoid the Default environment as a Dev source for real projects Use a dedicated Developer or Sandbox environment

References (Microsoft Learn)

Thursday, October 15, 2026

test


===

# Set the hour window start (local IST time). Window = start to start + 1 hour. Example: 2026-09-02 15:00 = Sep 2, 3PM-4PM
SET WindowStartInput TO $'''2026-09-02 15:00'''
Text.ConvertTextToDateTime.ToDateTimeCustomFormat Text: WindowStartInput CustomFormat: $'''yyyy-MM-dd HH:mm''' DateTime=> WindowStart
DateTime.Add DateTime: WindowStart TimeToAdd: 1 TimeUnit: DateTime.TimeUnit.Hours ResultedDate=> WindowEnd
Text.ConvertDateTimeToText.FromCustomDateTime DateTime: WindowStart CustomFormat: $'''yyyy-MM-ddTHH:00:00zzz''' Result=> StartText
Text.ConvertDateTimeToText.FromCustomDateTime DateTime: WindowEnd CustomFormat: $'''yyyy-MM-ddTHH:00:00zzz''' Result=> EndText
SET FetchXmlFilter TO $'''<attribute name=\"workqueueitemid\" />
<attribute name=\"name\" />
<attribute name=\"input\" />
<attribute name=\"statecode\" />
<attribute name=\"statuscode\" />
<attribute name=\"createdon\" />
<filter type=\"and\">
  <condition attribute=\"createdon\" operator=\"ge\" value=\"%StartText%\" />
  <condition attribute=\"createdon\" operator=\"lt\" value=\"%EndText%\" />
</filter>
<order attribute=\"createdon\" descending=\"false\" />'''
@@'InputSummaryValue:WORKQUEUE': 'Invoice Processing Queue'
WorkQueues.GetWorkQueueItems WorkQueue: $'''2ff3dc7e-9e73-f111-ab0d-6045bdce39cc''' FilterRows: FetchXmlFilter RowsToReturn: 5000 ThrowIfWorkQueueNotFound: True WorkQueueItems=> WorkQueueItems
SET ResultSummary TO $'''Window: %StartText% to %EndText%
Items found: %WorkQueueItems.Count%
'''
LOOP FOREACH CurrentItem IN WorkQueueItems
    SET ResultSummary TO $'''%ResultSummary%
- %CurrentItem.Name% | %CurrentItem.Value%'''
END
Display.ShowMessageDialog.ShowMessage Title: $'''Work queue items (1-hour window)''' Message: ResultSummary Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ButtonPressed


===

# Create_Folder_Tree: create every folder level in each path (e.g. folder1 > folder1.1 > folder1.1.1) if missing; report created / already exists
SET SiteHost TO $'''sreekanth10.sharepoint.com'''
SET SitePath TO $'''/sites/pub'''
# Folder paths to create (add more lines with Add item to list)
Variables.CreateNewList List=> FolderPaths
Variables.AddItemToList Item: $'''folder1/folder1.1/folder1.1.1''' List: FolderPaths
Variables.AddItemToList Item: $'''folder1/folder1.1/folder1.1.2''' List: FolderPaths
# 1) Access token + site ID
CALL Get_Access_Token
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteHost%:%SitePath%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> SiteHeaders Response=> SiteResponse StatusCode=> SiteStatus
Variables.ConvertJsonToCustomObject Json: SiteResponse CustomObject=> SiteObj
SET SiteId TO SiteObj['id']
SET ProcessedFolders TO $'''|'''
SET TreeReport TO $''''''
# 2) Walk each path level by level
LOOP FOREACH CurrentPath IN FolderPaths
    Text.SplitText.SplitWithDelimiter Text: CurrentPath CustomDelimiter: $'''/''' IsRegEx: False Result=> PathParts
    SET ParentPath TO $''''''
    LOOP FOREACH PathPart IN PathParts
        IF IsEmpty(ParentPath) THEN
            SET CurrentFolderPath TO PathPart
        ELSE
            SET CurrentFolderPath TO $'''%ParentPath%/%PathPart%'''
        END
        # Skip levels already handled by an earlier path (e.g. folder1, folder1/folder1.1)
        IF NotContains(ProcessedFolders, $'''|%CurrentFolderPath%|''', False) THEN
            Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%CurrentFolderPath%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> TreeCheckHeaders Response=> TreeCheckResponse StatusCode=> TreeCheckStatus
            IF TreeCheckStatus = 200 THEN
                SET TreeReport TO $'''%TreeReport%Already exists: %CurrentFolderPath%
'''
            ELSE IF TreeCheckStatus = 404 THEN
                IF IsEmpty(ParentPath) THEN
                    SET CreateFolderUrl TO $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root/children'''
                ELSE
                    SET CreateFolderUrl TO $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%ParentPath%:/children'''
                END
                Web.InvokeWebService.InvokeWebService Url: CreateFolderUrl Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: $'''{\"name\":\"%PathPart%\",\"folder\":{},\"@microsoft.graph.conflictBehavior\":\"fail\"}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> TreeCreateHeaders Response=> TreeCreateResponse StatusCode=> TreeCreateStatus
                SET TreeReport TO $'''%TreeReport%Created: %CurrentFolderPath%
'''
            ELSE
                SET TreeReport TO $'''%TreeReport%Check failed (status %TreeCheckStatus%): %CurrentFolderPath%
'''
            END
            SET ProcessedFolders TO $'''%ProcessedFolders%%CurrentFolderPath%|'''
        END
        SET ParentPath TO CurrentFolderPath
    END
END
# 3) Summary
Display.ShowMessageDialog.ShowMessage Title: $'''Folder tree result''' Message: TreeReport Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> TreeReportButton


===
# Create_Folder1: create 'folder1' in the Documents library if it does not exist; otherwise show its path
SET NewFolderName TO $'''folder1'''
SET SiteHost TO $'''sreekanth10.sharepoint.com'''
SET SitePath TO $'''/sites/pub'''
# 1) Get access token (reusable subflow)
CALL Get_Access_Token
# 2) Resolve site ID
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteHost%:%SitePath%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> SiteHeaders Response=> SiteResponse StatusCode=> SiteStatus
Variables.ConvertJsonToCustomObject Json: SiteResponse CustomObject=> SiteObj
SET SiteId TO SiteObj['id']
# 3) Check if folder exists (200 = exists, 404 = not found)
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%NewFolderName%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> Folder1Headers Response=> Folder1Response StatusCode=> Folder1Status
IF Folder1Status = 200 THEN
    Variables.ConvertJsonToCustomObject Json: Folder1Response CustomObject=> Folder1Obj
    Display.ShowMessageDialog.ShowMessage Title: $'''Folder exists''' Message: $'''Folder already exists: %NewFolderName%  |  Path: %Folder1Obj['webUrl']%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> Folder1ExistsButton
ELSE IF Folder1Status = 404 THEN
    # 4) Create the folder
    Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root/children''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: $'''{\"name\":\"%NewFolderName%\",\"folder\":{},\"@microsoft.graph.conflictBehavior\":\"fail\"}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> NewFolderHeaders Response=> NewFolderResponse StatusCode=> NewFolderStatus
    Variables.ConvertJsonToCustomObject Json: NewFolderResponse CustomObject=> NewFolderObj
    Display.ShowMessageDialog.ShowMessage Title: $'''Folder created''' Message: $'''Folder created: %NewFolderName%  |  Path: %NewFolderObj['webUrl']%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> Folder1CreatedButton
ELSE
    Display.ShowMessageDialog.ShowMessage Title: $'''Folder check failed''' Message: $'''Could not check folder %NewFolderName%. Status: %Folder1Status%''' Icon: Display.Icon.ErrorIcon Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> Folder1ErrButton
END

===
https://sreekanth10.sharepoint.com/sites/pub/Documents/Forms/AllItems.aspx?id=%2Fsites%2Fpub%2FDocuments%2FPAD%5FUploads&viewid=b171fd9e%2Da8a6%2D4213%2D879c%2D38953b78b0dc&FolderCTID=0x01200088EBD286DC218F48AFB7EF2F7C3AE32F

# Upload_Sample_File: check folder > create if missing > upload sample file
SET TenantId TO $'''5a2c87d9-ad1d-4d88-85ef-f5a3c30ca040'''
SET ClientId TO $'''473fd5ed-08fe-4c90-82a6-d3180546e5fd'''
SET ClientSecret TO $'''StB8Q~lB86O-7SxBKUm6Jjzf0guSFYtYqWrTBcv3'''
SET SiteHost TO $'''sreekanth10.sharepoint.com'''
SET SitePath TO $'''/sites/pub'''
SET TargetFileName TO $'''customlistcss.css'''
SET DownloadFolderPath TO $'''C:\\Users\\usree\\Downloads\\graphdownlaod'''
SET UA TO $'''Mozilla/5.0 (Windows NT 10.0; Win64; x64)'''
Web.InvokeWebService.InvokeWebService Url: $'''https://login.microsoftonline.com/%TenantId%/oauth2/v2.0/token''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/x-www-form-urlencoded''' RequestBody: $'''client_id=%ClientId%&client_secret=%ClientSecret%&scope=https%%3A%%2F%%2Fgraph.microsoft.com%%2F.default&grant_type=client_credentials''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> TokenHeaders Response=> TokenResponse StatusCode=> TokenStatus
Variables.ConvertJsonToCustomObject Json: TokenResponse CustomObject=> TokenObj
SET AccessToken TO TokenObj['access_token']
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteHost%:%SitePath%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> SiteHeaders Response=> SiteResponse StatusCode=> SiteStatus
Variables.ConvertJsonToCustomObject Json: SiteResponse CustomObject=> SiteObj
SET SiteId TO SiteObj['id']
SET UploadFolderName TO $'''PAD_Uploads'''
SET UploadFileName TO $'''sample_upload.txt'''
SET LocalSampleFile TO $'''%DownloadFolderPath%\\%UploadFileName%'''
# 1) Check if folder exists (404 = not found)
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%UploadFolderName%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> FolderHeaders Response=> FolderResponse StatusCode=> FolderStatus
# 2) Create folder if it does not exist
IF FolderStatus = 404 THEN
    Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root/children''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: $'''{\"name\":\"%UploadFolderName%\",\"folder\":{},\"@microsoft.graph.conflictBehavior\":\"fail\"}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> CreateFolderHeaders Response=> CreateFolderResponse StatusCode=> CreateFolderStatus
ELSE IF FolderStatus <> 200 THEN
    Display.ShowMessageDialog.ShowMessage Title: $'''SharePoint upload''' Message: $'''Folder check failed. Status: %FolderStatus%''' Icon: Display.Icon.ErrorIcon Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> UploadErrButton
    EXIT FUNCTION
END
# 3) Create a local sample file and upload it
File.WriteText File: LocalSampleFile TextToWrite: $'''Sample file uploaded by Power Automate Desktop via Microsoft Graph.''' AppendNewLine: True IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
File.ReadTextFromFile.ReadText File: LocalSampleFile Encoding: File.TextFileEncoding.UTF8 Content=> SampleFileContent
# Check if the file already exists in the folder
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%UploadFolderName%/%UploadFileName%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> ExistHeaders Response=> ExistResponse StatusCode=> ExistStatus
SET ConflictBehavior TO $'''fail'''
IF ExistStatus = 200 THEN
    Display.ShowMessageDialog.ShowMessage Title: $'''File already exists''' Message: $'''\'%UploadFileName%\' already exists in %UploadFolderName%.  YES = Replace  |  NO = Keep both (upload a renamed copy)  |  CANCEL = Do nothing''' Icon: Display.Icon.Question Buttons: Display.Buttons.YesNoCancel DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ExistChoice
    IF ExistChoice = $'''Yes''' THEN
        # Replace: library requires check-out, so check the existing file out first
        Variables.ConvertJsonToCustomObject Json: ExistResponse CustomObject=> ExistObj
        Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/items/%ExistObj['id']%/checkout''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: $'''{}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> CheckoutHeaders Response=> CheckoutResponse StatusCode=> CheckoutStatus
        SET ConflictBehavior TO $'''replace'''
    ELSE IF ExistChoice = $'''No''' THEN
        SET ConflictBehavior TO $'''rename'''
    ELSE
        Display.ShowMessageDialog.ShowMessage Title: $'''SharePoint upload''' Message: $'''File already exists: %UploadFolderName%/%UploadFileName%. Upload skipped.''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> SkipButton
        EXIT FUNCTION
    END
END
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/root:/%UploadFolderName%/%UploadFileName%:/content?@microsoft.graph.conflictBehavior=%ConflictBehavior%''' Method: Web.Method.Put Accept: $'''application/json''' ContentType: $'''text/plain''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: SampleFileContent ConnectionTimeout: 60 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> UploadHeaders Response=> UploadResponse StatusCode=> UploadStatus
Variables.ConvertJsonToCustomObject Json: UploadResponse CustomObject=> UploadObj
# Library requires check-out: check the uploaded file in so it becomes visible
Web.InvokeWebService.InvokeWebService Url: $'''https://graph.microsoft.com/v1.0/sites/%SiteId%/drive/items/%UploadObj['id']%/checkin''' Method: Web.Method.Post Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%''' RequestBody: $'''{\"comment\":\"Uploaded by Power Automate Desktop\"}''' ConnectionTimeout: 30 FollowRedirection: True ClearCookies: False FailOnErrorStatus: True EncodeRequestBody: False UserAgent: UA Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> CheckinHeaders Response=> CheckinResponse StatusCode=> CheckinStatus
Display.ShowMessageDialog.ShowMessage Title: $'''SharePoint upload''' Message: $'''Uploaded: %UploadObj['webUrl']%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> UploadOkButton

===
l/+fYK8wSoO3


SET OrgUrl TO $'''https://org362aa0a6.crm8.dynamics.com'''
SET WorkQueueId TO $'''2ff3dc7e-9e73-f111-ab0d-6045bdce39cc'''
SET TenantId TO $'''5a2c87d9-ad1d-4d88-85ef-f5a3c30ca040'''
Scripting.RunPowershellScript.RunScript Script: $'''$ErrorActionPreference = \'Stop\'
$WarningPreference = \'SilentlyContinue\'
$ProgressPreference = \'SilentlyContinue\'
$res = \'%OrgUrl%\'
$tenant = \'%TenantId%\'
try {
    Import-Module Az.Accounts -ErrorAction Stop
    $ctx = Get-AzContext -ListAvailable | Where-Object { $_.Tenant.Id -eq $tenant } | Select-Object -First 1
    if (-not $ctx) {
        # Use browser sign-in (no console prompts) so it works from a desktop flow
        Update-AzConfig -LoginExperienceV2 Off -EnableLoginByWam $false -Scope CurrentUser | Out-Null
        Connect-AzAccount -Tenant $tenant -SkipContextPopulation 6>$null | Out-Null
        $ctx = Get-AzContext -ListAvailable | Where-Object { $_.Tenant.Id -eq $tenant } | Select-Object -First 1
    }
    if (-not $ctx) {
        Write-Output \"ERROR: Sign-in to tenant $tenant did not complete.\"
        return
    }
    $t = Get-AzAccessToken -ResourceUrl $res -TenantId $tenant -DefaultProfile $ctx
    if ($t.Token -is [System.Security.SecureString]) {
        Write-Output ([System.Net.NetworkCredential]::new(\'\', $t.Token).Password)
    } else {
        Write-Output $t.Token
    }
} catch {
    Write-Output (\"ERROR: \" + $_.Exception.Message)
}''' ScriptOutput=> PowershellOutput
Text.Trim Text: PowershellOutput TrimOption: Text.TrimOption.Both TrimmedText=> AccessToken
IF StartsWith(AccessToken, $'''ERROR''', True) THEN
    Display.ShowMessageDialog.ShowMessage Title: $'''ITSM work queue - sign-in failed''' Message: AccessToken Icon: Display.Icon.ErrorIcon Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ButtonPressed
    EXIT Code: 0
END
Web.InvokeWebService.InvokeWebService Url: $'''%OrgUrl%/api/data/v9.2/workqueueitems?$filter=_workqueueid_value eq %WorkQueueId%''' Method: Web.Method.Get Accept: $'''application/json''' ContentType: $'''application/json''' CustomHeaders: $'''Authorization: Bearer %AccessToken%
OData-MaxVersion: 4.0
OData-Version: 4.0
Prefer: odata.maxpagesize=5000''' ConnectionTimeout: 60 FollowRedirection: True ClearCookies: False FailOnErrorStatus: False EncodeRequestBody: False UserAgent: $'''Mozilla/5.0 (Windows NT 10.0; Win64; x64)''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False ResponseHeaders=> WebServiceResponseHeaders Response=> WebServiceResponse StatusCode=> StatusCode
Variables.ConvertJsonToCustomObject Json: WebServiceResponse CustomObject=> JsonAsCustomObject
SET WorkQueueItems TO JsonAsCustomObject['value']
SET ItemCount TO WorkQueueItems.Count
SET ItemDetails TO $''''''
LOOP FOREACH CurrentItem IN WorkQueueItems
    SET ItemDetails TO $'''%ItemDetails%%CurrentItem['name']%  |  State: %CurrentItem['statecode']%  |  %CurrentItem['input']%
'''
END
Display.ShowMessageDialog.ShowMessage Title: $'''Invoice Processing Queue''' Message: $'''HTTP status: %StatusCode%   Items: %ItemCount%
(State: 0=Queued, 1=Processing, 2=Processed, 3=OnHold, 4=Error)

%ItemDetails%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ButtonPressed
Folder.GetSpecialFolder SpecialFolder: Folder.SpecialFolder.DesktopDirectory SpecialFolderPath=> DesktopPath
File.WriteText File: $'''%DesktopPath%\\InvoiceProcessingQueue_Items.json''' TextToWrite: WebServiceResponse AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
Display.ShowMessageDialog.ShowMessage Message: $'''%DesktopPath%\\InvoiceProcessingQueue_Items.json''' Icon: Display.Icon.None Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: False ButtonPressed=> ButtonPressed2

===

@@'InputSummaryValue:WORKQUEUE': 'Invoice Processing Queue'
WorkQueues.GetWorkQueueItems WorkQueue: $'''2ff3dc7e-9e73-f111-ab0d-6045bdce39cc''' FilterRows: $'''<attribute name=\"name\" />
<attribute name=\"input\" />
<attribute name=\"statecode\" />
<attribute name=\"statuscode\" />
<attribute name=\"priority\" />
<attribute name=\"expirydate\" />
<attribute name=\"processingresult\" />
<attribute name=\"createdon\" />
<attribute name=\"workqueueitemid\" />
<order attribute=\"createdon\" descending=\"false\" />''' RowsToReturn: 5000 ThrowIfWorkQueueNotFound: True WorkQueueItems=> WorkQueueItems2
SET QueueItemsJson TO $'''['''
SET JsonSeparator TO $''''''
LOOP FOREACH QueueItem IN WorkQueueItems2
    SET QueueItemsJson TO $'''%QueueItemsJson%%JsonSeparator%{\"id\":\"%QueueItem.Id%\",\"name\":\"%QueueItem.Name%\",\"value\":%QueueItem.Value%}'''
    SET JsonSeparator TO $''','''
END
SET QueueItemsJson TO $'''%QueueItemsJson%]'''
SET QueueItemCount TO WorkQueueItems2.Count
Folder.GetSpecialFolder SpecialFolder: Folder.SpecialFolder.DesktopDirectory SpecialFolderPath=> DesktopFolder
File.WriteText File: $'''%DesktopFolder%\\InvoiceProcessingQueue_BuiltIn.json''' TextToWrite: QueueItemsJson AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
Display.ShowMessageDialog.ShowMessage Title: $'''Invoice Processing Queue (built-in action)''' Message: $'''Items: %QueueItemCount%

%QueueItemsJson%''' Icon: Display.Icon.Information Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: True ButtonPressed=> ButtonPressed


===

Tuesday, October 13, 2026

Auto-Sync a Power BI Report from SharePoint Online to the Power BI Service

Save the .pbix in a SharePoint library once, connect it from a workspace once, and every later save flows into the Power BI service automatically. A hands-on walkthrough with screenshots.

ItemDetail
LevelBeginner – Intermediate
Time needed20–30 minutes
ToolsPower BI Desktop, SharePoint Online, Power BI service (app.powerbi.com)
OutcomeA report in a Power BI workspace that re-syncs from a .pbix in SharePoint about every hour

1. The key idea

Common misconception: Uploading a .pbix to SharePoint does NOT make it appear in Power BI. You must connect the file from a Power BI workspace once (Import → SharePoint). After that, the service checks the file about every hour and syncs changes.
StageWhat happens
1. AuthorBuild the report in Power BI Desktop and save SalesDemo.pbix
2. StoreUpload / save the .pbix into a SharePoint document library
3. ConnectIn a workspace: Import → Report, Paginated Report or Workbook → SharePoint → pick the file
4. SyncPower BI keeps a link to the file’s SharePoint item ID and runs a “OneDrive refresh” roughly every hour
5. UpdateReplace the file in SharePoint → report + semantic model in the service update automatically

2. Prerequisites

RequirementWhy
Power BI Desktop (latest)To build and save the .pbix
Microsoft 365 work/school account with a Power BI licenceSharePoint / OneDrive for work or school options are disabled without a subscription
Edit access to a SharePoint document libraryTo store the .pbix
A Power BI workspace (My workspace or a shared one)Where the report and semantic model are created
Not a Document Set folder, not personal OneDriveBoth are unsupported sources for this import

3. Step 1 – Prepare sample data

Any data works. For this walkthrough, an Excel workbook with a single table named SalesTable (240 rows, Jan–Sep) was used.

ColumnTypeExample
OrderIDWhole number1001
OrderDateDate2026-03-14
RegionTextNorth / South / East / West
ProductTextLaptop, Monitor, Keyboard…
CategoryTextHardware / Accessories / Software
Units, UnitPrice, RevenueNumber5 × 14,000 = 70,000
Tip: Format the range as an Excel Table (Ctrl+T) so Power BI picks up a clean, named table in the Navigator.

4. Step 2 – Build the report in Power BI Desktop

#Action
1Home → Get data → Excel workbook (or the Excel workbook tile on the start screen)
2Enter the file path or click Browse… → select the workbook
3Next → tick SalesTable in the Navigator → Load
4In the Data pane tick Region + Revenue → a clustered column chart is created
5Click an empty part of the canvas, tick Category + Revenue → second chart
6Resize the visuals and save the file as SalesDemo.pbix
Figure 1

Figure 1: Get Data (Power Query) → Excel workbook: enter the File path or use Browse…

Figure 2

Figure 2: Report with two visuals; Data pane fields and X/Y-axis wells highlighted

5. Step 3 – Put the .pbix in SharePoint

Two ways to get the file into the library:

OptionHowBest for
A. Browser uploadSharePoint library → Create or upload → Files upload → pick SalesDemo.pbixOne-off upload of an existing file
B. Save from DesktopPower BI Desktop → File → Save as → SharePoint → choose site & folderOngoing work: every Ctrl+S writes straight to SharePoint
Figure 3

Figure 3: Option A – the .pbix in the SharePoint library folder (Create or upload highlighted)

Figure 4

Figure 4: Option B – File → Save as → SharePoint lists your sites directly in Power BI Desktop

Watch out: libraries that require check-out

If the library has “Require documents to be checked out before they can be edited” set to Yes, a newly uploaded file stays checked out to you as a draft (version 0.1). Power BI and other users only see the last checked-in version.

SymptomFix
File shows a checked-out icon / “Checked Out To” = youSelect the file → ⋯ → More → Check in → choose Major version
Changes saved but Power BI never picks them upCheck the file in after every upload or save
Want to avoid this entirelyUse a library where Require Check Out = No (Library settings → Versioning settings)
Figure 5

Figure 5: Library settings → Versioning settings → Require Check Out

6. Step 4 – Connect the file from the Power BI service

#Action
1Open app.powerbi.com → go to the target workspace
2Import → Report, Paginated Report or Workbook → SharePoint
3In Select a file, pick the site (Quick access or More places…) → Documents → PowerBI folder
4Select SalesDemo.pbix → Upload
5The workspace now shows a Report and a Semantic model, both named SalesDemo
Figure 6

Figure 6: Workspace → Import → Report, Paginated Report or Workbook → SharePoint

Figure 7

Figure 7: Select a file: site → Documents → PowerBI → SalesDemo.pbix → Upload

Figure 8

Figure 8: Result: SalesDemo report + SalesDemo semantic model in the workspace

Note: “From this computer” uploads a copy with no link back. Only the OneDrive and SharePoint options create the synced connection.

7. Step 5 – Turn on automatic sync

Both settings live on the semantic model, not on the report.

#Action
1Hover the SalesDemo row of type Semantic model → ⋯ (More options) → Settings
2Scroll the left menu to OneDrive and SharePoint and expand it
3Sync mode → select Automatic updates
4OneDrive refresh → make sure the toggle is On (it also shows the linked SharePoint location)
5Settings save immediately – there is no Apply button in the side pane
Figure 9

Figure 9: Semantic model → ⋯ → Settings

Figure 10

Figure 10: OneDrive and SharePoint → Sync mode → Automatic updates

Figure 11

Figure 11: OneDrive and SharePoint → OneDrive refresh → On (location redacted)

Sync mode options explained

OptionBehaviourWhen it applies
Restrict updatesOnly the semantic model owner can pull file changes, and only by a manual refreshDefault for every NEW semantic model
Automatic updatesService updates automatically from the file; users with write permission can refreshChoose this for hands-off sync
Default updatesLegacy behaviour kept for older modelsExisting models only; disappears once you change the setting
Admin dependency: If Automatic updates is greyed out, a Fabric admin must enable the tenant setting “Semantic model owners can choose to automatically update from files imported from OneDrive or SharePoint”.

8. Step 6 – Verify

CheckWhere
Report opens and shows the visualsWorkspace → SalesDemo (Report)
Sync historySemantic model → ⋯ → Refresh history → OneDrive tab
Linked file locationSemantic model → Settings → OneDrive refresh
End-to-end testChange a visual in Desktop → save/replace in SharePoint → check in → wait up to ~60 min
Figure 12

Figure 12: The SalesDemo report rendered in the Power BI service

9. Two different “refreshes” – don’t mix them up

OneDrive refresh (file sync)Data refresh (semantic model)
What it updatesReport pages, visuals, measures, model structure from the .pbixData rows from the original data sources
Reads fromThe .pbix file in SharePoint / OneDriveThe sources (Excel, SQL, …) – NOT the .pbix
FrequencyAbout every hour, automaticManual (Refresh now) or a schedule you configure
Writes back to the .pbix?NoNo – refreshed data stays in the service
Needs a gateway?NoYes, if any source is on-premises or a local file path
Tip: If your Excel source sits on a local drive, scheduled data refresh fails without a gateway. Store the source workbook in SharePoint too and connect with Get data → SharePoint folder (or Web with the file URL) so the service can refresh it with cloud credentials.

10. What breaks the link? Rename, move, copy

Power BI tracks the file by its SharePoint/OneDrive item ID, not by its name or path.

Action on the .pbixItem IDSync keeps working?
Rename in SharePoint (⋯ → Rename)SameYes
Move to another folder/library (Move to, drag & drop)SameYes (documented)
Upload a new version with the same name → ReplaceSameYes – recommended way to update
Save from Desktop directly to the SharePoint fileSameYes
Save As a new name in Desktop and uploadNewNo
Copy the file, or copy another file over itNewNo (documented)
Delete and re-upload, even with the same nameNewNo
Remember: Renaming the file does not rename the report or semantic model in the service – rename those separately (⋯ → Rename). If the link breaks, import the file again and re-apply Sync mode = Automatic updates on the new semantic model.

11. Troubleshooting

ProblemLikely causeFix
Changes don’t appear after an hourSync mode still Restrict updatesSet Automatic updates, or owner runs Refresh now
Changes don’t appear (check-out library)New version not checked inCheck in as a major version
Changes don’t appearPower BI lost its connection to OneDrive/SharePoint (e.g. credentials changed)Re-import the file; choose “Keep me signed in” when prompted
Automatic updates option disabledTenant setting enforces restricted updatesAsk the Fabric admin to enable the owner-choice setting
Refresh fails after syncData source credentials missing / gateway offlineSemantic model → Settings → Data source credentials / gateway
Link broken after copying the fileCopy creates a new item IDUse Replace or Move instead; re-import if already broken
File not selectable in the pickerStored in a Document Set or personal OneDriveMove it to a normal library folder in OneDrive for work or school / SharePoint
Downloaded live-connection report won’t re-syncConnection string incompatible after downloadOpen in Desktop, reconnect to the semantic model, save again

12. Quick checklist

  • Build and save the .pbix in Power BI Desktop
  • Upload or Save as → SharePoint (normal library, not a Document Set)
  • Check the file in if the library requires check-out
  • Workspace → Import → Report… → SharePoint → select the file
  • Semantic model → Settings → Sync mode = Automatic updates
  • Confirm OneDrive refresh = On
  • Update by replacing the same file – never copy or Save As
  • Verify in Refresh history → OneDrive tab

References

Featured Post

How to Restore Deleted Emails in Exchange Online (Microsoft 365): The Complete Admin Guide

From the user's own "Recover deleted items" button to PowerShell bulk restores, and how to find out who deleted what, and whe...

Popular posts