1+ # =================================================================================================================================================
2+ # Best Practice Analyzer for Workspace with XMLA Endpoint
3+ # Romain Casteres - Microsoft Customer Engineer Data & AI - http://pulsweb.fr/
4+ # Inspirations & Thanks :
5+ # - Tabular Editor : https://docs.tabulareditor.com/Best-Practice-Analyzer.html
6+ # - Michael Kovalsky : https://powerbi.microsoft.com/en-us/blog/best-practice-rules-to-improve-your-models-performance/
7+ # - Dave Ruijter : https://www.moderndata.ai/2020/09/check-the-quality-of-all-power-bi-data-models-at-once-with-best-practice-analyzer-automation-bpaa/
8+ # =================================================================================================================================================
9+
10+ # Parameters
11+ $ConnectionMode = 2 # 1 for SSPI | 2 for Login and Password | 3 for Service Principal
12+ $PowerBIServicePrincipalTenantId = " ###"
13+ $PowerBIServicePrincipalClientId = " ###"
14+ $PowerBIServicePrincipalSecret = " ###"
15+ $PowerBIUserId = " ###"
16+ $PowerBIPassword = " ###"
17+ $OutputDirectory = " C:\temp\"
18+ $TabularEditorPortableExePath = " C:\Program Files (x86)\Tabular Editor\TabularEditor.exe"
19+ $PremiumWokspaceNameToBeAnalyzed = " ###" # PREMIUM REQUIRED
20+ $TabularEditorBPARulesPath = " https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json"
21+ $biglistofdatasets = [System.Collections.ArrayList ]::new()
22+ $CurrentDateTime = (Get-Date ).tostring(" yyyyMMdd-HHmmss" )
23+ $OutputDir = Join-Path - Path $OutputDirectory - ChildPath " \$CurrentDateTime "
24+ new-item $OutputDir - itemtype directory - Force | Out-Null
25+
26+ # Download BPA Rules
27+ [Net.ServicePointManager ]::SecurityProtocol = [Net.ServicePointManager ]::SecurityProtocol -bor [Net.SecurityProtocolType ]::Tls12
28+ wget $TabularEditorBPARulesPath - outfile $OutputDir " \Rules.json"
29+
30+ # Runctions to call the .exe - Author: https://mnaoumov.wordpress.com/
31+ function Test-CalledFromPrompt {
32+ (Get-PSCallStack )[-2 ].Command -eq " prompt"
33+ }
34+ function Invoke-NativeApplication {
35+ param (
36+ [ScriptBlock ] $ScriptBlock ,
37+ [int []] $AllowedExitCodes = @ (0 ),
38+ [switch ] $IgnoreExitCode
39+ )
40+ $backupErrorActionPreference = $ErrorActionPreference
41+ $ErrorActionPreference = " Continue"
42+ try {
43+ if (Test-CalledFromPrompt ) {
44+ $lines = & $ScriptBlock
45+ } else {
46+ $lines = & $ScriptBlock 2>&1
47+ }
48+ $lines | ForEach-Object - Process {
49+ $isError = $_ -is [System.Management.Automation.ErrorRecord ]
50+ " $_ " | Add-Member - Name IsError - MemberType NoteProperty - Value $isError - PassThru
51+ }
52+ if ((-not $IgnoreExitCode ) -and ($AllowedExitCodes -notcontains $LASTEXITCODE )){
53+ throw " Execution failed with exit code $LASTEXITCODE "
54+ }
55+ }
56+ finally {
57+ $ErrorActionPreference = $backupErrorActionPreference
58+ }
59+ }
60+
61+ # Connection
62+ IF ($WithServicePrincipal -eq " True" ) {
63+ Write-Host " Connecting with Service Principal..."
64+ $secureServicePrincipalSecretBis = $PowerBIServicePrincipalSecret | ConvertTo-SecureString - AsPlainText - Force
65+ $credential = New-Object PSCredential - ArgumentList $PowerBIServicePrincipalClientId , $secureServicePrincipalSecretBis
66+ Connect-PowerBIServiceAccount - ServicePrincipal - Credential $credential - Tenant $PowerBIServicePrincipalTenantId
67+ } ELSE {
68+ Write-Host " Connecting to the service..."
69+ Connect-PowerBIServiceAccount
70+ }
71+
72+ # BPA for every Datasets within the Workspace
73+ $workspaces = Get-PowerBIWorkspace - Name $PremiumWokspaceNameToBeAnalyzed
74+ if ($workspaces ) {
75+ $workspacesOutputPath = Join-Path - Path $OutputDirectory - ChildPath " \$CurrentDateTime \Workspaces.json"
76+ $workspaces | ConvertTo-Json - Compress | Out-File - FilePath $workspacesOutputPath
77+ $workspaces | Where-Object {$_.IsOnDedicatedCapacity -eq $True } | ForEach-Object {
78+ $workspaceName = $_.Name
79+ $worskpaceId = $_.Id
80+ Write-Host " Premium workspace: $workspaceName "
81+ $datasets = Get-PowerBIDataset - WorkspaceId $_.Id | Where-Object {$_.Name -ne " Report Usage Metrics Model" }
82+ $datasets | Add-Member - MemberType NoteProperty - Name " WorkspaceId" - Value $worskpaceId
83+ $biglistofdatasets += $datasets
84+ if ($datasets ) {
85+ $datasets | ForEach-Object {
86+ $datasetName = $_.Name
87+ Write-Host " - Dataset: $datasetName "
88+ $DatasetTRXOutputDir = Join-Path - Path $OutputDirectory - ChildPath " \$CurrentDateTime \"
89+ new-item $DatasetTRXOutputDir - itemtype directory - Force | Out-Null
90+ $DatasetTRXOutputPath = Join-Path - Path $DatasetTRXOutputDir - ChildPath " \$workspaceName - $datasetName .trx"
91+ Write-Host " --- Performing Best Practice Analyzer on dataset: $datasetName ."
92+ Write-Host " --- Output saved: $DatasetTRXOutputPath ."
93+ Switch ($ConnectionMode ){
94+ 1 {
95+ Write-Host " ----- Connecting with SSPI"
96+ Invoke-NativeApplication { cmd / c " "" $TabularEditorPortableExePath "" "" Provider=MSOLAP;Data Source=powerbi://api.powerbi.com/v1.0/myorg/$workspaceName ;Integrated Security=SSPI;"" "" $datasetName "" -A "" $TabularEditorBPARulesPath "" -TRX "" $DatasetTRXOutputPath "" " } @ (0 , 1 ) $True | Out-Null
97+ }
98+ 2 {
99+ Write-Host " ----- Connecting with Login and Password"
100+ Invoke-NativeApplication { cmd / c " "" $TabularEditorPortableExePath "" "" Provider=MSOLAP;Data Source=powerbi://api.powerbi.com/v1.0/myorg/$workspaceName ;User ID=$PowerBIUserId ;Password=$PowerBIPassword ;"" "" $datasetName "" -A "" $TabularEditorBPARulesPath "" -TRX "" $DatasetTRXOutputPath "" " } @ (0 , 1 ) $True | Out-Null
101+ }
102+ 3 {
103+ Write-Host " ----- Connecting with Service Principal"
104+ Invoke-NativeApplication { cmd / c " "" $TabularEditorPortableExePath "" "" Provider=MSOLAP;Data Source=powerbi://api.powerbi.com/v1.0/myorg/$workspaceName ;User ID=app:$PowerBIServicePrincipalClientId @$PowerBIServicePrincipalTenantId ;Password=$ ( $credential.getNetworkCredential ().password) "" "" $datasetName "" -A "" $TabularEditorBPARulesPath "" -TRX "" $DatasetTRXOutputPath "" " } @ (0 , 1 ) $True | Out-Null
105+ }
106+ }
107+ }
108+ }
109+ }
110+ Write-Host " Finished on workspace: $workspaceName ."
111+ }
112+ $datasetsOutputPath = Join-Path - Path $OutputDirectory - ChildPath " \$CurrentDateTime \Datasets.json"
113+ $biglistofdatasets | ConvertTo-Json - Compress | Out-File - FilePath $datasetsOutputPath
0 commit comments