PBIR & agents

Give your AI agent eyes: the Power BI Desktop Bridge from a Mac

An AI agent on a Mac drives Power BI Desktop in a Windows VM with the Desktop Bridge: write, validate, reload, capture every page, read the PNGs, fix.

9 min read

A terminal running the validate, reload and screenshot-all commands, next to captures of round 1, round 20 and the final render

An AI agent can write a Power BI report: with PBIR, pages and visuals are folders of JSON files. What it cannot do on its own is see what Power BI Desktop draws from those files. And Desktop only runs on Windows, while plenty of us work on a Mac.

Colis Pro, a free Power BI report built with an AI agent, took 77 rounds. In each one, the agent wrote the files on a Mac, validated them, reloaded Power BI Desktop in a Windows virtual machine, captured every page, read the PNGs and fixed what it saw. Nobody had to take a screenshot for it.

This article is that setup: the loop, the five commands, a small wrapper and the pitfalls we hit.

Round 1 of the Colis Pro Overview as the agent captured it: clipped values, an empty ring, and Desktop's Filters pane on the right
Round 1, as the agent received it. The capture is Desktop's editing canvas, Filters pane included.

Why the agent needs eyes

Microsoft's offline validator, powerbi-report-author validate, checks the schema: unknown objects, misspelt properties, invalid enum values. It never renders anything. The first render of Colis Pro passed it with 0 errors and 0 warnings, and still showed clipped values and a ring with nothing in it. A property in the wrong entry is silently ignored; a scatter trend line is valid and never drawn. We listed seven of these in The PBIR validator passes, Desktop disagrees. For an agent the lesson is the same as for a person: validate for structure, then look at the render.

The loop

ON THE MACIN THE WINDOWS VM1 Write the filesPBIR JSON, TMDL when needed2 Validate offlinepowerbi-report-author validate3 Reload in Desktoppowerbi-desktop reload --pid4 Capture every pagescreenshot-all --settle 250005 Copy the PNGs backxcopy to the Mac share6 Read the PNGscompare them with the mockupprlctl execPNG filesFix what the render shows,then start the next round.Colis Pro took 77 rounds.Steps 2 to 5 are commands. Steps 1 and 6 are the agent's work.
The report files live on the Mac. Reload, capture and copy run in the VM.
  1. Write. The agent edits the PBIR JSON on the Mac, and the TMDL model when a measure changes.
  2. Validate. powerbi-report-author validate runs offline on macOS, in seconds.
  3. Reload. Desktop re-reads the report from disk, without restarting.
  4. Capture. screenshot-all writes one PNG per page, inside the VM.
  5. Copy back. xcopy moves the PNGs to the Mac's shared folder.
  6. Read and fix. The agent opens each PNG, compares it with the mockup, and starts the next round.

The PNG shows the canvas as you see it while editing: the Filters pane is there, and dynamic text values carry the gold underline Desktop draws on the canvas. Judge the report, not the chrome around it.

How the pieces connect

The Desktop Bridge is a small local server inside the Power BI Desktop process. Tools reach it through a named pipe, pbi-desktop-bridge-<pid>, one per open Desktop window. Microsoft's documentation is explicit: the pipe is local only, and remote access is not supported. Desktop is the server. The client is the Power BI Desktop Bridge CLI: npm package @microsoft/powerbi-desktop-bridge-cli, command powerbi-desktop.

That CLI is Windows-only. Run powerbi-desktop status on a Mac and it answers UNSUPPORTED_OS: "Power BI Desktop Bridge discovery is only supported on Windows." So the CLI lives in the VM, next to Desktop, and the Mac only needs a way to start it there.

MACAI agentwrites files, reads PNGsReport.pbipPBIR + TMDL filesValidatoroffline, runs on macOSWINDOWS VM, LOGGED-IN SESSIONprlctl exec --current-usercmd /c "…"opens \\Mac\Home\…\Report.pbipBridge CLIclient: powerbi-desktopPower BI Desktopserver: external tool access onreload, screenshotstatus, PNG pagesnamed pipe pbi-desktop-bridge-<pid>The pipe is local only: the CLI must run on the Windows machine that runs Desktop.On macOS it stops with UNSUPPORTED_OS.
One folder, two machines: Desktop opens the same files the agent writes.

