From 5825d22517bf44dfbf18999823ec6053effe8d82 Mon Sep 17 00:00:00 2001 From: Jovana Taylor Date: Mon, 11 May 2020 09:41:23 -0700 Subject: [PATCH 1/5] add email report to remove-resourcegroups --- Utility/ARM/Remove-ResourceGroups.ps1 | 275 ++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 Utility/ARM/Remove-ResourceGroups.ps1 diff --git a/Utility/ARM/Remove-ResourceGroups.ps1 b/Utility/ARM/Remove-ResourceGroups.ps1 new file mode 100644 index 0000000..29dcba4 --- /dev/null +++ b/Utility/ARM/Remove-ResourceGroups.ps1 @@ -0,0 +1,275 @@ + +<#PSScriptInfo + +.VERSION 1.0 + +.GUID + +.AUTHOR AzureAutomationTeam + +.COMPANYNAME Microsoft + +.COPYRIGHT + +.TAGS AzureAutomation Utility + +.LICENSEURI + +.PROJECTURI https://github.com/azureautomation/runbooks/blob/master/Utility/Remove-ResourceGroups.ps1 + +.ICONURI + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES + +#> + +#Requires -Module Azure +#Requires -Module AzureRM.Profile +#Requires -Module AzureRM.Resources + +<# +.SYNOPSIS + Connects to Azure and removes all resource groups which match the name filter + +.DESCRIPTION + This runbook connects to Azure and removes all resource groups which match the name filter. + You can run across multiple subscriptions, delete all resource groups, or run in preview mode. + Warning: This will delete all resources, including child resources in a group when preview mode is set to $false. + +.PARAMETER NameFilter + Optional + Allows you to specify a name filter to limit the resource groups that you will KEEP or DELETE. + Pass multiple name filters through a comma separated list. + The filter is not case sensitive and will match any resource group that contains the string. + +.PARAMETER PreviewMode + Optional with default of $true. + Execute the runbook to see which resource groups would be deleted but take no action. + +#> + +workflow Remove-ResourceGroups +{ + [OutputType([String])] + + param( + [parameter(Mandatory = $false)] + [string] $NameFilter, + + [parameter(Mandatory = $false)] + [bool] $PreviewMode = $true, + + [parameter(Mandatory = $false)] + [string] $FromEmailAddress, + + [parameter(Mandatory = $false)] + [string] $DestEmailAddress, + + [parameter(Mandatory = $false)] + [sting] $SendGridToken # To be converted to keyvault + ) + + $VerbosePreference = 'Continue' + + inlineScript { + $NameFilter = $using:NameFilter + $PreviewMode = $using:PreviewMode + $PSPrivateMetadata = $using:PSPrivateMetadata + + $FromEmailAddress = $using:FromEmailAddress + $DestEmailAddress = $using:DestEmailAddress + $SendGridToken = $using:SendGridToken + + # Connect to Azure with RunAs account + $conn = Get-AutomationConnection -Name "AzureRunAsConnection" + $null = Add-AzureRmAccount ` + -ServicePrincipal ` + -Tenant $conn.TenantId ` + -ApplicationId $conn.ApplicationId ` + -CertificateThumbprint $conn.CertificateThumbprint + + # Use the subscription that this Automation account is in + $null = Select-AzureRmSubscription -SubscriptionId $conn.SubscriptionID + + # Parse name filter list + if ($NameFilter) { + $nameFilterList = $NameFilter.Split(',') + [regex]$nameFilterRegex = '(' + (($nameFilterList | foreach {[regex]::escape($_.ToLower())}) –join "|") + ')' + } + + # Find the resource group that this Automation job is running in so that we can protect it from being removed + if ([string]::IsNullOrEmpty($PSPrivateMetadata.JobId.Guid)) { + throw ("This is not running from the Automation service, so could not retrieve the resource group for the Automation account in order to protect it from being removed.") + exit + } + else { + $resources = Get-AzureRmResource + $automationResources = $resources | ? {$_.ResourceType -eq "Microsoft.Automation/automationAccounts"} + foreach ($automation in $automationResources) { + # Loop through each Automation account to find this job + $job = Get-AzureRmAutomationJob -ResourceGroupName $automation.ResourceGroupName -AutomationAccountName $automation.Name -Id $PSPrivateMetadata.JobId.Guid -ErrorAction SilentlyContinue + if (!([string]::IsNullOrEmpty($job))) { + $thisResourceGroupName = $job.ResourceGroupName + return + } + } + } + + # Process the resource groups + try { + # Find RGs to remove based on passed in name filter + $resourceGroups = Get-AzureRmResourceGroup | ` + ? { $nameFilterList.Count -eq 0 -or $_.ResourceGroupName.ToLower() -match $nameFilterRegex } + $groupMap = @{} + $resourceGroups | % { $groupMap[$_.ResourceGroupName] = $_ } + + # Get the locks on the resources and RGs + $locks = Get-AzureRmResourceLock + $rLocks = $locks | ? {$_.ExtensionResourceType -eq "Microsoft.Authorization/locks"} # locks on resources + $rgLocks = $locks | ? {$_.ResourceType -eq "Microsoft.Authorization/locks"} # locks on RGs + + $resourceGroups | % { + $currentRg = $_ + # Filter out RGs with locks + $rgLock = $rgLocks | ? {$_.ResourceGroupName -eq $currentRg.ResourceGroupName} + if ($rgLock) { + Write-Output "$($currentRg.ResourceGroupName) is locked: $($rgLock.Properties.notes)" + $groupMap.Remove($currentRg.ResourceGroupName) + return + } + # Filter out the RG running this runbook + if ($currentRg.ResourceGroupName -eq $thisResourceGroupName) { + Write-Output ("The resource group for this runbook job will not be removed. Resource group: $thisResourceGroupName") + $groupMap.Remove($currentRg.ResourceGroupName) + return + } + } + + $resources = Get-AzureRmResource + $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) + $resourcesToRemove | % { + $currentR = $_ + $rLock = $rLocks | ? {$_.ResourceGroupName -eq $currentR.ResourceGroupName} | ? {$_.ResourceName -eq $currentR.Name } + if ($rLock) { + # Filter out RGs that contain locked resources + Write-Output "$($currentR.ResourceGroupName)/$($currentR.Name) is locked: $($rLock.Properties.notes)" + $groupMap.Remove($currentR.ResourceGroupName) + } + } + + # The RGs to be removed + $groupsToRemove = $groupMap.Values + + # No matching groups were found to remove + if ($groupsToRemove.Count -eq 0) { + Write-Output "No matching resource groups found." + return + } + # Matching groups were found to remove + else + { + # The resources in RGs to be removed + $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) + + # In preview mode, so report what would be removed, but take no action. + if ($PreviewMode -eq $true) { + Write-Output "Preview Mode: The following resource groups would be removed:" + foreach ($group in $groupsToRemove){ + Write-Output "`t$($group.ResourceGroupName)" + } + Write-Output "Preview Mode: The following resources would be removed:" + $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } + } + # Remove the resource groups + else { + Write-Output "The following resource groups will be removed:" + foreach ($group in $groupsToRemove){ + Write-Output $($group.ResourceGroupName) + } + Write-Output "The following resources will be removed:" + $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } + # Here is where the remove actions happen + foreach ($resourceGroup in $groupsToRemove) { + Write-Output "Starting to remove resource group: $($resourceGroup.ResourceGroupName) ..." + # Remove-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -Force + if ((Get-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -ErrorAction SilentlyContinue) -eq $null) { + Write-Output "...successfully removed resource group: $($resourceGroup.ResourceGroupName)" + } + } + } + Write-Output "Completed." + } + } + catch { + $errorMessage = $_ + } + if ($errorMessage) { + Write-Error $errorMessage + } + + # Send email report + if ($DestEmailAddress -and $FromEmailAddress -and $SendGridKey) { + + $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $headers.Add("Authorization", "Bearer " + $SendGridKey) + $headers.Add("Content-Type", "application/json") + + if ($PreviewMode) { + $subject = "Azure Resources scheduled for cleanup" + $content = "

Cleanup summary

" + $content += "

Resources scheduled for clean up:

" + } else { + $subject = "Azure Resources cleanup report" + $content = "

Cleanup summary

" + $content += "

Resources cleaned up:

" + } + + # TODO: if cleanup occurred, should report on any failures + + $resourcesToRemoveTable = $resourcesToRemove | Select-Object Name, ResourceGroupName, ResourceType | ConvertTo-Html -Fragment + $content += $resourcesToRemoveTable + + $content += "

Locked Resource Groups:

" + $content += $rgLocks | Select-Object -ExpandProperty Properties -Property ResourceGroupName, Name | Select-Object ResourceGroupName, Name, notes | ConvertTo-Html -Fragment + + $content += "

Locked resources:

" + $content += $rLocks | Select-Object -ExpandProperty Properties -Property ResourceName, ResourceGroupName, Name | Select-Object ResourceName, ResourceGroupName, Name, notes | ConvertTo-Html -Fragment + + $content += "" + + $content = $content -join [Environment]::NewLine + + $body = @{ + personalizations = @( + @{ + to = @( + @{ + email = $DestEmailAddress + } + ) + } + ) + from = @{ + email = $FromEmailAddress + } + subject = $subject + content = @( + @{ + type = "text/html" + value = $content + } + ) + } + + $bodyJson = $body | ConvertTo-Json -Depth 8 + + $response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -Body $bodyJson + } + } +} \ No newline at end of file From ea9d3566c44171492e187e44dff20bed1e50dec4 Mon Sep 17 00:00:00 2001 From: Jovana Taylor Date: Mon, 11 May 2020 09:45:35 -0700 Subject: [PATCH 2/5] fixing indentation --- Utility/ARM/Remove-ResourceGroups.ps1 | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Utility/ARM/Remove-ResourceGroups.ps1 b/Utility/ARM/Remove-ResourceGroups.ps1 index 29dcba4..b6bb279 100644 --- a/Utility/ARM/Remove-ResourceGroups.ps1 +++ b/Utility/ARM/Remove-ResourceGroups.ps1 @@ -58,28 +58,28 @@ workflow Remove-ResourceGroups { [OutputType([String])] - param( - [parameter(Mandatory = $false)] - [string] $NameFilter, + param( + [parameter(Mandatory = $false)] + [string] $NameFilter, - [parameter(Mandatory = $false)] - [bool] $PreviewMode = $true, + [parameter(Mandatory = $false)] + [bool] $PreviewMode = $true, [parameter(Mandatory = $false)] [string] $FromEmailAddress, [parameter(Mandatory = $false)] [string] $DestEmailAddress, - + [parameter(Mandatory = $false)] - [sting] $SendGridToken # To be converted to keyvault - ) + [sting] $SendGridToken # To be converted to keyvault + ) - $VerbosePreference = 'Continue' + $VerbosePreference = 'Continue' - inlineScript { - $NameFilter = $using:NameFilter - $PreviewMode = $using:PreviewMode + inlineScript { + $NameFilter = $using:NameFilter + $PreviewMode = $using:PreviewMode $PSPrivateMetadata = $using:PSPrivateMetadata $FromEmailAddress = $using:FromEmailAddress From c046763bbbc6a51ee1a8ffed09a73ed466ecbb66 Mon Sep 17 00:00:00 2001 From: Jovana Taylor Date: Fri, 5 Jun 2020 15:08:56 -0700 Subject: [PATCH 3/5] fixing email logic --- Utility/ARM/Remove-ResourceGroups.ps1 | 400 +++++++++++++------------- 1 file changed, 204 insertions(+), 196 deletions(-) diff --git a/Utility/ARM/Remove-ResourceGroups.ps1 b/Utility/ARM/Remove-ResourceGroups.ps1 index b6bb279..10c0f49 100644 --- a/Utility/ARM/Remove-ResourceGroups.ps1 +++ b/Utility/ARM/Remove-ResourceGroups.ps1 @@ -30,6 +30,7 @@ #> #Requires -Module Azure +#Requires -Module AzureRM.KeyVault #Requires -Module AzureRM.Profile #Requires -Module AzureRM.Resources @@ -54,222 +55,229 @@ #> -workflow Remove-ResourceGroups -{ - [OutputType([String])] - param( - [parameter(Mandatory = $false)] - [string] $NameFilter, +param( + [parameter(Mandatory = $false)] + [string] $NameFilter, - [parameter(Mandatory = $false)] - [bool] $PreviewMode = $true, + [parameter(Mandatory = $false)] + [bool] $PreviewMode = $true, - [parameter(Mandatory = $false)] - [string] $FromEmailAddress, + [parameter(Mandatory = $false)] + [string] $FromEmailAddress, - [parameter(Mandatory = $false)] - [string] $DestEmailAddress, + [parameter(Mandatory = $false)] + [string] $DestEmailAddress, - [parameter(Mandatory = $false)] - [sting] $SendGridToken # To be converted to keyvault - ) - - $VerbosePreference = 'Continue' - - inlineScript { - $NameFilter = $using:NameFilter - $PreviewMode = $using:PreviewMode - $PSPrivateMetadata = $using:PSPrivateMetadata - - $FromEmailAddress = $using:FromEmailAddress - $DestEmailAddress = $using:DestEmailAddress - $SendGridToken = $using:SendGridToken - - # Connect to Azure with RunAs account - $conn = Get-AutomationConnection -Name "AzureRunAsConnection" - $null = Add-AzureRmAccount ` - -ServicePrincipal ` - -Tenant $conn.TenantId ` - -ApplicationId $conn.ApplicationId ` - -CertificateThumbprint $conn.CertificateThumbprint - - # Use the subscription that this Automation account is in - $null = Select-AzureRmSubscription -SubscriptionId $conn.SubscriptionID - - # Parse name filter list - if ($NameFilter) { - $nameFilterList = $NameFilter.Split(',') - [regex]$nameFilterRegex = '(' + (($nameFilterList | foreach {[regex]::escape($_.ToLower())}) –join "|") + ')' - } - - # Find the resource group that this Automation job is running in so that we can protect it from being removed - if ([string]::IsNullOrEmpty($PSPrivateMetadata.JobId.Guid)) { - throw ("This is not running from the Automation service, so could not retrieve the resource group for the Automation account in order to protect it from being removed.") - exit + [parameter(Mandatory = $false)] + [string] $KeyvaultName # Keyvault that contains "SendGridAPIKey" secret +) + +Import-Module AzureRM.KeyVault + +$VerbosePreference = 'Continue' + +Write-Output "Connecting to Azure" + +# Connect to Azure with RunAs account +$conn = Get-AutomationConnection -Name "AzureRunAsConnection" +$null = Login-AzureRmAccount ` + -ServicePrincipal ` + -Tenant $conn.TenantId ` + -ApplicationId $conn.ApplicationId ` + -CertificateThumbprint $conn.CertificateThumbprint + +# Use the subscription that this Automation account is in +$null = Select-AzureRmSubscription -SubscriptionId $conn.SubscriptionID + +# Parse name filter list +if ($NameFilter) { + $nameFilterList = $NameFilter.Split(',') + [regex]$nameFilterRegex = '(' + (($nameFilterList | foreach {[regex]::escape($_.ToLower())}) –join "|") + ')' +} + +Write-Output "Determine current resource group" + +# Find the resource group that this Automation job is running in so that we can protect it from being removed +if ([string]::IsNullOrEmpty($PSPrivateMetadata.JobId.Guid)) { + throw ("This is not running from the Automation service, so could not retrieve the resource group for the Automation account in order to protect it from being removed.") + exit +} +else { + $resources = Get-AzureRmResource + $automationResources = $resources | ? {$_.ResourceType -eq "Microsoft.Automation/automationAccounts"} + foreach ($automation in $automationResources) { + # Loop through each Automation account to find this job + $job = Get-AzureRmAutomationJob -ResourceGroupName $automation.ResourceGroupName -AutomationAccountName $automation.Name -Id $PSPrivateMetadata.JobId.Guid -ErrorAction SilentlyContinue + if (!([string]::IsNullOrEmpty($job))) { + $thisResourceGroupName = $job.ResourceGroupName + break } - else { - $resources = Get-AzureRmResource - $automationResources = $resources | ? {$_.ResourceType -eq "Microsoft.Automation/automationAccounts"} - foreach ($automation in $automationResources) { - # Loop through each Automation account to find this job - $job = Get-AzureRmAutomationJob -ResourceGroupName $automation.ResourceGroupName -AutomationAccountName $automation.Name -Id $PSPrivateMetadata.JobId.Guid -ErrorAction SilentlyContinue - if (!([string]::IsNullOrEmpty($job))) { - $thisResourceGroupName = $job.ResourceGroupName - return - } - } + } +} + +Get-Module -Name AzureRM* + +# Process the resource groups +try { + # Find RGs to remove based on passed in name filter + $resourceGroups = Get-AzureRmResourceGroup | ` + ? { $nameFilterList.Count -eq 0 -or $_.ResourceGroupName.ToLower() -match $nameFilterRegex } + $groupMap = @{} + $resourceGroups | % { $groupMap[$_.ResourceGroupName] = $_ } + + # Get the locks on the resources and RGs + $locks = Get-AzureRmResourceLock + $rLocks = $locks | ? {$_.ExtensionResourceType -eq "Microsoft.Authorization/locks"} # locks on resources + $rgLocks = $locks | ? {$_.ResourceType -eq "Microsoft.Authorization/locks"} # locks on RGs + + $resourceGroups | % { + $currentRg = $_ + # Filter out RGs with locks + $rgLock = $rgLocks | ? {$_.ResourceGroupName -eq $currentRg.ResourceGroupName} + if ($rgLock) { + Write-Output "$($currentRg.ResourceGroupName) is locked: $($rgLock.Properties.notes)" + $groupMap.Remove($currentRg.ResourceGroupName) + return } + # Filter out the RG running this runbook + if ($currentRg.ResourceGroupName -eq $thisResourceGroupName) { + Write-Output ("The resource group for this runbook job will not be removed. Resource group: $thisResourceGroupName") + $groupMap.Remove($currentRg.ResourceGroupName) + return + } + } - # Process the resource groups - try { - # Find RGs to remove based on passed in name filter - $resourceGroups = Get-AzureRmResourceGroup | ` - ? { $nameFilterList.Count -eq 0 -or $_.ResourceGroupName.ToLower() -match $nameFilterRegex } - $groupMap = @{} - $resourceGroups | % { $groupMap[$_.ResourceGroupName] = $_ } - - # Get the locks on the resources and RGs - $locks = Get-AzureRmResourceLock - $rLocks = $locks | ? {$_.ExtensionResourceType -eq "Microsoft.Authorization/locks"} # locks on resources - $rgLocks = $locks | ? {$_.ResourceType -eq "Microsoft.Authorization/locks"} # locks on RGs - - $resourceGroups | % { - $currentRg = $_ - # Filter out RGs with locks - $rgLock = $rgLocks | ? {$_.ResourceGroupName -eq $currentRg.ResourceGroupName} - if ($rgLock) { - Write-Output "$($currentRg.ResourceGroupName) is locked: $($rgLock.Properties.notes)" - $groupMap.Remove($currentRg.ResourceGroupName) - return - } - # Filter out the RG running this runbook - if ($currentRg.ResourceGroupName -eq $thisResourceGroupName) { - Write-Output ("The resource group for this runbook job will not be removed. Resource group: $thisResourceGroupName") - $groupMap.Remove($currentRg.ResourceGroupName) - return - } - } - - $resources = Get-AzureRmResource - $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) - $resourcesToRemove | % { - $currentR = $_ - $rLock = $rLocks | ? {$_.ResourceGroupName -eq $currentR.ResourceGroupName} | ? {$_.ResourceName -eq $currentR.Name } - if ($rLock) { - # Filter out RGs that contain locked resources - Write-Output "$($currentR.ResourceGroupName)/$($currentR.Name) is locked: $($rLock.Properties.notes)" - $groupMap.Remove($currentR.ResourceGroupName) - } - } + $resources = Get-AzureRmResource + $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) + $resourcesToRemove | % { + $currentR = $_ + $rLock = $rLocks | ? {$_.ResourceGroupName -eq $currentR.ResourceGroupName} | ? {$_.ResourceName -eq $currentR.Name } + if ($rLock) { + # Filter out RGs that contain locked resources + Write-Output "$($currentR.ResourceGroupName)/$($currentR.Name) is locked: $($rLock.Properties.notes)" + $groupMap.Remove($currentR.ResourceGroupName) + } + } - # The RGs to be removed - $groupsToRemove = $groupMap.Values + # The RGs to be removed + $groupsToRemove = $groupMap.Values - # No matching groups were found to remove - if ($groupsToRemove.Count -eq 0) { - Write-Output "No matching resource groups found." - return - } - # Matching groups were found to remove - else - { - # The resources in RGs to be removed - $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) - - # In preview mode, so report what would be removed, but take no action. - if ($PreviewMode -eq $true) { - Write-Output "Preview Mode: The following resource groups would be removed:" - foreach ($group in $groupsToRemove){ - Write-Output "`t$($group.ResourceGroupName)" - } - Write-Output "Preview Mode: The following resources would be removed:" - $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } - } - # Remove the resource groups - else { - Write-Output "The following resource groups will be removed:" - foreach ($group in $groupsToRemove){ - Write-Output $($group.ResourceGroupName) - } - Write-Output "The following resources will be removed:" - $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } - # Here is where the remove actions happen - foreach ($resourceGroup in $groupsToRemove) { - Write-Output "Starting to remove resource group: $($resourceGroup.ResourceGroupName) ..." - # Remove-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -Force - if ((Get-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -ErrorAction SilentlyContinue) -eq $null) { - Write-Output "...successfully removed resource group: $($resourceGroup.ResourceGroupName)" - } - } - } - Write-Output "Completed." + # No matching groups were found to remove + if ($groupsToRemove.Count -eq 0) { + Write-Output "No matching resource groups found." + return + } + # Matching groups were found to remove + else + { + # The resources in RGs to be removed + $resourcesToRemove = ($resources | ? {$groupMap.ContainsKey($_.ResourceGroupName)}) + + # In preview mode, so report what would be removed, but take no action. + if ($PreviewMode -eq $true) { + Write-Output "Preview Mode: The following resource groups would be removed:" + foreach ($group in $groupsToRemove){ + Write-Output "`t$($group.ResourceGroupName)" } + Write-Output "Preview Mode: The following resources would be removed:" + $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } } - catch { - $errorMessage = $_ - } - if ($errorMessage) { - Write-Error $errorMessage + # Remove the resource groups + else { + $resourcesRemoved = @() + $resourceRemoveFailed = @() + Write-Output "The following resource groups will be removed:" + foreach ($group in $groupsToRemove){ + Write-Output $($group.ResourceGroupName) + } + Write-Output "The following resources will be removed:" + $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } + # Here is where the remove actions happen + foreach ($resourceGroup in $groupsToRemove) { + Write-Output "Starting to remove resource group: $($resourceGroup.ResourceGroupName) ..." + Remove-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -Force + if ((Get-AzureRmResourceGroup -Name $($resourceGroup.ResourceGroupName) -ErrorAction SilentlyContinue) -eq $null) { + $resourcesRemoved += $resourceGroup + Write-Output "...successfully removed resource group: $($resourceGroup.ResourceGroupName)" + } else { + $resourceRemoveFailed += $resourceGroup + Write-Output "...failed to remove resource group: $($resourceGroup.ResourceGroupName)" + } + } } - - # Send email report - if ($DestEmailAddress -and $FromEmailAddress -and $SendGridKey) { + Write-Output "Completed." + } +} +catch { + $errorMessage = $_ +} +if ($errorMessage) { + Write-Error $errorMessage +} + +# Send email report +if ($DestEmailAddress -and $FromEmailAddress -and $KeyvaultName) { + $SendGridKey = (Get-AzureKeyVaultSecret -VaultName $KeyvaultName -Name "SendGridAPIKey").SecretValueText + + $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $headers.Add("Authorization", "Bearer " + $SendGridKey) + $headers.Add("Content-Type", "application/json") - $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $headers.Add("Authorization", "Bearer " + $SendGridKey) - $headers.Add("Content-Type", "application/json") - - if ($PreviewMode) { - $subject = "Azure Resources scheduled for cleanup" - $content = "

