tools/cleanup.ps1 (462 lines of code) (raw):

param ( [Parameter(Mandatory=$true)] [string]$RG, [Parameter(Mandatory=$false)] [switch]$RemoveAll, [Parameter(Mandatory=$false)] [switch]$RemovePacks, [Parameter(Mandatory=$false)] [switch]$RemoveAMAPolicySet, [Parameter(Mandatory=$false)] [switch]$RemoveMainSolution, [Parameter(Mandatory=$false)] [switch]$RemoveDiscovery, [Parameter(Mandatory=$false)] [switch]$RemoveDiscoveryVMApps, [Parameter(Mandatory=$false)] [switch]$RemoveStorage, [Parameter(Mandatory=$false)] [switch]$RemoveLAW, [Parameter(Mandatory=$false)] [switch]$confirmEachPack ) #region functions function remove-appversions { param ( [object]$vm, [array]$appstoremove ) #$vmswithApps=Get-AzVM | Where-Object { $_.ApplicationProfile -ne $null} #$appstoremove=@("/subscriptions/6c64f9ed-88d2-4598-8de6-7a9527dc16ca/resourceGroups/rg-MonstarPacks/providers/Microsoft.Compute/galleries/AMPprodGallery/applications/ADDS-collection/versions/1.0.0","/subscriptions/6c64f9ed-88d2-4598-8de6-7a9527dc16ca/resourceGroups/rg-MonstarPacks/providers/Microsoft.Compute/galleries/AMPprodGallery/applications/prod-windiscovery/versions/1.0.0") # foreach ($VM in $vmswithApps) # { # if app name is the one to remove foreach ($apptoremove in $appstoremove) { "Checking for app: $apptoremove" "VM has " + $VM.ApplicationProfile.GalleryApplications.Count + " apps." $app = $VM.ApplicationProfile.GalleryApplications | Where-Object { $_.PackageReferenceId -eq $apptoremove} if ($app) { # remove app from vm "Removing $($app.PackageReferenceId.Split("/applications/")[1]) from VM: $($VM.Name)" # Then and only then find the VM. Need to switch to the VM subscription if different. if ((Get-AzContext).Subscription.Id -ne $VM.id.Split('/')[2]) { Set-AzContext -Subscription $VM.Id.Split('/')[2] } $VMT=get-azVM -ResourceGroupName $VM.ResourceGroup -Name $VM.name $VMT.ApplicationProfile.GalleryApplications.Remove($app) Write-Output "Removed app from VM: $($VMT.Name)" } } #$vm.ApplicationProfile Write-Output "Updating VM: $($VM.Name)" Update-AzVM -VM $VMT -ResourceGroupName $VMT.ResourceGroupName -asjob } #endregion # Check login # import module(s) # Resource graph #region initialization Write-Output "Installing/Loading Azure Resource Graph module." if ($null -eq (get-module Az.ResourceGraph)) { try { install-module az.resourcegraph import-module az.ResourceGraph #-Force } catch { Write-Error "Unable to install az.resourcegraph module. Please make sure you have the proper permissions to install modules." return } } # Test if resource group exists Write-Output "Checking if resource group $RG exists." if ($null -eq (Get-AzResourceGroup -Name $RG -ErrorAction SilentlyContinue)) { Write-Error "Resource group $RG does not exist." return } # Add deployment cleanup. Deployments may conflict if previous deployment to the same resource group failed or done to another region. #endregion #region AMAPolicySet # AMA policy set removal # Remove policy sets if ($RemoveAMAPolicySet -or $RemoveAll) { "Removing AMA policy set." $inits=Get-AzPolicySetDefinition | where-object {$_.Metadata.MonitorStarterPacks -ne $null} foreach ($init in $inits) { "Removing policy set $($init.Id)" #$assignments=Get-AzPolicyAssignment -PolicyDefinitionId $init.Id $query=@" policyresources | where type == "microsoft.authorization/policyassignments" | extend AssignmentDisplayName=properties.displayName,scope=properties.scope,PolicyId=tostring(properties.policyDefinitionId) | where PolicyId == '$($init.Id)' "@ $assignments=Search-AzGraph -Query $query -UseTenantScope if ($assignments.count -ne 0) { "Removing assignments for $($init.Id) initiative." foreach ($assignment in $assignments) { "Removing assignment for $($assignment.name)" Remove-AzPolicyAssignment -Id $assignment.id } } Remove-AzPolicySetDefinition -Id $init.Id -Force } } else { "Skipping AMA policy set removal. Use -RemoveAMAPolicySet to remove them." } #endregion #region Discovery if ($RemoveDiscovery -or $RemoveAll) { # Remove DCR associations # Remove DCRs "Removing discovery components." "Removing DCRs and associations." $query=@' insightsresources | where type == "microsoft.insights/datacollectionruleassociations" | extend resourceId=split(id,'/providers/Microsoft.Insights/')[0] | where isnotnull(properties.dataCollectionRuleId) | project rulename=split(properties.dataCollectionRuleId,"/")[8],resourceName=split(resourceId,"/")[8],resourceId, ruleId=properties.dataCollectionRuleId, name | where ruleId =~ '@ # Remove DCRs and associations $DCRs=Get-AzDataCollectionRule -ResourceGroupName $RG | where-object {$_.Tag.AdditionalProperties.MonitoringPackType -eq "Discovery"} -ErrorAction SilentlyContinue foreach ($DCR in $DCRs) { $searchQuery=$query + "'$($DCR.Id)'" $dcras=Search-AzGraph -Query $searchQuery -UseTenantScope foreach ($dcra in $dcras) { "Removing DCR association $($dcra.rulename) for $($dcra.resourceId)" Remove-AzDataCollectionRuleAssociation -TargetResourceId $dcra.resourceId -AssociationName $dcra.name } Remove-AzDataCollectionRule -ResourceGroupName $DCR.Id.Split('/')[4] -Name $DCR.Name } $pols=Get-AzPolicyDefinition | Where-Object {$_.Metadata.MonitoringPackType -eq "Discovery"} # retrive unique list of packs installed $packs=$pols.Metadata.MonitorStarterPacks | Select-Object -Unique # should be just discovery anyways in this case. "Found $($packs.count) packs with DCRs: $packs" # if ($RemoveTag) { # "Removing packs with tag $RemoveTag." # $pols=$pols | where-object {$_.Metadata.MonitorStarterPacks -eq $RemoveTag} # } foreach ($pack in $packs) { "Removing pack $pack." foreach ($pol in ($pols | Where-Object {$_.Metadata.MonitorStarterPacks -eq $pack}) ) { $remove=$true if ($confirmEachPack) { $confirm=Read-Host "Do you want to remove pack $($pol.Name)? (Y/N)" if ($confirm -eq 'N') { $remove=$false } else { $Remove=$true } } if ($remove) { "Removing policy $($pol.Id) and assignments for pack $($pol.Metadata.MonitorStarterPacks)" #$assignments=Get-AzPolicyAssignment -PolicyDefinitionId $pol.Id # Only works for the current subscription. Need to use resource graph. $query=@" policyresources | where type == "microsoft.authorization/policyassignments" | extend AssignmentDisplayName=properties.displayName,scope=properties.scope,PolicyId=tostring(properties.policyDefinitionId) | where PolicyId == '$($pol.Id)' "@ $assignments=Search-AzGraph -Query $query -UseTenantScope if ($assignments.count -ne 0) { "Removing assignments for $($pol.Id)" foreach ($assignment in $assignments) { # No need to remove role assignments with user defined managed identities. # $assignmentObjectId= Get-AzADServicePrincipal -Id $assignment.Identity.PrincipalId -ErrorAction SilentlyContinue # Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $assignmentObjectId.Id} | Remove-AzRoleAssignment # #$ras=Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $assignmentObjectId.Id} # -and $_. -eq $assignments.Identity.PrincipalId} | Remove-AzRoleAssignment "Removing assignment for $($assignment.name)" Remove-AzPolicyAssignment -Id $assignment.id } } "Removing policy definition for $($pol.Id)" Remove-AzPolicyDefinition -Id $pol.Id -Force } else { "Skipping pack $($pol.Name)" } } } $originalSub=(Get-AzContext).Subscription.Id Get-AzGallery -ResourceGroupName $RG | Where-Object {$_.Tags.MonitorStarterPacksComponents -ne $null} | ForEach-Object { "Finding apps..." $galleryApps=Get-AzGalleryApplication -GalleryName $_.Name -ResourceGroupName $RG "Found $($galleryApps.Count) apps." $gas=@() $gavs=@() foreach ($ga in $galleryApps) { $gas+=$ga.Id $gtemps=Get-AzGalleryApplicationVersion -GalleryName $_.Name -GalleryApplicationName $ga.Name -ResourceGroupName $RG if ($gtemps) {$gavs+=$gtemps.Id} } if ($gavs.Count -gt 0 -and $RemoveDiscoveryVMApps) { #need an Azure Resource Graph query to get all VMs with apps. $query=@" resources | where type == "microsoft.compute/virtualmachines" | where isnotempty(properties.applicationProfile.galleryApplications) | project id, name, resourceGroup, applicationProfile=properties.applicationProfile "@ $vmswithApps=Search-AzGraph -Query $query #$vmswithApps=Get-AzVM | Where-Object { $_.ApplicationProfile -ne $null} foreach ($VM in $vmswithApps) { remove-appversions -vm $vm -appstoremove $gavs } # switch back to the original subscription Set-AzContext -Subscription $originalSub } elseif (!($RemoveDiscoveryVMApps) -and $gavs.Count -gt 0) { "There may applicattions installed in the VMs. Manual removal may be required." } # then go ahead and remove applications and gallery once all VMs are clear. foreach ($gav in $gavs) { $gaName=$gav.Split("/")[10] $gavName=$gav.Split("/")[12] "Removing $gav from $gaName" Remove-AzGalleryApplicationVersion -GalleryName $_.Name -GalleryApplicationName $gaName -Name $gavName -ResourceGroupName $RG } foreach ($ga in $gas) { $gaName=$ga.Split("/")[10] "Removing $($ga)" Remove-AzGalleryApplication -GalleryName $_.Name -Name $gaName -ResourceGroupName $RG } "Removing gallery: $($_.Name)" Remove-AzGallery -Name $_.Name -ResourceGroupName $RG -Force } } #endregion #region Packs # Remove policy assignments and policies if ($RemovePacks -or $RemoveAll) { "Removing packs." # Gets all policies with the tag MonitorStarterPacks $pols=Get-AzPolicyDefinition | Where-Object {$_.Metadata.MonitorStarterPacks -ne $null -and $_.Metadata.MonitoringPackType -ne "Discovery"} # retrive unique list of packs installed $packs=$pols.Metadata.MonitorStarterPacks | Select-Object -Unique "Found $($packs.count) packs from policies: $packs" # if ($RemoveTag) { # "Removing packs with tag $RemoveTag." # $pols=$pols | where-object {$_.Metadata.MonitorStarterPacks -eq $RemoveTag} # } # Remove policy sets and assignments "Removing policy sets." $inits=Get-AzPolicySetDefinition | where-object {$_.Metadata.MonitorStarterPacks -ne $null -and $_.Metadata.MonitoringPackType -ne "Discovery"} foreach ($init in $inits) { "Removing policy set $($init.Id)" #$assignments=Get-AzPolicyAssignment -PolicyDefinitionId $init.Id $query=@" policyresources | where type == "microsoft.authorization/policyassignments" | extend AssignmentDisplayName=properties.displayName,scope=properties.scope,PolicyId=tostring(properties.policyDefinitionId) | where PolicyId == '$($init.Id)' "@ $assignments=Search-AzGraph -Query $query -UseTenantScope if ($assignments.count -ne 0) { "Removing assignments for $($pol.Id)" foreach ($assignment in $assignments) { "Removing assignment for $($assignment.name)" Remove-AzPolicyAssignment -Id $assignment.id } } Remove-AzPolicySetDefinition -Id $init.Id -Force } foreach ($pol in ($pols | Where-Object {$_.Metadata.MonitorStarterPacks -ne $null}) ) { $remove=$true $pack=$pol.Metadata.MonitorStarterPacks "Removing $pack pack." if ($confirmEachPack) { $confirm=Read-Host "Do you want to remove pack $($pol.Name)? (Y/N)" if ($confirm -eq 'N') { $remove=$false } else { $Remove=$true } } if ($remove) { "Removing policy $($pol.Id) and assignments for pack $pack" #$assignments=Get-AzPolicyAssignment -PolicyDefinitionId $pol.Id # Only works for the current subscription. Need to use resource graph. $query=@" policyresources | where type == "microsoft.authorization/policyassignments" | extend AssignmentDisplayName=properties.displayName,scope=properties.scope,PolicyId=tostring(properties.policyDefinitionId) | where PolicyId == '$($pol.Id)' "@ $assignments=Search-AzGraph -Query $query -UseTenantScope if ($assignments.count -ne 0) { "Removing assignments for $($pol.Id)" foreach ($assignment in $assignments) { # No need to remove role assignments with user defined managed identities. # $assignmentObjectId= Get-AzADServicePrincipal -Id $assignment.Identity.PrincipalId -ErrorAction SilentlyContinue # Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $assignmentObjectId.Id} | Remove-AzRoleAssignment # #$ras=Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $assignmentObjectId.Id} # -and $_. -eq $assignments.Identity.PrincipalId} | Remove-AzRoleAssignment "Removing assignment for $($assignment.name)" Remove-AzPolicyAssignment -Id $assignment.id } } "Removing policy definition for $($pol.Id)" Remove-AzPolicyDefinition -Id $pol.Id -Force } else { "Skipping pack $($pol.Name)" } } # If something remains, clear all dead assignments in the current subscription #Get-AzRoleAssignment -scope "/subscriptions/$((Get-AzContext).Subscription)" | where-object {$_.ObjectType -eq 'unknown'} | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)"} | Remove-AzRoleAssignment # remove DCR associations $dcrs=Get-AzDataCollectionRule -ResourceGroupName $RG | Where-Object {($_.tag | convertfrom-json).MonitorStarterPacks -ne $null} # retrive unique list of packs installed if ($dcrs) { $packs=($dcrs.tag | convertfrom-json).MonitorStarterPacks | select -Unique foreach ($pack in $packs) { "Removing pack $pack from DCRs." $query=@' insightsresources | where type == "microsoft.insights/datacollectionruleassociations" | extend resourceId=split(id,'/providers/Microsoft.Insights/')[0] | where isnotnull(properties.dataCollectionRuleId) | project rulename=split(properties.dataCollectionRuleId,"/")[8],resourceName=split(resourceId,"/")[8],resourceId, ruleId=properties.dataCollectionRuleId, name | where ruleId =~ '@ $DCRs=Get-AzDataCollectionRule -ResourceGroupName $RG | where-object {($_.Tag | convertfrom-json).MonitorStarterPacks -eq $pack} "Found $($DCRs.count) for $pack pack." foreach ($DCR in $DCRs) { "Working on $($DCR.name)" $searchQuery=$query + "'$($DCR.Id)'" "Looking for associations." ##$dcras=Search-AzGraph -Query $searchQuery -UseTenantScope $dcras=Get-AzDataCollectionRuleAssociation -DataCollectionRuleName $DCR.Name -ResourceGroupName $DCR.ResourceGroupName "Found $($dcras.count) associationsfor $($DCR.Name)." foreach ($dcra in $dcras) { #"Removing DCR association $($dcra.rulename) for $($dcra.resourceId)" "Removing DCR association $($dcra.Name) for $($dcra.Id)" $resourceId=$dcra.Id.toLower().Split('/providers/microsoft.insights/')[0] Remove-AzDataCollectionRuleAssociation -TargetResourceId $resourceId -AssociationName $dcra.name } "Removing DCR $($DCR.Name)" Remove-AzDataCollectionRule -ResourceGroupName $DCR.Id.Split('/')[4] -Name $DCR.Name } # remove DCRs #Get-AzDataCollectionRule -ResourceGroupName $RG | Remove-AzDataCollectionRule # remove Tags from VMs. # remove monitor extensions (optional) # remove alert rules } } else { "No DCRs found in $RG resource group." } "Removing alerts." $Alerts=Get-AzResource -ResourceType "microsoft.insights/scheduledqueryrules" -ResourceGroupName $RG | Where-Object {$_.Tags.MonitorStarterPacks -ne $null} # if ($RemoveTag) { # $Alerts=$Alerts | where-object {$_.Tags.MonitorStarterPacks -eq $RemoveTag} # } "Found $($Alerts.count) alerts for pack $pack" $Alerts | Remove-AzResource -Force -AsJob } else { "Skipping packs removal. Use -RemovePacks to remove packs." } #endregion #region Main Solution if ($RemoveMainSolution -or $RemoveAll) { "Removing main solution." "Removing workbook(s)." Get-AzResource -ResourceType 'Microsoft.Insights/workbooks' -ResourceGroupName $RG | Remove-AzResource -Force "Removing Logic app." Get-AzResource -ResourceType 'Microsoft.Logic/workflows' -ResourceGroupName $RG | Remove-AzResource -Force # remove function app roles and functiona app itself "Removing function app." $PrincipalId=(Get-AzWebApp -ResourceGroupName $RG).Identity.PrincipalId Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $PrincipalId} | Remove-AzRoleAssignment Get-AzResource -ResourceType 'Microsoft.Web/sites' -ResourceGroupName $RG | Remove-AzResource -Force # Remove web server (farm) "Removing web server." Get-AzResource -ResourceType 'Microsoft.Web/serverfarms' -ResourceGroupName $RG | Remove-AzResource -Force #remove deployment scripts "Removing deployment scripts." Get-azresource -ResourceType 'Microsoft.Resources/deploymentScripts' -ResourceGroupName $RG | Remove-AzResource -Force #remove app insights "Removing app insights." $appinsights=Get-AzApplicationInsights -ResourceGroupName $RG -ErrorAction SilentlyContinue if ($appinsights) { $appinsights | Remove-AzApplicationInsights -Confirm:$false } #remove app insights default alerts "Removing app insights default alerts." get-azresource -ResourceType 'microsoft.alertsmanagement/smartDetectorAlertRules' -ResourceGroupName $RG | Remove-AzResource -Force # Remove grafana "Removing grafana. This removes the grafana dashboard and the grafana resource. It takes a while to complete. Make sure it has been removed before running the script again." Get-AzResource -ResourceType 'Microsoft.Dashboard/grafana' -ResourceGroupName $RG | Remove-AzResource -Force -asJob #delete data collection endpoints "Removing data collection endpoints." get-azresource -ResourceType 'Microsoft.Insights/dataCollectionEndpoints' -ResourceGroupName $RG | Remove-AzResource -Force # Remove custom remediation role #Remove-AzRoleDefinition -Name 'Custom Role - Remediation Contributor' -Force # remove storage account # remove managed identities # Fetch existing managed identities. Name should be: $query=@" resources | where type =~ 'Microsoft.ManagedIdentity/userAssignedIdentities' | where tolower(resourceGroup) == '$($RG.toLower())' | project name "@ $managedIdentityNames=(Search-AzGraph -Query $query).name foreach ($MIName in $managedIdentityNames) { $MIResourceName=(get-azresource -ResourceGroupName $RG -ResourceType 'Microsoft.ManagedIdentity/userAssignedIdentities' -Name $MIName -ErrorAction SilentlyContinue).Name $MIObjectId=(Get-AzADServicePrincipal -DisplayName $MIName -ErrorAction SilentlyContinue).Id if ($MIObjectId) { Get-AzRoleAssignment | where-object {$_.Scope -eq "/subscriptions/$((Get-AzContext).Subscription)" -and $_.ObjectId -eq $MIObjectId} | Remove-AzRoleAssignment } if ($MIResourceName) { get-azresource -ResourceType 'Microsoft.ManagedIdentity/userAssignedIdentities' -Name $MIResourceName -ResourceGroupName $RG | Remove-AzResource -Force } } # Remove Key Vault "Removing key vault." Get-AzResource -ResourceType 'Microsoft.KeyVault/vaults' -ResourceGroupName $RG | Remove-AzResource -Force # Remove api connection for logic app "Removing api connection for logic app." Get-AzResource -ResourceType 'Microsoft.Web/connections' -ResourceGroupName $RG | Remove-AzResource -Force #remove resource #do the same for the function MI. # remove log analytics workspace } else { "Skipping main solution removal. Use -RemoveMainSolution to remove it" } if ($RemoveStorage) { "Removing storage account." Get-AzResource -ResourceType 'Microsoft.Storage/storageAccounts' -ResourceGroupName $RG | Remove-AzResource -Confirm } else { "Skipping storage account removal. Use -RemoveStorage to remove it." } if ($RemoveLAW) { "Removing log analytics workspace." $LAWS=Get-AzResource -ResourceType 'Microsoft.OperationalInsights/workspaces' -ResourceGroupName $RG foreach ($LAW in $LAWS) { Remove-AzOperationalInsightsWorkspace -ResourceGroupName $RG -Name $LAW.Name -ForceDelete -Confirm } } else { "Skipping log analytics workspace removal. Use -RemoveLAW to remove it." } if ($RemoveTag) { " Removing tag $RemoveTag from resources." " Not implemented yet." }