...
1# Add 'AzsdkResourceType' member to outputs since actual output types have changed over the years.
2
3#Requires -Modules @{ModuleName='Az.KeyVault'; ModuleVersion='3.4.1'}
4
5function Get-PurgeableGroupResources {
6 param (
7 [Parameter(Mandatory=$true, Position=0)]
8 [string] $ResourceGroupName
9 )
10
11 $purgeableResources = @()
12
13 # Discover Managed HSMs first since they are a premium resource.
14 Write-Verbose "Retrieving deleted Managed HSMs from resource group $ResourceGroupName"
15
16 # Get any Managed HSMs in the resource group, for which soft delete cannot be disabled.
17 $deletedHsms = @(Get-AzKeyVaultManagedHsm -ResourceGroupName $ResourceGroupName -ErrorAction Ignore `
18 | Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Managed HSM' -PassThru `
19 | Add-Member -MemberType AliasProperty -Name AzsdkName -Value VaultName -PassThru)
20
21 if ($deletedHsms) {
22 Write-Verbose "Found $($deletedHsms.Count) deleted Managed HSMs to potentially purge."
23 $purgeableResources += $deletedHsms
24 }
25
26 Write-Verbose "Retrieving deleted Key Vaults from resource group $ResourceGroupName"
27
28 # Get any Key Vaults that will be deleted so they can be purged later if soft delete is enabled.
29 $deletedKeyVaults = @(Get-AzKeyVault -ResourceGroupName $ResourceGroupName -ErrorAction Ignore | ForEach-Object {
30 # Enumerating vaults from a resource group does not return all properties we required.
31 Get-AzKeyVault -VaultName $_.VaultName -ErrorAction Ignore | Where-Object { $_.EnableSoftDelete } `
32 | Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Key Vault' -PassThru `
33 | Add-Member -MemberType AliasProperty -Name AzsdkName -Value VaultName -PassThru
34 })
35
36 if ($deletedKeyVaults) {
37 Write-Verbose "Found $($deletedKeyVaults.Count) deleted Key Vaults to potentially purge."
38 $purgeableResources += $deletedKeyVaults
39 }
40
41 return $purgeableResources
42}
43
44function Get-PurgeableResources {
45 $purgeableResources = @()
46 $subscriptionId = (Get-AzContext).Subscription.Id
47
48 # Discover Managed HSMs first since they are a premium resource.
49 Write-Verbose "Retrieving deleted Managed HSMs from subscription $subscriptionId"
50
51 # Get deleted Managed HSMs for the current subscription.
52 $response = Invoke-AzRestMethod -Method GET -Path "/subscriptions/$subscriptionId/providers/Microsoft.KeyVault/deletedManagedHSMs?api-version=2021-04-01-preview" -ErrorAction Ignore
53 if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300 -and $response.Content) {
54 $content = $response.Content | ConvertFrom-Json
55
56 $deletedHsms = @()
57 foreach ($r in $content.value) {
58 $deletedHsms += [pscustomobject] @{
59 AzsdkResourceType = 'Managed HSM'
60 AzsdkName = $r.name
61 Id = $r.id
62 Name = $r.name
63 Location = $r.properties.location
64 DeletionDate = $r.properties.deletionDate -as [DateTime]
65 ScheduledPurgeDate = $r.properties.scheduledPurgeDate -as [DateTime]
66 EnablePurgeProtection = $r.properties.purgeProtectionEnabled
67 }
68 }
69
70 if ($deletedHsms) {
71 Write-Verbose "Found $($deletedHsms.Count) deleted Managed HSMs to potentially purge."
72 $purgeableResources += $deletedHsms
73 }
74 }
75
76 Write-Verbose "Retrieving deleted Key Vaults from subscription $subscriptionId"
77
78 # Get deleted Key Vaults for the current subscription.
79 $deletedKeyVaults = @(Get-AzKeyVault -InRemovedState `
80 | Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Key Vault' -PassThru `
81 | Add-Member -MemberType AliasProperty -Name AzsdkName -Value VaultName -PassThru)
82
83 if ($deletedKeyVaults) {
84 Write-Verbose "Found $($deletedKeyVaults.Count) deleted Key Vaults to potentially purge."
85 $purgeableResources += $deletedKeyVaults
86 }
87
88 return $purgeableResources
89}
90
91# A filter differs from a function by teating body as -process {} instead of -end {}.
92# This allows you to pipe a collection and process each item in the collection.
93filter Remove-PurgeableResources {
94 param (
95 [Parameter(Position=0, ValueFromPipeline=$true)]
96 [object[]] $Resource,
97
98 [Parameter()]
99 [ValidateRange(1, [int]::MaxValue)]
100 [int] $Timeout = 30,
101
102 [Parameter()]
103 [switch] $PassThru
104 )
105
106 if (!$Resource) {
107 return
108 }
109
110 $subscriptionId = (Get-AzContext).Subscription.Id
111
112 foreach ($r in $Resource) {
113 Log "Attempting to purge $($r.AzsdkResourceType) '$($r.AzsdkName)'"
114 switch ($r.AzsdkResourceType) {
115 'Key Vault' {
116 if ($r.EnablePurgeProtection) {
117 # We will try anyway but will ignore errors.
118 Write-Warning "Key Vault '$($r.VaultName)' has purge protection enabled and may not be purged for $($r.SoftDeleteRetentionInDays) days"
119 }
120
121 # Use `-AsJob` to start a lightweight, cancellable job and pass to `Wait-PurgeableResoruceJob` for consistent behavior.
122 Remove-AzKeyVault -VaultName $r.VaultName -Location $r.Location -InRemovedState -Force -ErrorAction Continue -AsJob `
123 | Wait-PurgeableResourceJob -Resource $r -Timeout $Timeout -PassThru:$PassThru
124 }
125
126 'Managed HSM' {
127 if ($r.EnablePurgeProtection) {
128 # We will try anyway but will ignore errors.
129 Write-Warning "Managed HSM '$($r.Name)' has purge protection enabled and may not be purged for $($r.SoftDeleteRetentionInDays) days"
130 }
131
132 # Use `GetNewClosure()` on the `-Action` ScriptBlock to make sure variables are captured.
133 Invoke-AzRestMethod -Method POST -Path "/subscriptions/$subscriptionId/providers/Microsoft.KeyVault/locations/$($r.Location)/deletedManagedHSMs/$($r.Name)/purge?api-version=2021-04-01-preview" -ErrorAction Ignore -AsJob `
134 | Wait-PurgeableResourceJob -Resource $r -Timeout $Timeout -PassThru:$PassThru -Action {
135 param ( $response )
136 if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) {
137 Write-Warning "Successfully requested that Managed HSM '$($r.Name)' be purged, but may take a few minutes before it is actually purged."
138 } elseif ($response.Content) {
139 $content = $response.Content | ConvertFrom-Json
140 if ($content.error) {
141 $err = $content.error
142 Write-Warning "Failed to deleted Managed HSM '$($r.Name)': ($($err.code)) $($err.message)"
143 }
144 }
145 }.GetNewClosure()
146 }
147
148 default {
149 Write-Warning "Cannot purge $($r.AzsdkResourceType) '$($r.AzsdkName)'. Add support to https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/scripts/Helpers/Resource-Helpers.ps1."
150 }
151 }
152 }
153}
154
155# The Log function can be overridden by the sourcing script.
156function Log($Message) {
157 Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message)
158}
159
160function Wait-PurgeableResourceJob {
161 param (
162 [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
163 $Job,
164
165 # The resource is used for logging and to return if `-PassThru` is specified
166 # so we can easily see all resources that may be in a bad state when the script has completed.
167 [Parameter(Mandatory=$true)]
168 $Resource,
169
170 # Optional ScriptBlock should define params corresponding to the associated job's `Output` property.
171 [Parameter()]
172 [scriptblock] $Action,
173
174 [Parameter()]
175 [ValidateRange(1, [int]::MaxValue)]
176 [int] $Timeout = 30,
177
178 [Parameter()]
179 [switch] $PassThru
180 )
181
182 $null = Wait-Job -Job $Job -Timeout $Timeout
183
184 if ($Job.State -eq 'Completed' -or $Job.State -eq 'Failed') {
185 $result = Receive-Job -Job $Job -ErrorAction Continue
186
187 if ($Action) {
188 $null = $Action.Invoke($result)
189 }
190 } else {
191 Write-Warning "Timed out waiting to purge $($Resource.AzsdkResourceType) '$($Resource.AzsdkName)'. Cancelling job."
192 $Job.Cancel()
193
194 if ($PassThru) {
195 $Resource
196 }
197 }
198}
View as plain text