Skip to content

Instantly share code, notes, and snippets.

@petervandivier
Created March 17, 2026 16:27
Show Gist options
  • Select an option

  • Save petervandivier/1cdce8b30a6aa57db69baea1b6036ea4 to your computer and use it in GitHub Desktop.

Select an option

Save petervandivier/1cdce8b30a6aa57db69baea1b6036ea4 to your computer and use it in GitHub Desktop.
<#
.Description
Requires you to get a Bearer auth token from the Web UI and save it to .local/azdo-auth-token.txt
#>
Push-Location $PsScriptRoot/..
. ./Scripts/Functions/Get-AdxDashboardDefinition.ps1
$authorization = Get-Content .local/azdo-auth-token.txt -Raw
$dashboardInventory = Get-Content Dashboards/inventory.csv | ConvertFrom-Csv
$last30DaysQuery = @"
.show commands-and-queries
| extend ClientParts = split(ClientActivityId,';')
| extend IsGuidClient = not(isempty(toguid(ClientParts[0])))
| extend DashboardGuid = case(
tostring(ClientParts[0]) == 'RTD',
tostring(ClientParts[1]),
tostring(ClientParts[0]) == 'Kusto.Web.KWE.Dashboards',
tostring(ClientParts[2]),
''
)
| where isempty(DashboardGuid) == false
| summarize
count(),
min(StartedOn),
max(StartedOn),
dcount(User),
array_sort_asc(make_set(User))
by
DashboardGuid
| where DashboardGuid != 'new'
"@
$last30Dashboards = Invoke-AdxCmd -Query $last30DaysQuery
$last30Dashboards.DashboardGuid | Where-Object {
$_ -NotIn $dashboardInventory.DashboardGuid
} | ForEach-Object {
$dashboardInventory += [PSCustomObject]@{
DashboardGuid = $_
Name = ''
}
}
$GuidsToDelete = @()
$dashboardInventory | ForEach-Object {
$DashboardGuid = $_.DashboardGuid
$inventoryTitle = $_.Name
$reportDefinition = Get-AdxDashboardDefinition `
-authorization $authorization `
-DashboardGuid $DashboardGuid
if([string]::IsNullOrEmpty($inventoryTitle)){
$inventoryTitle = $reportDefinition.title
Write-Verbose "Setting title from unknown (GUID) to '$inventoryTitle'."
# TODO: handle for same-name/different-GUID reports
}
if(Test-Path "Dashboards/dashboard-${inventoryTitle}.json"){
Remove-Item "Dashboards/dashboard-${inventoryTitle}.json"
} elseif($null -ne $reportDefinition) {
Write-Host "Checking in new dashboard '$($reportDefinition.title)'"
}
if($null -eq $reportDefinition){
# TODO: add known-unreachable handling to quiesce warnings
$GuidsToDelete += $DashboardGuid
Write-Warning "Dashboard with guid '$DashboardGuid' will be removed from the inventory"
} else {
if($inventoryTitle -ne $reportDefinition.title){
Write-Warning "Dashboard '$DashboardGuid' renamed from $inventoryTitle to $($reportDefinition.title)"
$_.Name = $reportDefinition.title
}
# TODO, check if this breaks shit. `IdentityService Metrics` s/b good test candidate
$json = $reportDefinition | ConvertTo-Json -Depth 10
$json | Set-Content "Dashboards/dashboard-$($reportDefinition.title).json" -Force
}
$_.Name = $reportDefinition.title
}
$dashboardInventory `
| Where-Object DashboardGuid -NotIn $GuidsToDelete `
| Sort-Object Name `
| Export-Csv Dashboards/inventory.csv -UseQuotes AsNeeded
Pop-Location
function Get-AdxDashboardDefinition {
param (
[Parameter(Mandatory)]
[string]
$authorization,
[Parameter(Mandatory)]
[guid]
$DashboardGuid
)
# TODO: minify
$headers = @{
authority = "dashboards.kusto.windows.net"
method = "GET"
path = "/dashboards/$DashboardGuid"
scheme = "https"
accept = "*/*"
"accept-encoding" = "gzip, deflate, br"
"accept-language" = "en-US,en;q=0.9"
authorization = $authorization
contextid = "1eaf415f-39ff-4fe5-a74f-deef607d2777"
origin = "https://dataexplorer.azure.com"
referer = "https://dataexplorer.azure.com/"
"sec-ch-ua" = '"Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"'
"sec-ch-ua-mobile" = "?0"
"sec-ch-ua-platform" = '"Windows"'
"sec-fetch-dest" = "empty"
"sec-fetch-mode" = "cors"
"sec-fetch-site" = "cross-site"
sessionid = "fa824bfd-dbe6-4a62-995d-b97a1d915d83"
}
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
$iwrSplat = @{
UseBasicParsing = $true
Uri = "https://dashboards.kusto.windows.net/dashboards/$DashboardGuid"
WebSession = $session
Headers = $headers
}
try{
$response = Invoke-WebRequest @iwrSplat
} catch [Microsoft.PowerShell.Commands.HttpResponseException] {
$HttpError = ((Get-Error).ErrorDetails | ConvertFrom-Json).error
}
if($HttpError.message -eq "Dashboard $DashboardGuid not found."){
Write-Warning $HttpError.message
return
}
$reportJson = $response.Content
if($null -eq $reportJson){
Write-Warning "Report definition could not be obtained for guid '$DashboardGuid'"
return
}
$reportDefinition = $reportJson | ConvertFrom-Json -AsHashtable
$schemaVersion = $reportDefinition.schema_version
if($null -eq $schemaVersion){
$schemaVersion = $reportDefinition.'$schema'
}
if($null -eq $schemaVersion){
Write-Error "Schema version could not be obtained for report '$($reportDefinition.title)' with guid '$DashboardGuid'"
}
$props = @(
@{Name='$schema';Expression={"https://dataexplorer.azure.com/static/d/schema/$schemaVersion/dashboard.json"}}
'id'
'eTag'
'title'
'tiles'
'dataSources'
@{Name='schema_version';Expression={$schemaVersion}}
'autoRefresh'
'sharedQueries'
'pinnedParameters'
'parameters'
'pages'
)
# TODO: remove props not allowed by schema. `jq 'del(..|nulls)'` kills required elements
$reportDefinition | Select-Object $props
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment