mirror of
https://github.com/MinaSaad1/pbi-cli
synced 2026-04-21 21:47:34 +00:00
197 lines
5.7 KiB
Python
197 lines
5.7 KiB
Python
"""Trigger Power BI Desktop to reload the current report.
|
|
|
|
Implements a fallback chain:
|
|
1. pywin32 (if installed): find window, send keyboard shortcut
|
|
2. PowerShell: use Add-Type + SendKeys via subprocess
|
|
3. Manual: print instructions for the user
|
|
|
|
Power BI Desktop's Developer Mode auto-detects file changes in TMDL but
|
|
not in PBIR. This module bridges the gap by programmatically triggering
|
|
a reload from the CLI.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from typing import Any
|
|
|
|
|
|
def reload_desktop() -> dict[str, Any]:
|
|
"""Attempt to reload the current report in Power BI Desktop.
|
|
|
|
Tries methods in order of reliability. Returns a dict with
|
|
``status``, ``method``, and ``message``.
|
|
"""
|
|
# Method 1: pywin32
|
|
result = _try_pywin32()
|
|
if result is not None:
|
|
return result
|
|
|
|
# Method 2: PowerShell
|
|
result = _try_powershell()
|
|
if result is not None:
|
|
return result
|
|
|
|
# Method 3: manual instructions
|
|
return {
|
|
"status": "manual",
|
|
"method": "instructions",
|
|
"message": (
|
|
"Could not auto-reload Power BI Desktop. "
|
|
"Please press Ctrl+Shift+F5 in Power BI Desktop to refresh, "
|
|
"or close and reopen the report file."
|
|
),
|
|
}
|
|
|
|
|
|
def _try_pywin32() -> dict[str, Any] | None:
|
|
"""Try to use pywin32 to send a reload shortcut to PBI Desktop."""
|
|
try:
|
|
import win32api
|
|
import win32con
|
|
import win32gui
|
|
except ImportError:
|
|
return None
|
|
|
|
hwnd = _find_pbi_window_pywin32()
|
|
if hwnd == 0:
|
|
return None # window not found; let fallback chain continue
|
|
|
|
try:
|
|
# Bring window to foreground
|
|
win32gui.SetForegroundWindow(hwnd)
|
|
|
|
# Send Ctrl+Shift+F5 (common refresh shortcut)
|
|
VK_CONTROL = 0x11
|
|
VK_SHIFT = 0x10
|
|
VK_F5 = 0x74
|
|
|
|
win32api.keybd_event(VK_CONTROL, 0, 0, 0)
|
|
win32api.keybd_event(VK_SHIFT, 0, 0, 0)
|
|
win32api.keybd_event(VK_F5, 0, 0, 0)
|
|
win32api.keybd_event(VK_F5, 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
win32api.keybd_event(VK_SHIFT, 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
win32api.keybd_event(VK_CONTROL, 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
|
|
return {
|
|
"status": "success",
|
|
"method": "pywin32",
|
|
"message": "Sent Ctrl+Shift+F5 to Power BI Desktop.",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"method": "pywin32",
|
|
"message": f"Failed to send keystrokes: {e}",
|
|
}
|
|
|
|
|
|
def _find_pbi_window_pywin32() -> int:
|
|
"""Find Power BI Desktop's main window handle via pywin32."""
|
|
import win32api
|
|
import win32con
|
|
import win32gui
|
|
import win32process
|
|
|
|
result = 0
|
|
|
|
def callback(hwnd: int, _: Any) -> bool:
|
|
nonlocal result
|
|
if not win32gui.IsWindowVisible(hwnd):
|
|
return True
|
|
title = win32gui.GetWindowText(hwnd)
|
|
if "Power BI Desktop" in title:
|
|
result = hwnd
|
|
return False
|
|
# Newer PBI Desktop versions title the window with just the report
|
|
# name (e.g. "Sales_Demo") -- fall back to matching by process name.
|
|
try:
|
|
_, pid = win32process.GetWindowThreadProcessId(hwnd)
|
|
h_proc = win32api.OpenProcess(win32con.PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
|
|
exe_path = win32process.GetModuleFileNameEx(h_proc, 0)
|
|
win32api.CloseHandle(h_proc)
|
|
if exe_path.lower().endswith("pbidesktop.exe"):
|
|
result = hwnd
|
|
return False
|
|
except Exception:
|
|
pass
|
|
return True
|
|
|
|
try:
|
|
win32gui.EnumWindows(callback, None)
|
|
except Exception:
|
|
pass
|
|
|
|
return result
|
|
|
|
|
|
def _try_powershell() -> dict[str, Any] | None:
|
|
"""Try to use PowerShell to activate PBI Desktop and send keystrokes."""
|
|
if sys.platform != "win32":
|
|
return None
|
|
|
|
ps_script = """
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName Microsoft.VisualBasic
|
|
|
|
$pbi = Get-Process -Name "PBIDesktop" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if (-not $pbi) {
|
|
$pbi = Get-Process -Name "PBIDesktopStore" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
}
|
|
|
|
if (-not $pbi) {
|
|
Write-Output "NOT_FOUND"
|
|
exit 0
|
|
}
|
|
|
|
# Activate the window
|
|
[Microsoft.VisualBasic.Interaction]::AppActivate($pbi.Id)
|
|
Start-Sleep -Milliseconds 500
|
|
|
|
# Send Ctrl+Shift+F5
|
|
[System.Windows.Forms.SendKeys]::SendWait("^+{F5}")
|
|
Write-Output "OK"
|
|
"""
|
|
|
|
try:
|
|
proc = subprocess.run(
|
|
["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
output = proc.stdout.strip()
|
|
|
|
if output == "NOT_FOUND":
|
|
return {
|
|
"status": "error",
|
|
"method": "powershell",
|
|
"message": "Power BI Desktop process not found. Is it running?",
|
|
}
|
|
if output == "OK":
|
|
return {
|
|
"status": "success",
|
|
"method": "powershell",
|
|
"message": "Sent Ctrl+Shift+F5 to Power BI Desktop via PowerShell.",
|
|
}
|
|
|
|
return {
|
|
"status": "error",
|
|
"method": "powershell",
|
|
"message": f"Unexpected output: {output}",
|
|
}
|
|
except FileNotFoundError:
|
|
return None # PowerShell not available
|
|
except subprocess.TimeoutExpired:
|
|
return {
|
|
"status": "error",
|
|
"method": "powershell",
|
|
"message": "PowerShell command timed out.",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"method": "powershell",
|
|
"message": f"PowerShell error: {e}",
|
|
}
|