thereaper.ps1
<#PSScriptInfo .VERSION 1.0.0 .GUID fd91400e-efd8-4ae8-856d-6fa32b1250d3 .AUTHOR Chris Speers .COMPANYNAME Avanade .COPYRIGHT 2016 Avanade .TAGS Azure ARM .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> #Requires -Module Avanade.AzureAD <# .DESCRIPTION Asynchronously deletes any resource groups not matching the desired tag set .PARAMETER ClientId The Azure AD Application Client Id .PARAMETER TenantId The Azure AD Tenant Id .PARAMETER ResourceId The Azure AD Authorized Resource Uri .PARAMETER ArmEndpoint The Azure Resource Manager Endpoint .PARAMETER ArmApiVersion The ARM Api Version .PARAMETER Credential The Credential to use .PARAMETER SubscriptionFilters Limit the scope to only the specified Subscription id's .PARAMETER RequiredTags The list of required tag names .PARAMETER AllowEmptyTags Keep resource groups with an empty tag value .PARAMETER DeleteEmpty Delete resource groups with no resources even if tagged properly #> [CmdletBinding()] Param ( [Parameter(Mandatory=$false)] [System.String] $ClientId='1950a258-227b-4e31-a9cf-717495945fc2', [Parameter(Mandatory=$false)] [System.String] $TenantId='common', [Parameter(Mandatory=$false)] [System.Uri] $ResourceId='https://management.core.windows.net', [Parameter(Mandatory=$false)] [System.Uri] $ArmEndpoint='https://management.azure.com', [Parameter(Mandatory=$false)] $ArmApiVersion='2015-11-01', [Parameter(Mandatory=$true)] [PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(Mandatory=$true)] [String[]]$RequiredTags, [Parameter(Mandatory=$false)] [String[]] $SubscriptionFilters, [Parameter(Mandatory=$false)] [Switch] $Whatif=$true, [Parameter(Mandatory=$false)] [Switch] $AllowEmptyTags, [Parameter(Mandatory=$false)] [Switch] $DeleteEmpty ) $SubscriptionItems=@() $InScopeSubscriptions=@() $ResourceGroupsToPurge=@() $ResourceGroupsToKeep=@() $DeletedResourceGroups=@() Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Status "Retrieving OAuth Token From Azure AD" -PercentComplete 5 $AuthToken=Get-AzureADUserToken -Resource $ResourceId -ClientId $ClientId -Credential $Credential -TenantId $TenantId $Headers=@{Authorization="Bearer $($AuthToken.access_token)";} Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Status "Retrieving Azure Subscriptions" -PercentComplete 15 $UriBld=New-Object System.UriBuilder($ArmEndpoint) $UriBld.Path="Subscriptions" $UriBld.Query="api-version=$ArmApiVersion" $Subscriptions=@(Invoke-RestMethod -Uri $UriBld.Uri -ContentType "application/json" -Headers $Headers|Select-Object -ExpandProperty Value) if(($null -ne $SubscriptionFilters) -and ($SubscriptionFilters.Count -gt 0)) { foreach($SubscriptionFilter in $SubscriptionFilters) { if($Subscription.id -like "*$SubscriptionFilter*") { $InScopeSubscriptions+=$Subscription } } } else { $InScopeSubscriptions=$Subscriptions } Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Status "Gathering Azure Subscription Detail" -PercentComplete 25 Write-Progress -ParentId 33 -Id 333 -Activity "Gathering Subscription Data" -PercentComplete 0 for ($i = 0; $i -lt $InScopeSubscriptions.Count; $i++) { $SubProgress=($i/$InScopeSubscriptions.Count)*100 $Subscription=$InScopeSubscriptions[$i] $SubResults=@{Subscription=$Subscription} Write-Progress -ParentId 33 -Id 333 -Activity "Gathering Subscription Data" -Status "Gathering Subscription $($Subscription.id) Providers" -PercentComplete $SubProgress $UriBld.Path="$($Subscription.id)/providers" $SubResult=Invoke-RestMethod -Uri $UriBld.Uri -ContentType "application/json" -Headers $Headers|Select-Object -ExpandProperty Value $SubResults.Providers=$SubResult Write-Progress -ParentId 33 -Id 333 -Activity "Gathering Subscription Data" -Status "Gathering Subscription $($Subscription.id) Resource Groups" -PercentComplete $SubProgress $UriBld.Path="$($Subscription.id)/resourceGroups" $ResourceGroups=Invoke-RestMethod -Uri $UriBld.Uri -ContentType "application/json" -Headers $Headers|Select-Object -ExpandProperty Value $SubResults.ResourceGroups=$ResourceGroups Write-Progress -ParentId 33 -Id 333 -Activity "Gathering Subscription Data" -Status "Gathering Subscription $($Subscription.id) Resources" -PercentComplete $SubProgress $UriBld.Path="$($Subscription.id)/resources" $Resources=Invoke-RestMethod -Uri $UriBld.Uri -ContentType "application/json" -Headers $Headers|Select-Object -ExpandProperty Value $SubResults.Resources=$Resources $SubscriptionItem=New-Object PSObject -Property $SubResults $SubscriptionItems+=$SubscriptionItem } Write-Progress -ParentId 33 -Id 333 -Activity "Gathering Subscription Data" -Completed Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Status "Evaluating Resource Group Tags" -PercentComplete 35 for ($i = 0; $i -lt $SubscriptionItems.Count; $i++) { Write-Progress -ParentId 33 -Id 3333 -Activity "Examining Resource Group Tags" -PercentComplete 0 $SubscriptionItem=$SubscriptionItems[$i] #Start with the ones we know we wanna kill.. if($null -ne $SubscriptionItem.ResourceGroups) { $UntaggedResourceGroups=@($SubscriptionItem.ResourceGroups|Where-Object{$_.tags -eq $null}) $UntaggedResourceGroups|ForEach-Object{Write-Verbose "Removing Untagged Resource Group $($_.id)";$ResourceGroupsToPurge+=$_;} } $TaggedResourceGroups=@($SubscriptionItem.ResourceGroups|Where-Object{$_.tags -ne $null}) for ($t = 0; $t -lt $TaggedResourceGroups.Count; $t++) { $TaggedResourceGroup=$TaggedResourceGroups[$t] Write-Verbose "Examining Resource Group $($TaggedResourceGroup.id)" $TagProgress=($t/$TaggedResourceGroups.Count)*100 Write-Progress -ParentId 33 -Id 3333 -Activity "Examining Resource Group Tags" -Status "Examining Resource Group $($TaggedResourceGroup.id)" -PercentComplete $TagProgress $ResourceGroupResources=@($SubscriptionItem.Resources|Where-Object{$_.id -like "$($TaggedResourceGroup.id)*"}) $ResourceGroupTagNames=$TaggedResourceGroup.tags|Get-Member -MemberType NoteProperty|Select-Object -ExpandProperty Name $AllTagsPresent=$true foreach ($RequiredTag in $RequiredTags) { #Check for the tag presence if($RequiredTag -notin $ResourceGroupTagNames) { Write-Verbose "Required Tag $RequiredTag is not present on $($TaggedResourceGroup.id)" $AllTagsPresent=$false break } else { #See if there is a value too... if(-not $AllowEmptyTags) { if([String]::IsNullOrEmpty($TaggedResourceGroup.tags."$RequiredTag")) { Write-Verbose "Required Tag $RequiredTag is present but empty on $($TaggedResourceGroup.id)" $AllTagsPresent=$false break } } } } if($AllTagsPresent) { if($DeleteEmpty.IsPresent -and $ResourceGroupResources.Count -eq 0) { Write-Verbose "$($TaggedResourceGroup.id) is tagged but has no resources. It will be removed" $ResourceGroupsToPurge+=$TaggedResourceGroup } else { Write-Verbose "$($TaggedResourceGroup.id) is tagged and has resources. It will be kept" $ResourceGroupsToKeep+=$TaggedResourceGroup } } else { Write-Verbose "$($TaggedResourceGroup.id) is not properly tagged. It will be removed" $ResourceGroupsToPurge+=$TaggedResourceGroup } } Write-Progress -ParentId 33 -Id 3333 -Activity "Examining Resource Group Tags" -Completed } Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Status "Removing Resource Groups" -PercentComplete 50 Write-Progress -ParentId 33 -Id 33333 -Activity "Deleting $($ResourceGroupsToPurge.Count) Resource Groups" -PercentComplete 0 for ($i = 0; $i -lt $ResourceGroupsToPurge.Count; $i++) { $PurgeProgress=($i/$ResourceGroupsToPurge.Count)*100 $ResourceGroupToPurge=$ResourceGroupsToPurge[$i] Write-Progress -ParentId 33 -Id 33333 -Activity 'Deleting Resource Groups' -Status "Deleting Resource Group $($ResourceGroupToPurge.id)" try { Write-Verbose "Deleting Resource Group $($ResourceGroupToPurge.id)" $UriBld.Path=$ResourceGroupToPurge.id if(-not $Whatif) { Invoke-RestMethod -Method Delete -Uri $UriBld.Uri -Headers $Headers|Out-Null } $DeletedResourceGroups+=$ResourceGroupToPurge } catch { Write-Warning "Error deleting resource group $($ResourceGroupToPurge.id) $_" } } Write-Progress -ParentId 33 -Id 33333 -Activity 'Deleting Resource Groups' -Completed Write-Progress -Id 33 -Activity "Cleaning Resource Groups By Tag" -Completed $ProcessResult=New-Object PSObject -Property @{ ResourceGroupsRetained=$ResourceGroupsToKeep; ResourceGroupsDeleted=$DeletedResourceGroups; } return $ProcessResult |