Created
March 17, 2026 16:27
-
-
Save petervandivier/1cdce8b30a6aa57db69baea1b6036ea4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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