Skip to content

Instantly share code, notes, and snippets.

@nenkoru
Last active March 31, 2026 01:10
Show Gist options
  • Select an option

  • Save nenkoru/ab4544664755c89eb833456fe9557635 to your computer and use it in GitHub Desktop.

Select an option

Save nenkoru/ab4544664755c89eb833456fe9557635 to your computer and use it in GitHub Desktop.
Connect to PowerBI OLAP from Python cross-platform using dotnet
"""
My journey started after reading this topic. And I started to gather all the breadcrumbs and clues I have found around the Internet.
First of all. There is a difference between Mono and dotnet(aka .net core), as pointed by Darren Gosbell on a microsoft learn page [4].
As everyone stumbling upon this issue I tried using Mono and specific to Windows version of Adomd whic is located in this link [5]. It didn't work as there were API used specific to Windows. It was failing.
Then I learned that there is actualy a 'native' dotnet app that could run those DLLs.
I started by loading that native to Windows AdomdClient DLL version using Mono - Fail.
Then using dotnet - Fail.
And right after that I understood that a .net core DLL has to be used. So I found one on Nuget [3].
And boom, right after that the original problem has gone, but I had a malformed connection string, which I believe was leading to lib to try to run a interactive login(just imo) and then failing into an issue where it said that a function or a dependency is only supported on Windows platform or something like that.
So make sure you add a valid url to the powerbi(in my case), username and password.
I am using Python3.9 with pyadomd==0.1.1 and
```bash
dotnet --version
7.0.102
```
installed using brew
So, you download .net core version of AdomdClient DLL, Microsoft.Identity.Client, Microsoft.IdentityModel.Abstractions as listed below.
Put them in the same folder and add the absolute path to that folder into sys.path so that it could retrieve it.
And there you are. Link to github gist which works for me
[1] https://www.nuget.org/packages/Microsoft.AnalysisServices.AdomdClient.NetCore.retail.amd64
lib/netcoreapp3.0/Microsoft.AnalysisServices.AdomdClient.dll
[2] https://www.nuget.org/packages/Microsoft.Identity.Client/
lib/netcoreapp2.1/Microsoft.Identity.Client.dll
[3] https://www.nuget.org/packages/Microsoft.IdentityModel.Abstractions
lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll
[4] https://learn.microsoft.com/en-us/answers/questions/233745/can-adomd-net-core-connect-to-an-ssas-endpoint-fro?page=2#answers
[5] https://www.nuget.org/packages/Microsoft.AnalysisServices.AdomdClient.retail.amd64/
"""
# adds a path to DLLs into sys.path
from sys import path
path.append("/powerbi-adomd/netcore.adomd/lib/netcoreapp3.0")
# forces pythonnet to use dotnet
# also could be forced by setting env variable(checkout pythonnet repository)
from pythonnet import load
load("coreclr")
import clr
from pyadomd import Pyadomd
conn= (
'Provider=MSOLAP;Data Source=powerbi://api.powerbi.com/v1.0/myorg/<WORKSPACE_IF_NEEDED>;Initial Catalog=<DATASETNAME>;'
'User ID=<USERNAME(EMAIL)>;Password=<PASSWORD>;Persist Security Info=True;'
'Impersonation Level=Impersonate;'
)
# this particular example loads a list of RLS roles needed for the DATASETNAME
# I suggest to tinker with this in a Windows, where it's at least stable
# and making sure that no interactive login is required and making sure that
# the request works fine, then you could try to port it to be usable on linux
query = "Select [Name] from $SYSTEM.TMSCHEMA_ROLES"
#connection = Pyadomd(conn)
with Pyadomd(conn) as conn:
with conn.cursor().execute(query) as cur:
print(cur.fetchall())
@Coruscate5
Copy link
Copy Markdown

Coruscate5 commented Mar 31, 2026

Here's some all-in-one python to get this working again for 2026 - though, I think GraphQL is a better option than XMLA at this point.

import os
import requests
import zipfile
import io

PACKAGE_NAME = "Microsoft.AnalysisServices.AdomdClient"
VERSION = "19.113.2"
DLL_NAME = "Microsoft.AnalysisServices.AdomdClient.dll"

def download_and_extract_latest():
    script_dir = os.getcwd()
    print(f"πŸ“¦ Downloading {PACKAGE_NAME} v{VERSION}...")
    

    url = f"https://www.nuget.org/api/v2/package/{PACKAGE_NAME}/{VERSION}"
    r = requests.get(url, allow_redirects=True)
    
    if r.status_code == 200:
        with zipfile.ZipFile(io.BytesIO(r.content)) as z:

            dll_files = [n for n in z.namelist() if n.endswith(".dll") and "lib/" in n]
            
            for member in dll_files:
                filename = os.path.basename(member)
                target_path = os.path.join(script_dir, filename)
                
                with z.open(member) as source, open(target_path, "wb") as target:
                    target.write(source.read())
                print(f"   βœ… Extracted: {filename}")
                
        print(f"\nπŸš€ Done! {VERSION} files are now in: {script_dir}")
    else:
        print(f"❌ Failed to download. Status code: {r.status_code}")

if __name__ == "__main__":
    download_and_extract_latest()

import os
import requests
import zipfile
import io


LIBRARIES = {
    "MSAL": {
        "package": "Microsoft.Identity.Client",
        "version": "4.65.0", # Exact version from your error
        "dll": "Microsoft.Identity.Client.dll"
    },
    "Identity_Abstractions": {
        "package": "Microsoft.IdentityModel.Abstractions",
        "version": "8.0.0", # Foundation for modern MSAL
        "dll": "Microsoft.IdentityModel.Abstractions.dll"
    }
}

def download_dependencies():
    script_dir = os.getcwd()
    
    for key, info in LIBRARIES.items():
        print(f"πŸ“¦ Downloading {info['package']} v{info['version']}...")
        url = f"https://www.nuget.org/api/v2/package/{info['package']}/{info['version']}"
        r = requests.get(url, allow_redirects=True)
        
        if r.status_code == 200:
            with zipfile.ZipFile(io.BytesIO(r.content)) as z:
                # We prioritize netstandard2.0 or net6.0 for Linux/CoreCLR compatibility
                dll_files = [n for n in z.namelist() if n.endswith(info['dll'])]
                
                # Filter for the best cross-platform version
                target_member = next((f for f in dll_files if "netstandard2.0" in f), 
                                     next((f for f in dll_files if "net6.0" in f), dll_files[0]))
                
                target_path = os.path.join(script_dir, info['dll'])
                with z.open(target_member) as source, open(target_path, "wb") as target:
                    target.write(source.read())
                print(f"   βœ… Extracted: {info['dll']}")
        else:
            print(f"❌ Failed to download {info['package']}. Status: {r.status_code}")

if __name__ == "__main__":
    download_dependencies()
    print("\nπŸš€ All identity foundations are now in your directory.")

from sys import path


from pythonnet import load
load("coreclr")
import clr

from pyadomd import Pyadomd

conn = (
    f"Provider=MSOLAP;"
    f"Data Source=powerbi://api.powerbi.com/v1.0/myorg/{WORKSPACE_ID};"
    f"Initial Catalog={MODEL_NAME};"
    f"User ID=app:{CLIENT_ID}@{TENANT_ID};"
    f"Password={CLIENT_SECRET};"
)

query = """
Select [Name] from $SYSTEM.TMSCHEMA_ROLES
"""


with Pyadomd(conn) as conn:
    with conn.cursor().execute(query) as cur:
        print(cur.fetchall())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment