thereaper.ps1


<#PSScriptInfo
 
.VERSION 1.0.2
 
.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,
    [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)
    {
        $FilteredSubs=$Subscriptions|Where-Object{$_.id -like "*$SubscriptionFilter*"}
        $FilteredSubs|ForEach-Object{$InScopeSubscriptions+=$_}
    }
}
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)" -PercentComplete $PurgeProgress
    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