Cleanup summary

" - $content += "

Resources scheduled for clean up:

" - } else { - $subject = "Azure Resources cleanup report" - $content = "

Cleanup summary

" - $content += "

Resources cleaned up:

" - } - - # TODO: if cleanup occurred, should report on any failures - - $resourcesToRemoveTable = $resourcesToRemove | Select-Object Name, ResourceGroupName, ResourceType | ConvertTo-Html -Fragment - $content += $resourcesToRemoveTable + if ($PreviewMode) { + $subject = "Azure Resources scheduled for cleanup" + $content = "

Cleanup summary

" + $content += "

Resources scheduled for clean up:

" + $resourcesToRemoveTable = $resourcesToRemove | Select-Object Name, ResourceGroupName, ResourceType | ConvertTo-Html -Fragment + $content += $resourcesToRemoveTable + } else { + $subject = "Azure Resources cleanup report" + $content = "

Cleanup summary

" + if ($resourcesRemoved) { + $content += "

Resource groups cleaned up:

" + $resourcesRemovedTable = $resourcesRemoved | Select-Object ResourceGroupName | ConvertTo-Html -Fragment + $content += $resourcesRemovedTable + } + if ($resourceRemoveFailed) { + $content += "

Resource groups with failed clean up:

" + $resourceRemoveFailedTable = $resourceRemoveFailed | Select-Object ResourceGroupName | ConvertTo-Html -Fragment + $content += $resourceRemoveFailedTable + } + } - $content += "