Set it up, step by step

  1. A Windows VM with Power BI Desktop. We used Parallels Desktop and the Microsoft Store build of Power BI Desktop.
  2. Turn on the bridge in Desktop. Go to File > Options and settings > Options > Security and tick Enable external tool access to Power BI Desktop through secure local APIs, in the Desktop Bridge section. Microsoft's documentation says it is on by default. Earlier builds listed it under Preview features.
  3. Install Node and the CLI inside the VM. The documented route is npm install -g @microsoft/powerbi-desktop-bridge-cli. We used a portable Node 22 and installed the CLI in a folder of the Windows user profile, outside any project.
  4. Point the CLI at the Store build. The CLI does not find a Store install on its own and stops with DESKTOP_EXE_NOT_FOUND. Set PBI_DESKTOP_PATH to its pbidesktop.exe, in C:\Program Files\WindowsApps\Microsoft.MicrosoftPowerBIDesktop_<version>_x64__8wekyb3d8bbwe\bin\. The version in that folder name changes with each Store update. In PowerShell, (Get-AppxPackage Microsoft.MicrosoftPowerBIDesktop*).InstallLocation prints the current one.
  5. Drive the VM from the Mac. prlctl exec "<VM>" --current-user cmd /c "..." runs a command in the logged-in Windows session. No SSH server needed. Go through cmd, not PowerShell: there, an npm notice on stderr becomes an error.
  6. Open the project from the Mac share. The VM sees your Mac's home folder as \\Mac\Home\..., so Desktop opens the very files the agent writes.
Power BI Desktop Options, Security section: under Desktop Bridge, Enable external tool access to Power BI Desktop through secure local APIs is ticked
File > Options and settings > Options > Security: the Desktop Bridge setting. Desktop is the server; the CLI connects to it.

The five commands

Inside the VM, or through prlctl exec from the Mac:

CMD
powerbi-desktop open "\\Mac\Home\<project>\Report.pbip"
powerbi-desktop status
powerbi-desktop reload --pid <pid>
powerbi-desktop screenshot <page-id> --pid <pid> --output "%LOCALAPPDATA%\shots\page.png" --scale 1
powerbi-desktop screenshot-all --pid <pid> --output-dir "%LOCALAPPDATA%\shots" --settle 25000 --scale 1
  • status lists each Desktop instance with its pid, open file, pages and unsaved-change state. The other commands target one instance with --pid.
  • reload re-reads the report currently open in that instance.
  • screenshot captures one page by its PBIR page id. screenshot-all captures every page listed in pages.json. The CLI's default scale is 2; we used 1.
  • --settle waits before the first capture, in milliseconds. 20 to 25 seconds let every visual render.

Run them one at a time per instance: overlapping operations on the same Desktop may be cancelled or time out.

A small wrapper on the Mac

Two short scripts turn each step into one call for the agent. The first runs the CLI inside the VM:

BASH
#!/bin/bash
# pbd.sh: run the bridge CLI in the VM's logged-in Windows session.
VM="<VM>"
DESKTOP='C:\Program Files\WindowsApps\<Power BI Desktop folder>\bin\pbidesktop.exe'
NODE='%LOCALAPPDATA%\bridge\node\node.exe'
CLI='%LOCALAPPDATA%\bridge\cli\node_modules\@microsoft\powerbi-desktop-bridge-cli\dist\cli.js'
prlctl exec "$VM" --current-user cmd /c "set PBI_DESKTOP_PATH=$DESKTOP&& $NODE $CLI $*"

The agent then calls bash pbd.sh status or bash pbd.sh reload --pid 1234 like a local command. Keep && glued to the path: with a space before it, cmd keeps that space in the variable.

The second captures every page and brings the PNGs back to the Mac:

BASH
#!/bin/bash
# capture.sh <pid> <folder under your Mac home> [settle_ms]
VM="<VM>"; PID=$1; DEST=$2; SETTLE=${3:-25000}
LOCAL='%LOCALAPPDATA%\bridge\shots'
WIN_DEST="\\\\Mac\\Home\\$(echo "$DEST" | sed 's#/#\\#g')"
prlctl exec "$VM" --current-user cmd /c "rmdir /S /Q $LOCAL" >/dev/null 2>&1 || true
bash "$(dirname "$0")/pbd.sh" screenshot-all --pid "$PID" --output-dir "$LOCAL" --settle "$SETTLE" --scale 1
mkdir -p ~/"$DEST"
prlctl exec "$VM" --current-user cmd /c "xcopy /Y /I $LOCAL $WIN_DEST" >/dev/null
ls ~/"$DEST"

Why not point --output-dir straight at the Mac share? Because that write failed at times.

