In changing our reports to point from an older database server to a newer one, we needed a way to tell which reports we’d migrated so far and which ones still had data connections to the old server. Keeping a list of reports would be the easiest way to do this, but why do that when you can write a query to track it for you!
I came across this post that provides the outline of what I was looking for:
https://community.powerbi.com/t5/Report-Server/Get-all-connection-string-for-reports-from-power-bi-report/td-p/920392
It provided the following M-code, which uses the Power BI API to fetch a list of all reports on the server and all the datasource details (it doesn’t fetch the contents of the actual M-code behind the data sources – that would be pretty amazing). Nonetheless, pretty neat on its own (you need to replace “<YOUR URL>” in a couple of spots):
let
Source = OData.Feed("https://<YOUR URL>/pbireports/api/v2.0/PowerBIReports", null, [Implementation="2.0"]),
#"Expanded DataSources" = Table.ExpandTableColumn(Source, "DataSources", {"Id", "ModifiedBy", "ModifiedDate", "ConnectionString", "DataModelDataSource"}, {"DataSources.Id", "DataSources.ModifiedBy", "DataSources.ModifiedDate", "DataSources.ConnectionString", "DataSources.DataModelDataSource"}),
#"Expanded DataSources.DataModelDataSource" = Table.ExpandRecordColumn(#"Expanded DataSources", "DataSources.DataModelDataSource", {"Type", "Kind", "AuthType", "Username", "ModelConnectionName"}, {"DataSources.DataModelDataSource.Type", "DataSources.DataModelDataSource.Kind", "DataSources.DataModelDataSource.AuthType", "DataSources.DataModelDataSource.Username", "DataSources.DataModelDataSource.ModelConnectionName"}),
#"Added Conditional Column" = Table.AddColumn(#"Expanded DataSources.DataModelDataSource", "DS.Connection_String", each if [DataSources.ConnectionString] = null then "No Data Source" else [DataSources.ConnectionString]),
#"Removed Columns" = Table.RemoveColumns(#"Added Conditional Column",{"CacheRefreshPlans", "AccessToken", "Roles", "ContentType", "Content", "ParentFolder", "Properties", "Comments", "AlertSubscriptions", "AllowedActions", "Policies", "DependentItems","Id", "ParentFolderId", "DataSources.Id", "DataSources.DataModelDataSource.ModelConnectionName"}),
#"Renamed Columns" = Table.RenameColumns(#"Removed Columns",{{"DataSources.ConnectionString", "DS.ConnectionString"}, {"DataSources.DataModelDataSource.Type", "DS.Type"}, {"DataSources.DataModelDataSource.Kind", "DS.Kind"}, {"DataSources.DataModelDataSource.AuthType", "DS.AuthType"}, {"DataSources.DataModelDataSource.Username", "DS.Username"}, {"DataSources.ModifiedBy", "DS.ModifiedBy"}, {"DataSources.ModifiedDate", "DS.ModifiedDate"}, {"Name", "Report"}, {"Path", "Report Path"}}),
#"Add Report URL" = Table.AddColumn(#"Renamed Columns", "Report URL", each "https://<YOUR URL>/PBIReports/powerbi" & [Report Path] & "?rs:embed=true")
in
#"Add Report URL"
This was a good framework, but I wanted to add a few things and combine multiple different servers together (My example shows a dev, qa, and prod server being combined, but they could be any number of servers or even both Power BI Server and SSRS if you wanted):
The M-code shown earlier became the highlighted item above – I converted it to a function (“PBI Data Sources” at the top of the “PBI Data Sources Function” group) that accepted both a “Server URL” (replacing the “<YOUR URL>” portion in the initial query) and a “Server Zone” allowing you to name the server in the results. Once you have the function, we move up to the last three objects in “Each Server”, which each call the function with different parameters (for each of my server zones), and then combine them into a single dataset (“Combined Data Sources”). Finally, the results are split into a list of Reports and a list of Data Sources that you can pull into your Power BI model.
Once of the issues I ran into was that I didn’t have access to all the reports on my server – those broke my dataset with the following message:
Interestingly, it wasn’t a “Permission Denied” error, but a 500 server error. I got around it by adding some code to drop any rows where I received an error by using the “try otherwise” error handing in M, something I’d never used before:
= Table.SelectRows(#"Sorted Rows", each ((try [DataSources]{0}[Id] otherwise null) <> null))
This code checks to see if it can view the very first value within the “DataSources” field, and if it can’t, it returns null and then the row gets filtered. This isn’t the most elegant way to do this – it drops any reports I don’t have access too, rather than calling them out, but it got the job done and allowed my dataset to move forward without errors.
Once the data is in the model, you can use some simple visualizations to see what types of connections you have, where you’re connecting, and what user those connections are set up to use (so you can see if every one of your connections is using the correct proxy users).
I hope this helps you get a handle on all the data sources you’re using, and make sure that everything is using the correct settings. I’ve included the empty PBIT file at the end of this post for anybody that wants to connect to their data and see what it looks like (when you open the file, it prompts you for two variables – you can put whatever you want there, but it’s because I’ve left the source query in the file).
If you do end up using this to create something, please let me know what as I’d love to see it in action!
Hello,
This seems to be very interesting and exactly what i am trying to acheive!
Thanks for putting this together, but it seems the pbit file is no more available and i also cannot figure out where you added the bit of code to handle reports you did not have access to!
Can you please help?
Cheers
It looks like the link I had to the PBIT was having an issue with my SSL – I’ve fixed it and it works now, thanks for letting me know! I can see that I’m missing some detail on that additional code and I’ll update the article to be more clear here. Thanks for making me aware of both things and I’m glad to hear that the solution helped you!
I’m excited to try this out! Is there a way to pull in security (RLS) groups as well?
I haven’t tried this with RLS, but it looks like the API supports it and there are some examples here:
https://community.powerbi.com/t5/Report-Server/Can-I-update-Row-level-security-via-Rest-API/m-p/1147874#M14509
It looks like once you have a particular report ID that you’re interested in, you can drill into both DataModelRoles and DataModelRoleAssignments. I haven’t used RLS on Power BI server so I’ll give this a try and past back if there’s more to it, but good luck!
I’d like to use this, but we don’t host our own report server. We’re using Microsoft’s own Power BI Report Service. Our URLs are in the form of:
https://app.powerbi.com/groups/%5Blong code 1]/reports/[long code 2]
Do you know how I would find the correct URL to use with your M-code?
I don’t know how to find the specific URL that you’d use, but it looks like it’s possible – you also need to set the API permissions, as shown here (https://learn.microsoft.com/en-us/rest/api/power-bi/). Good luck!
I throws me an error of authentication ,when i Try Windows authentication it says, you should use anonamous. Can you write orderly how do i get this work,
Thanks
You’re accessing a Power BI Server API, so you’ll need to use Windows authentication and your user will need to have permissions to run the API. I can’t find good documentation, but since you’re loading datasource details for all the reports on the server, you’ll probably need a user that has Admin-level permissions in Power BI Report Server.
Hi Ryan , Is it possible to export data from Power BI Report Server by connecting to API ?
i mean can we create a script that will pull the data from Power BI Report Server ?
Unfortunately, I don’t think there’s a way to get the data our of Power BI Report Server – in PowerBI.com, you can connect to your dataset as if it’s located in SQL Analysis Services, and even though PBIRS runs an SSAS engine in the background for the datasets, I’m not aware of any way to get access to it or pull your data. This would be a great feature and I’d love to be able to run a query against a dataset using the API, but I’m not aware of a way to do that currently.
If you do find one, I’d love to hear about it!