@@ -434,11 +434,17 @@ function RemoveStorageAccount($Account) {
434434
435435 try {
436436 foreach ($container in $containers ) {
437- $blobs = $container | Get-AzStorageBlob
438- foreach ($blob in $blobs ) {
439- $shouldDelete = EnableBlobDeletion - Blob $blob - Container $container - StorageAccountName $Account.StorageAccountName - ResourceGroupName $Account.ResourceGroupName
440- if ($shouldDelete ) {
441- $deleteNow += $blob
437+ # VLW containers need version-aware cleanup: soft-delete causes deleted blobs to linger
438+ # as non-current versions that block container deletion. See Remove-VlwContainerBlobs.
439+ if (($container | Get-Member ' BlobContainerProperties' ) -and $container.BlobContainerProperties.HasImmutableStorageWithVersioning ) {
440+ Remove-VlwContainerBlobs - Container $container - StorageAccountName $Account.StorageAccountName - ResourceGroupName $Account.ResourceGroupName
441+ } else {
442+ $blobs = $container | Get-AzStorageBlob
443+ foreach ($blob in $blobs ) {
444+ $shouldDelete = EnableBlobDeletion - Blob $blob - Container $container - StorageAccountName $Account.StorageAccountName - ResourceGroupName $Account.ResourceGroupName
445+ if ($shouldDelete ) {
446+ $deleteNow += $blob
447+ }
442448 }
443449 }
444450 }
@@ -524,6 +530,41 @@ function EnableBlobDeletion($Blob, $Container, $StorageAccountName, $ResourceGro
524530 return $forceBlobDeletion
525531}
526532
533+ # In VLW (Versioned-Level WORM) containers with soft-delete enabled, deleting a blob creates a
534+ # non-current version instead of truly removing it. A standard Get-AzStorageBlob listing can't
535+ # see these leftovers, but they still block container deletion (409 Conflict on the management
536+ # plane DELETE). Listing with -IncludeVersion -IncludeDeleted makes them visible so we can clear
537+ # immutability policies / legal holds and delete each version individually. Multiple passes handle
538+ # new non-current versions that surface after each round of deletions.
539+ function Remove-VlwContainerBlobs ($Container , $StorageAccountName , $ResourceGroupName ) {
540+ Write-Host " Cleaning VLW container '$ ( $Container.Name ) ' versions and soft-deleted blobs in account '$StorageAccountName ', group: $ResourceGroupName "
541+
542+ for ($round = 0 ; $round -lt 5 ; $round ++ ) {
543+ $found = $false
544+ $blobs = @ ($Container | Get-AzStorageBlob - IncludeVersion - IncludeDeleted - ErrorAction SilentlyContinue)
545+
546+ foreach ($blob in $blobs ) {
547+ $found = $true
548+
549+ # Unconditionally clear legal holds and immutability policies. Errors are expected for
550+ # soft-deleted blobs or blobs that don't have these set.
551+ try { $blob | Set-AzStorageBlobLegalHold - DisableLegalHold | Out-Null } catch { }
552+ try { $blob | Remove-AzStorageBlobImmutabilityPolicy | Out-Null } catch { }
553+ try {
554+ $blob | Remove-AzStorageBlob - Force
555+ } catch {
556+ # Deleting the current version by version ID returns 403
557+ # (OperationNotAllowedOnRootBlob); fall back to base blob deletion.
558+ try {
559+ Remove-AzStorageBlob - Container $Container.Name - Blob $blob.Name - Context $Container.Context - Force
560+ } catch { }
561+ }
562+ }
563+
564+ if (-not $found ) { break }
565+ }
566+ }
567+
527568function DoesSubnetOverlap ([string ]$ipOrCidr , [string ]$overlapIp ) {
528569 [System.Net.IPAddress ]$overlapIpAddress = $overlapIp
529570 $parsed = $ipOrCidr -split ' /'
@@ -543,4 +584,4 @@ function DoesSubnetOverlap([string]$ipOrCidr, [string]$overlapIp) {
543584 }
544585
545586 return $baseIp.Address -eq ($overlapIpAddress.Address -band ([System.Net.IPAddress ]$mask ).Address)
546- }
587+ }
0 commit comments