STRAIGHT TO THE SHARECAPTURE, THEN COPYscreenshot-all--output-dir \\Mac\Home\…write ECANCELEDat timesMac sharethe write failsscreenshot-all--output-dir %LOCALAPPDATA%\shotsVM local folder%LOCALAPPDATA%\shotsxcopy /Y /IMac share\\Mac\Home\shotsreadAgent on the Macone PNG per pageThe wrapper takes the right-hand path: clear the local folder, capture, copy, list.
The wrapper never writes captures across the share. It writes locally, then copies.

Pitfalls

SymptomCauseFix
screenshot-all fails with "write ECANCELED"Writing to the Mac share fails at timesCapture into a VM folder, then xcopy
Same error on status or reloadThe CLI's own output through prlctl exec fails at timesRedirect it to a file in the VM and read the file. Rerun: it is transient
DESKTOP_EXE_NOT_FOUNDStore build, folder renamed by an updateUpdate PBI_DESKTOP_PATH
A command fails on an npm noticePowerShell treats stderr output as an errorLaunch through cmd
A TMDL change does not showIn our runs, reload did not re-read the modelClose Desktop without saving, open the .pbip again
"Print metadata is not available"Desktop has only just opened the reportWait a few minutes, capture again
reload hits the wrong reportopen returned the pid of another Desktop instanceIdentify the instance by its window title first
Loading freezesscreenshot-all started during a data refreshNever capture during a refresh; stop the stuck CLI process, not Desktop
A sign-in dialog blocks Desktop after a reloadAn Azure Maps visual on the pageReplace the map

One rule did not fit in the table: one writer at a time. The agent and Desktop can both write the same files. reload discards unsaved edits made in Desktop, and after a reload Desktop shows unsaved changes of its own: never save from Desktop over files the agent just wrote. When Desktop did save our project, it emptied the period of every synced date slicer, and a script had to restore it. If you edit and save in Desktop, the agent re-reads the files before it writes again.

The official shortcut

Microsoft's authoring CLI, @microsoft/powerbi-report-authoring-cli (the package that holds the validator), added a preview command in version 0.3.0-beta.0. It wraps the bridge:

CMD
powerbi-report-author preview "<project>\Report.Report" --host desktop --reload
powerbi-report-author preview "<project>\Report.Report" --host desktop --reload-with-model
powerbi-report-author preview "<project>\Report.Report" --host desktop --screenshot "<dir>" --all-pages

--reload-with-model reloads the report and the semantic model definitions, without refreshing data: the step we did by closing and reopening Desktop. We drove the raw bridge CLI and did not test the wrapper. The command is still in 0.5.0, the current release on npm, and the package as a whole is marked public preview. It does not change the Mac side: on macOS its Desktop host reports the same UNSUPPORTED_OS, so it runs in the VM too.

What it changed for Colis Pro

Before the bridge, every visual check waited for a human screenshot. With it, the agent opened the report, captured all the pages and listed every visual in error, empty or badly drawn, page by page. The first seven rounds fixed those. The next seventy went into the design, and into French and English versions of every page.

The render settled what no schema check can: KPI cards that clipped their own values until their margins went to zero, a ring that lost every colour until % was encoded as %25 in its SVG measures, a map that only asked to sign in.

Round 1 and round 75 of the four KPI cards of the Overview page
The same four cards. Round 1: clipped values, no period set, a ring with nothing drawn. Round 75: September 2026, full numbers, a native donut in its place.
Round 1 and round 75 of the Network map panel
In round 1 the Azure Maps visual only asked to sign in. By round 75 it was a native scatter chart drawn over a map image, with no sign-in to block the loop.

Some states need a click. For the French and English versions of the report, the agent changed the default selection of the language slicer in the files, reloaded and captured, then switched it back.

Try it

Download Colis Pro, free: 5 pages and 347 visuals, built through this loop.

If you would rather your agent did not start from a blank page, ChartCraft's MCP server gives it a designed Power BI project on your own semantic model. The loop above then checks it in Desktop, round after round.

FAQ

Does it work without Parallels, or on Windows directly?

On a Windows PC it is simpler: run the CLI next to Desktop, with no prlctl and no shared folder. From a Mac you need a Windows VM; we only ran it with Parallels, whose prlctl exec --current-user reaches the logged-in session.

Do I need a Power BI Pro license?

Not for this loop: Desktop is a free download, the bridge is local and nothing is published. In our runs, the one thing that wanted a sign-in was the Azure Maps visual, and we replaced it.

Can the agent click buttons and slicers?

Not through the bridge: its commands open, reload and capture. We reached other states by editing the files and reloading, and tested button actions by hand with Ctrl+click in Desktop.

With your consent, we use Google Analytics cookies to measure audience. Cookies needed for sign-in and payment are always on. Learn more