Skip to content

Commit 9eb667b

Browse files
committed
Commit
1 parent 93251a2 commit 9eb667b

16 files changed

Lines changed: 500 additions & 1 deletion

‎Analysis Services/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SSAS & Azure Analysis Services Scripts
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Romain Casteres - Analyzing PBIRS Tabular Model Size
2+
# AMO Required (SSAS Feature Pack)
3+
# SSAS Instance for PBIRS : "localhost:5132"
4+
5+
Param($ServerName="localhost:5132")
6+
7+
$loadInfo = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
8+
9+
$server = New-Object Microsoft.AnalysisServices.Server
10+
$server.connect($ServerName)
11+
12+
if ($server.name -eq $null) {
13+
Write-Output (Server ‘{0}’ not found -f $ServerName)
14+
break
15+
}
16+
17+
$sum=0
18+
foreach ($d in $server.Databases ){
19+
Write-Output ( "Database: {0}; Status: {1}; Size: {2}MB" -f $d.Name, $d.State, ($d.EstimatedSize/1024/1024).ToString("#,##0") )
20+
$sum=$sum+$d.EstimatedSize/1024/1024
21+
}
22+
23+
$SizeGB=$Sum/1024
24+
25+
write-host 'Sum of Database = '$sum ' MB'
26+
Write-host 'Total Size of Cube Databases =' $SizeGB ' GB'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Romain Casteres - Analyzing PBIRS Tabular Model Size
2+
# AMO Required (SSAS Feature Pack)
3+
# SSAS Instance for PBIRS : "localhost:5132"
4+
# Export to CSV
5+
6+
Param($ServerName="localhost:5132")
7+
$loadInfo = [Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")
8+
9+
$outputfile = "C:\temp\PBIRS_ModelSize.csv"
10+
if (Test-Path $outputfile) {
11+
Remove-Item $outputfile
12+
}
13+
14+
$server = New-Object Microsoft.AnalysisServices.Server
15+
$server.connect($ServerName)
16+
if ($server.name -eq $null) {
17+
Write-Output (Server ‘{0}’ not found -f $ServerName)
18+
break
19+
}
20+
21+
$sum=0
22+
foreach ($d in $server.Databases ){
23+
New-Object PSObject -Property @{
24+
Name = $d.Name;
25+
ItemID = $d.Name.SubString(0,36); # Mapping to the ItemID column from Catalog table
26+
SizeMB = [math]::Round($d.EstimatedSize/1024/1024,2);
27+
} | export-csv -Path $outputfile -NoTypeInformation -Append
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
spark.conf.set("spark.sql.parquet.vorder.enabled", "true")
2+
import json, requests, pandas as pd
3+
from azure.identity import UsernamePasswordCredential
4+
from datetime import date
5+
6+
tenant = '###'
7+
api = 'https://analysis.windows.net/powerbi/api/.default'
8+
client_id = '###'
9+
username = '###'
10+
password = '###'
11+
path = '/lakehouse/default/Files/TenantSettings/'
12+
activityDate = date.today().strftime("%Y-%m-%d")
13+
14+
username_password_credential_class = UsernamePasswordCredential(client_id=client_id, username=username, password=password, tenant_id=tenant)
15+
access_token_class = username_password_credential_class.get_token(api)
16+
access_token = access_token_class.token
17+
18+
TenantSettingsURL = 'https://api.fabric.microsoft.com/v1/admin/tenantsettings'
19+
header = {'Authorization': f'Bearer {access_token}'}
20+
TenantSettingsJSON = requests.get(TenantSettingsURL , headers=header)
21+
22+
TenantSettingsJSONContent = json.loads(TenantSettingsJSON.content)
23+
TenantSettingsJSONContentExplode = TenantSettingsJSONContent['tenantSettings']
24+
25+
df = pd.DataFrame(TenantSettingsJSONContentExplode)
26+
df['ExportedDate'] = activityDate
27+
df.to_csv(path + activityDate + '_TenantSettings.csv', index=False)
28+
29+
df = spark.read.format("csv").option("header","true").load("Files/TenantSettings/*.csv")
30+
df.write.mode("overwrite").format("delta").saveAsTable("TenantSettings")

‎LICENSE.txt‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Romain Casteres
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
SELECT
2+
SUB.SubscriptionID,
3+
SUB.Report_OID,
4+
CAT.Path,
5+
CAT.Name,
6+
USR.username,
7+
SUB.Description AS SchedulName,
8+
SUB.EventType,
9+
HIST.SubscriptionHistoryID,
10+
HIST.StartTime,
11+
Hist.EndTime,
12+
DATEDIFF(SECOND,HIST.StartTime,Hist.EndTime) AS Dure,
13+
HIST.Status,
14+
HIST.Message,
15+
CASE
16+
WHEN HIST.Status = 0 THEN 'Data refresh finished sucessfully'
17+
WHEN HIST.Status = 1 THEN 'Data refresh is in progress'
18+
ELSE 'Error during refresh'
19+
END AS RESULT
20+
FROM
21+
dbo.Subscriptions SUB
22+
INNER JOIN dbo.Users USR ON USR.UserID = SUB.OwnerID
23+
INNER JOIN SubscriptionHistory HIST ON HIST.SubscriptionID = SUB.SubscriptionID
24+
INNER JOIN dbo.Catalog CAT ON CAT.ItemID = SUB.Report_OID
50.4 KB
Binary file not shown.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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

‎Power BI/Export-PbiLicenses.ps1‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Romain Casteres - https://www.pulsweb.fr
2+
3+
$PBIAdminUPN = ""
4+
$PBIAdminPW = ""
5+
$TenantID = ""
6+
7+
$SecPasswd = ConvertTo-SecureString $PBIAdminPW -AsPlainText -Force
8+
$myCred = New-Object System.Management.Automation.PSCredential($PBIAdminUPN,$SecPasswd)
9+
Connect-AzureAD -TenantId $TenantID -Credential $myCred
10+
11+
$RetrieveDate = Get-Date
12+
$BasePath = "C:\Users\romainca\Desktop\PBI Licences\"
13+
$AzureADUsersCSV = $BasePath + "LicencesUsers.csv"
14+
$OrgO365LicensesCSV = $BasePath + "LicensesOrgO365.csv"
15+
$UserPBIProLicensesCSV = $BasePath + "LicensesUserPBIPro.csv"
16+
17+
$PBIProServicePlanID = "70d33638-9c74-4d01-bfd3-562de28bd4ba"
18+
19+
Write-Host "Retrieve and export users"
20+
$ADUsers = Get-AzureADUser -All $true | Select-Object ObjectId, ObjectType, CompanyName, Department, DisplayName, Mail, UserPrincipalName, UserType, @{Name="Date Retrieved";Expression={$RetrieveDate}}
21+
0..($ADUsers.count-1) | foreach {
22+
$percent = ($_/$ADUsers.count)*100
23+
Write-Progress -Activity 'Retrieve and export users to CSV' -Status "$percent % Complete" -CurrentOperation "Exporting item # $($_+1)" -PercentComplete $percent
24+
$ADUsers[$_]
25+
} | Export-Csv $AzureADUsersCSV -NoTypeInformation -Force
26+
27+
Write-Host "Retrieve and export organizational licenses"
28+
$OrgO365Licenses = Get-AzureADSubscribedSku | Select-Object SkuID, SkuPartNumber,CapabilityStatus, ConsumedUnits -ExpandProperty PrepaidUnits | `
29+
Select-Object SkuID,SkuPartNumber,CapabilityStatus,ConsumedUnits,Enabled,Suspended,Warning, @{Name="Retrieve Date";Expression={$RetrieveDate}}
30+
0..($OrgO365Licenses.count-1) | foreach {
31+
$percent = ($_/$OrgO365Licenses.count)*100
32+
Write-Progress -Activity 'Retrieve and export organizational licenses to CSV' -Status "$percent % Complete" -CurrentOperation "Exporting item # $($_+1)" -PercentComplete $percent
33+
$OrgO365Licenses[$_]
34+
} | Export-Csv $OrgO365LicensesCSV -NoTypeInformation -Force
35+
36+
Write-Host "Retrieve and export users with pro licenses based on Power BI Pro service plan ID"
37+
$ProUsersCounter = 0
38+
$ProUsersCount = $ADUsers.Count
39+
$UserLicenseDetail = ForEach ($ADUser in $ADUsers){
40+
$UserObjectID = $ADUser.ObjectId
41+
$UPN = $ADUser.UserPrincipalName
42+
Get-AzureADUserLicenseDetail -ObjectId $UserObjectID -ErrorAction SilentlyContinue | `
43+
Select-Object ObjectID, @{Name="UserPrincipalName";Expression={$UPN}} -ExpandProperty ServicePlans
44+
Write-Progress -Activity "Retreiving users licences, set `$Licences to `$False and rerun the script" -PercentComplete ($ProUsersCounter * 100.0/$ProUsersCount)
45+
$ProUsersCounter += 1
46+
}
47+
$ProUsers = $UserLicenseDetail | Where-Object {$_.ServicePlanId -eq $PBIProServicePlanID}
48+
$ProUsers | Export-Csv $UserPBIProLicensesCSV -NoTypeInformation -Force
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Romain Casteres - https://www.pulsweb.fr
2+
3+
# The output folder to put all the extracts
4+
$folder = "out"
5+
$folderFullPath = "$PSScriptRoot\$folder"
6+
7+
If(!(test-path $folderFullPath)){
8+
New-Item -ItemType Directory -Force -Path $folderFullPath
9+
}
10+
11+
# Installing PbiAdminModules if not present
12+
if (Get-Module -ListAvailable -Name "MicrosoftPowerBIMgmt") {
13+
Write-Host "MicrosoftPowerBIMgmt Module exists"
14+
} else {
15+
Write-Host "MicrosoftPowerBIMgmt Module does not exist - Installing it..."
16+
Install-PbiAdminModules
17+
}
18+
19+
# Prompt the user for credentials
20+
$credential = (Get-Credential -Message "Credentials")
21+
22+
# Log in to Power BI
23+
Login-PowerBIServiceAccount -Credential $credential
24+
25+
# Store the Auth token
26+
$auth = (Get-PowerBIAccessToken).Authorization
27+
28+
Write-Host 'Building Rest API header with authorization token'
29+
$authHeader = @{
30+
'Content-Type'='application/json'
31+
'Authorization'='Bearer ' + $auth
32+
}
33+
34+
$TopN = 50
35+
$headers = @{
36+
"Authorization" = $auth;
37+
"X-PowerBI-User-Admin" = $true
38+
}
39+
40+
$FileName = "$folderFullPath\PowerBI-RefreshHistory.csv"
41+
if (Test-Path $FileName) {
42+
Remove-Item $FileName
43+
}
44+
45+
$workspaces = Get-PowerBIWorkspace
46+
47+
foreach($workspaces in $workspaces){
48+
49+
Write-Host "> Workspace: $($workspaces.Name)"
50+
51+
$datasets = Get-PowerBIDataset -WorkspaceId $workspaces.Id
52+
53+
$refreshes = @()
54+
55+
foreach($dataset in $datasets){
56+
57+
if($dataset.IsRefreshable -eq "True") {
58+
59+
Write-Host ">> Dataset: $($dataset.name)"
60+
61+
$uri = "https://api.powerbi.com/v1.0/myorg/groups/$($workspaces.Id)/datasets/$($dataset.id)/refreshes/?`$top=$($TopN)"
62+
63+
$refresh = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET
64+
$refresh.value | Add-Member -NotePropertyName "DatasetID" -NotePropertyValue $dataset.id
65+
$refresh.value | Add-Member -NotePropertyName "DatasetName" -NotePropertyValue $dataset.name
66+
$refresh.value | Add-Member -NotePropertyName "WorkspaceID" -NotePropertyValue $workspaces.Id
67+
$refreshes += $refresh
68+
}
69+
}
70+
71+
$refreshes.value | ForEach-Object {
72+
New-Object PSObject -Property @{
73+
id = $_.id;
74+
refreshType = $_.refreshType;
75+
startTime = $_.startTime;
76+
endTime = $_.endTime;
77+
serviceExceptionJson = $_.serviceExceptionJson;
78+
status = $_.status;
79+
DatasetID = $_.DatasetID;
80+
DatasetName = $_.DatasetName;
81+
WorkspaceID = $_.WorkspaceID;
82+
}
83+
} | Export-Csv -Path $FileName -NoTypeInformation -Append
84+
}

0 commit comments

Comments
 (0)