Public/Find-AzureUtilsIdleResource.ps1
|
function Find-AzureUtilsIdleResource { <# .SYNOPSIS Finds idle Azure resources (running/allocated but doing no work). .DESCRIPTION Uses Azure Resource Graph to surface resources that still exist (and may still cost money) while serving no purpose, and explains each in a 'Reason' column. This complements Find-AzureUtilsOrphanResource, which looks for *unassociated* resources - here the focus is *idle* ones. Returns one object per finding (also a colored console table). Categories (all by default, narrow with -Type): StoppedVm VMs that are not running - 'stopped' (still billed for compute) or 'deallocated' (disks and static public IPs may still cost) EmptyAppServicePlan fixed-tier App Service Plans hosting no apps StoppedAksCluster AKS clusters in the 'Stopped' power state These are heuristics; review before acting. .PARAMETER SubscriptionId One or more subscription IDs. Default: every enabled subscription. .PARAMETER ManagementGroupId One or more management groups to scan (requires Az.ResourceGraph). .PARAMETER Type Limit the idle categories to check. Defaults to all of them. .EXAMPLE Find-AzureUtilsIdleResource .EXAMPLE Find-AzureUtilsIdleResource -Type StoppedVm, EmptyAppServicePlan | Sort-Object SubscriptionName, ResourceGroup .EXAMPLE Find-AzureUtilsIdleResource | Export-Csv .\idle.csv -NoTypeInformation .OUTPUTS AzureUtils.IdleResource #> [CmdletBinding(DefaultParameterSetName = 'Subscriptions')] [OutputType('AzureUtils.IdleResource')] param( [Parameter(ParameterSetName = 'Subscriptions')] [string[]] $SubscriptionId, [Parameter(ParameterSetName = 'ManagementGroup', Mandatory)] [string[]] $ManagementGroupId, [ValidateSet('StoppedVm', 'EmptyAppServicePlan', 'StoppedAksCluster')] [string[]] $Type = @('StoppedVm', 'EmptyAppServicePlan', 'StoppedAksCluster') ) $null = Assert-AzureUtilsContext $resolved = Resolve-AzureUtilsScope -SubscriptionId $SubscriptionId -ManagementGroupId $ManagementGroupId if ($resolved.Type -eq 'Subscription' -and -not $resolved.HasSubscriptions) { Write-Warning 'No accessible subscriptions in scope.' return } $query = New-AzureUtilsIdleResourceQuery -Type $Type $nameMap = $resolved.NameMap $scope = $resolved.Scope Write-Verbose "Idle-resource query:`n$query" Invoke-AzureUtilsGraphQuery -Query $query @scope | ForEach-Object { $subId = [string]$_.subscriptionId [pscustomobject]@{ PSTypeName = 'AzureUtils.IdleResource' Name = $_.name ResourceType = $_.type ResourceGroup = $_.resourceGroup Location = $_.location SubscriptionName = if ($nameMap.ContainsKey($subId)) { $nameMap[$subId] } else { $subId } SubscriptionId = $subId Reason = $_.reason Tags = ConvertTo-AzureUtilsHashtable -InputObject $_.tags ResourceId = $_.id } } } |