Locked Resource Groups:

" - $content += $rgLocks | Select-Object -ExpandProperty Properties -Property ResourceGroupName, Name | Select-Object ResourceGroupName, Name, notes | ConvertTo-Html -Fragment + $content += "

Locked Resource Groups:

" + $content += $rgLocks | Select-Object -ExpandProperty Properties -Property ResourceGroupName, Name | Select-Object ResourceGroupName, Name, notes | ConvertTo-Html -Fragment - $content += "

Locked resources:

" - $content += $rLocks | Select-Object -ExpandProperty Properties -Property ResourceName, ResourceGroupName, Name | Select-Object ResourceName, ResourceGroupName, Name, notes | ConvertTo-Html -Fragment + $content += "

Locked resources:

" + $content += $rLocks | Select-Object -ExpandProperty Properties -Property ResourceName, ResourceGroupName, Name | Select-Object ResourceName, ResourceGroupName, Name, notes | ConvertTo-Html -Fragment - $content += "" + $content += "" - $content = $content -join [Environment]::NewLine + $content = $content -join [Environment]::NewLine - $body = @{ - personalizations = @( - @{ - to = @( - @{ - email = $DestEmailAddress - } - ) - } + $body = @{ + personalizations = @( + @{ + to = @( + @{ + email = $DestEmailAddress + } ) - from = @{ - email = $FromEmailAddress - } - subject = $subject - content = @( - @{ - type = "text/html" - value = $content - } - ) - } - - $bodyJson = $body | ConvertTo-Json -Depth 8 - - $response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -Body $bodyJson } + ) + from = @{ + email = $FromEmailAddress } + subject = $subject + content = @( + @{ + type = "text/html" + value = $content + } + ) + } + + $bodyJson = $body | ConvertTo-Json -Depth 8 + + $response = Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method Post -Headers $headers -Body $bodyJson } \ No newline at end of file From 303a80cfda936a5478ab48dcb42f39682f14c701 Mon Sep 17 00:00:00 2001 From: Jovana Taylor Date: Tue, 9 Jun 2020 15:29:39 -0700 Subject: [PATCH 4/5] remove unused code --- Utility/ARM/Remove-ResourceGroups.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/Utility/ARM/Remove-ResourceGroups.ps1 b/Utility/ARM/Remove-ResourceGroups.ps1 index 10c0f49..9ab66fb 100644 --- a/Utility/ARM/Remove-ResourceGroups.ps1 +++ b/Utility/ARM/Remove-ResourceGroups.ps1 @@ -116,8 +116,6 @@ else { } } -Get-Module -Name AzureRM* - # Process the resource groups try { # Find RGs to remove based on passed in name filter From 11e58bf8ed489531e0f445794cdcc42507302b57 Mon Sep 17 00:00:00 2001 From: Jovana Taylor Date: Wed, 17 Jun 2020 14:08:03 -0700 Subject: [PATCH 5/5] Adding comments --- Utility/ARM/Remove-ResourceGroups.ps1 | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Utility/ARM/Remove-ResourceGroups.ps1 b/Utility/ARM/Remove-ResourceGroups.ps1 index 9ab66fb..14f21e6 100644 --- a/Utility/ARM/Remove-ResourceGroups.ps1 +++ b/Utility/ARM/Remove-ResourceGroups.ps1 @@ -53,6 +53,21 @@ Optional with default of $true. Execute the runbook to see which resource groups would be deleted but take no action. +.PARAMETER FromEmailAddress + Optional + Specify the sender address for the email notification + Requires DestEmailAddress and KeyvaultName to be set + +.PARAMETER DestEmailAddress + Optional + Specify the recipient address for the email notification + Requires FromEmailAddress and KeyvaultName to be set + +.PARAMETER KeyvaultName + Optional + Specify the name of a KeyVault that contains the "SendGridAPIKey" secret + The Run As Automation Account must have read access to the secrets in the KeyVault + Requires FromEmailAddress and DestEmailAddress to be set #> @@ -70,7 +85,7 @@ param( [string] $DestEmailAddress, [parameter(Mandatory = $false)] - [string] $KeyvaultName # Keyvault that contains "SendGridAPIKey" secret + [string] $KeyvaultName ) Import-Module AzureRM.KeyVault @@ -179,7 +194,7 @@ try { Write-Output "`t$($group.ResourceGroupName)" } Write-Output "Preview Mode: The following resources would be removed:" - $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } + $resourcesToRemove | % { "`t$($_.ResourceGroupName)/$($_.Name)" } } # Remove the resource groups else { @@ -190,7 +205,7 @@ try { Write-Output $($group.ResourceGroupName) } Write-Output "The following resources will be removed:" - $resources | % { "`t$($_.ResourceGroupName)/$($_.Name)" } + $resourcesToRemove | % { "`t$($_.ResourceGroupName)/$($_.Name)" } # Here is where the remove actions happen foreach ($resourceGroup in $groupsToRemove) { Write-Output "Starting to remove resource group: $($resourceGroup.ResourceGroupName) ..."