Public/Show-AACDashboard.ps1
|
function Show-AACDashboard { <# .EXTERNALHELP Azure.Admin.Console-help.xml .SYNOPSIS A one-screen dashboard of your Azure estate: who you're signed in as, the subscriptions in scope, resources by region, Resource Health, active Azure Service Health events, Advisor and the latest changes - with one status at the top. .DESCRIPTION Reads it all in one Azure Resource Graph batch (no Az modules), with the Connect-AAC sign-in: ── Azure Admin Console :: Dashboard ───────────────────────────── admin@contoso.com · tenant ... · all subscriptions · 9 Oct 2026 10:42 ✗ Action needed 2 resource(s) unavailable; 1 active Azure service issue(s) [ subscriptions ] [ resources ] [ resource groups ] [ regions ] [ unhealthy ] [ Advisor High ] [ changes in 24h ] ╭─ Session ─────────────────╮ ╭─ Resource Health ──────────╮ │ Account admin@... │ │ ✓ Available 1,204 │ │ Tenant ... │ │ ✗ Unavailable 2 │ │ Token ✓ valid until … │ │ ⚠ Degraded 1 │ ╰───────────────────────────╯ ╰────────────────────────────╯ Subscriptions (state) · Resources by region · Azure Service Health - active events · Unhealthy resources · Azure Advisor recommendations · Recent changes (create, update, delete - what, where, who) The status at the top: ✗ Action needed resources are unavailable, or an Azure service issue is active in these subscriptions ⚠ Needs attention resources are degraded, Advisor has High-impact recommendations, planned maintenance or an advisory is active, or something couldn't be read ✓ Healthy none of these Symbols and colours are the module's (Unicode, or ASCII in a console that isn't UTF-8), so the state never depends on colour alone. What couldn't be read (Resource Health, Service Health, Advisor or the change history need Reader on the subscriptions) is listed at the end, and the rest is still shown. Scope: every subscription the account can see, those in -SubscriptionId, or - with -Select - those you tick in a list at the console. .PARAMETER SubscriptionId Only these subscriptions. .PARAMETER Select Pick the subscriptions from a list at the console (arrow keys, space to tick, Enter to accept). Needs an interactive console; in a script use -SubscriptionId. .PARAMETER Hours How far back the recent changes go, in hours (1 to 336 - Resource Graph keeps 14 days). Defaults to 24. .PARAMETER PassThru Show the dashboard and also return it as an AAC.Dashboard object. .PARAMETER NoDisplay Return the AAC.Dashboard object without showing the dashboard. .PARAMETER NoPaging Show the whole dashboard at once instead of a page at a time. .EXAMPLE Connect-AAC Show-AACDashboard The dashboard for every subscription you can see. .EXAMPLE Show-AACDashboard -Select -Hours 72 Pick the subscriptions from a list, with the changes of the last three days. .EXAMPLE (Show-AACDashboard -NoDisplay).UnhealthyResources | Format-Table The unavailable and degraded resources, as objects. .EXAMPLE if ((Show-AACDashboard -SubscriptionId $prod -NoDisplay).Status -eq 'Failed') { Send-MyAlert } Act on the dashboard's status in a script. .OUTPUTS AAC.Dashboard (with -PassThru or -NoDisplay, or piped onward) #> [CmdletBinding(DefaultParameterSetName = 'All')] [OutputType('AAC.Dashboard')] param( [Parameter(ParameterSetName = 'Subscription')] [ValidatePattern('^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$')] [string[]] $SubscriptionId, [Parameter(ParameterSetName = 'Select')] [switch] $Select, [ValidateRange(1, 336)] [int] $Hours = 24, [switch] $PassThru, [switch] $NoDisplay, [switch] $NoPaging ) # A failure anywhere below ends as a Spectre.Console error panel and this # command's own terminating error, not a line inside the module. A stopped # pipeline (Select-Object -First, Ctrl+C) is no failure: just return - a # rethrow would stop the caller's whole script, not only this command. trap { if ($_.Exception -is [System.Management.Automation.PipelineStoppedException]) { return }; $PSCmdlet.ThrowTerminatingError((Show-AACError -ErrorRecord $_ -Cmdlet $PSCmdlet)) } $pipedOnward = $MyInvocation.PipelinePosition -lt $MyInvocation.PipelineLength $showView = -not $NoDisplay -and -not $pipedOnward $returnObject = $PassThru -or $NoDisplay -or $pipedOnward # Signed in? (Get-AACAccessToken says what to do when not.) $null = Get-AACAccessToken if ($showView) { Write-AACRule -Title 'Azure Admin Console :: Dashboard' -Color 'deepskyblue3_1' } # --- The subscriptions, and which are in scope ------------------------------------------------------------ $subscriptions = Invoke-AACProgress -ScriptBlock { Update-AACProgress -Id 'subscriptions' -Indeterminate -Description 'Finding the subscriptions' $read = Invoke-AACGraphBatch -Query ([ordered]@{ subscriptions = "resourcecontainers | where type =~ 'microsoft.resources/subscriptions' | project id, subscriptionId, name, state = tostring(properties.state)" }) $rows = @($read.Rows['subscriptions'] | Where-Object { $null -ne $_ }) Update-AACProgress -Id 'subscriptions' -Complete -Description ('Found {0:N0} subscription(s)' -f $rows.Count) , $rows } $subscriptions = @($subscriptions) if (-not $subscriptions.Count) { throw 'The account can see no subscriptions. It needs Reader (or any role) on at least one.' } if ($SubscriptionId) { $wanted = @($SubscriptionId | ForEach-Object { $_.ToLowerInvariant() }) foreach ($id in $wanted) { if (-not @($subscriptions | Where-Object { ([string]$_['subscriptionId']).ToLowerInvariant() -eq $id }).Count) { Write-Warning "Subscription $id isn't visible to this account; it's left out." } } $subscriptions = @($subscriptions | Where-Object { $wanted -contains ([string]$_['subscriptionId']).ToLowerInvariant() }) if (-not $subscriptions.Count) { throw "None of the subscriptions asked for ($($SubscriptionId -join ', ')) is visible to this account." } } elseif ($Select) { $subscriptions = @(Read-AACSelection -Title 'Which subscriptions should the dashboard cover?' -Item ($subscriptions | Sort-Object -Property { [string]$_['name'] }) -Label { param($S) "$($S['name']) ($($S['subscriptionId']))" } -Multiple -Hint 'Use -SubscriptionId to name the subscriptions.') } $scopeIds = @(if ($SubscriptionId -or $Select) { $subscriptions | ForEach-Object { [string]$_['subscriptionId'] } }) # --- Everything else, in one batch ------------------------------------------------------------------------------- $queries = Get-AACDashboardQuery -Hours $Hours $read = Invoke-AACProgress -ScriptBlock { Update-AACProgress -Id 'dashboard' -Total $queries.Count -Description 'Reading the estate, Resource Health, Service Health, Advisor and the recent changes' $batch = Invoke-AACGraphBatch -Query $queries -SubscriptionId $scopeIds -AllowFailure @($queries.Keys | Where-Object { $_ -ne 'totals' }) -OnProgress { param($Name, $Done, $Total) Update-AACProgress -Id 'dashboard' -Increment 1 -Description "Read $Name ($Done of $Total)" } $unread = @($batch.Errors.Keys | Where-Object { $batch.Errors[$_] }).Count Update-AACProgress -Id 'dashboard' -Complete -Description ('Read the dashboard for {0:N0} subscription(s){1}' -f $subscriptions.Count, $(if ($unread) { "; $unread source(s) couldn't be read" })) $batch } $dashboard = ConvertTo-AACDashboard -Read $read -Subscription $subscriptions -Hours $Hours -Session $script:AACSession if ($showView) { $scope = [ordered]@{ Scope = $(if ($scopeIds.Count) { "$($scopeIds.Count) subscription(s)" } else { 'all subscriptions' }) } Invoke-AACPagedOutput -NoPaging:$NoPaging -ScriptBlock { Show-AACDashboardView -Dashboard $dashboard -Scope $scope } } if ($returnObject) { $dashboard } } |