@@ -13,12 +13,12 @@ function Start-BackupRetentionCleanup {
1313 $ConfigTable = Get-CippTable - tablename Config
1414 $Filter = " PartitionKey eq 'BackupRetention' and RowKey eq 'Settings'"
1515 $RetentionSettings = Get-CIPPAzDataTableEntity @ConfigTable - Filter $Filter
16-
16+
1717 # Default to 30 days if not set
18- $RetentionDays = if ($RetentionSettings.RetentionDays ) {
19- [int ]$RetentionSettings.RetentionDays
20- } else {
21- 30
18+ $RetentionDays = if ($RetentionSettings.RetentionDays ) {
19+ [int ]$RetentionSettings.RetentionDays
20+ } else {
21+ 30
2222 }
2323
2424 # Ensure minimum retention of 7 days
@@ -27,24 +27,67 @@ function Start-BackupRetentionCleanup {
2727 }
2828
2929 Write-Host " Starting backup cleanup with retention of $RetentionDays days"
30-
30+
3131 # Calculate cutoff date
3232 $CutoffDate = (Get-Date ).AddDays(- $RetentionDays ).ToUniversalTime().ToString(' yyyy-MM-ddTHH:mm:ssZ' )
33-
33+
3434 $DeletedCounts = [System.Collections.Generic.List [int ]]::new()
3535
3636 # Clean up CIPP Backups
3737 if ($PSCmdlet.ShouldProcess (' CIPPBackup' , ' Cleaning up old backups' )) {
3838 $CIPPBackupTable = Get-CippTable - tablename ' CIPPBackup'
39- $Filter = " PartitionKey eq 'CIPPBackup' and Timestamp lt datetime'$CutoffDate '"
40-
41- $OldCIPPBackups = Get-AzDataTableEntity @CIPPBackupTable - Filter $Filter - Property @ (' PartitionKey' , ' RowKey' , ' ETag' )
42-
43- if ($OldCIPPBackups ) {
44- Write-Host " Found $ ( $OldCIPPBackups.Count ) old CIPP backups to delete"
45- Remove-AzDataTableEntity @CIPPBackupTable - Entity $OldCIPPBackups - Force
46- $DeletedCounts.Add ($OldCIPPBackups.Count )
47- Write-LogMessage - API ' BackupRetentionCleanup' - message " Deleted $ ( $OldCIPPBackups.Count ) old CIPP backups" - Sev ' Info'
39+ $CutoffFilter = " PartitionKey eq 'CIPPBackup' and Timestamp lt datetime'$CutoffDate '"
40+
41+ # Delete blob files
42+ $BlobFilter = " $CutoffFilter and BackupIsBlob eq true"
43+ $BlobBackups = Get-AzDataTableEntity @CIPPBackupTable - Filter $BlobFilter - Property @ (' PartitionKey' , ' RowKey' , ' Backup' )
44+
45+ $BlobDeletedCount = 0
46+ if ($BlobBackups ) {
47+ foreach ($Backup in $BlobBackups ) {
48+ if ($Backup.Backup ) {
49+ try {
50+ $BlobPath = $Backup.Backup
51+ # Extract container/blob path from URL
52+ if ($BlobPath -like ' *:10000/*' ) {
53+ # Azurite format: http://host:10000/devstoreaccount1/container/blob
54+ $parts = $BlobPath -split ' :10000/'
55+ if ($parts.Count -gt 1 ) {
56+ # Remove account name to get container/blob
57+ $BlobPath = ($parts [1 ] -split ' /' , 2 )[-1 ]
58+ }
59+ } elseif ($BlobPath -like ' *blob.core.windows.net/*' ) {
60+ # Azure Storage format: https://account.blob.core.windows.net/container/blob
61+ $BlobPath = ($BlobPath -split ' .blob.core.windows.net/' , 2 )[-1 ]
62+ }
63+ $null = New-CIPPAzStorageRequest - Service ' blob' - Resource $BlobPath - Method ' DELETE' - ConnectionString $ConnectionString
64+ $BlobDeletedCount ++
65+ Write-Host " Deleted blob: $BlobPath "
66+ } catch {
67+ Write-LogMessage - API ' BackupRetentionCleanup' - message " Failed to delete blob $ ( $Backup.Backup ) : $ ( $_.Exception.Message ) " - Sev ' Warning'
68+ }
69+ }
70+ }
71+ # Delete blob table entities
72+ Remove-AzDataTableEntity @CIPPBackupTable - Entity $BlobBackups - Force
73+ }
74+
75+ # Delete table-only backups (no blobs)
76+ # Fetch all old entries and filter out blob entries client-side (null check is unreliable in filters)
77+ $AllOldBackups = Get-AzDataTableEntity @CIPPBackupTable - Filter $CutoffFilter - Property @ (' PartitionKey' , ' RowKey' , ' ETag' , ' BackupIsBlob' )
78+ $TableBackups = $AllOldBackups | Where-Object { $_.BackupIsBlob -ne $true }
79+
80+ $TableDeletedCount = 0
81+ if ($TableBackups ) {
82+ Remove-AzDataTableEntity @CIPPBackupTable - Entity $TableBackups - Force
83+ $TableDeletedCount = ($TableBackups | Measure-Object ).Count
84+ }
85+
86+ $TotalDeleted = $BlobDeletedCount + $TableDeletedCount
87+ if ($TotalDeleted -gt 0 ) {
88+ $DeletedCounts.Add ($TotalDeleted )
89+ Write-LogMessage - API ' BackupRetentionCleanup' - message " Deleted $TotalDeleted old CIPP backups ($BlobDeletedCount blobs, $TableDeletedCount table entries)" - Sev ' Info'
90+ Write-Host " Deleted $TotalDeleted old CIPP backups"
4891 } else {
4992 Write-Host ' No old CIPP backups found'
5093 }
@@ -53,23 +96,66 @@ function Start-BackupRetentionCleanup {
5396 # Clean up Scheduled/Tenant Backups
5497 if ($PSCmdlet.ShouldProcess (' ScheduledBackup' , ' Cleaning up old backups' )) {
5598 $ScheduledBackupTable = Get-CippTable - tablename ' ScheduledBackup'
56- $Filter = " PartitionKey eq 'ScheduledBackup' and Timestamp lt datetime'$CutoffDate '"
57-
58- $OldScheduledBackups = Get-AzDataTableEntity @ScheduledBackupTable - Filter $Filter - Property @ (' PartitionKey' , ' RowKey' , ' ETag' )
59-
60- if ($OldScheduledBackups ) {
61- Write-Host " Found $ ( $OldScheduledBackups.Count ) old tenant backups to delete"
62- Remove-AzDataTableEntity @ScheduledBackupTable - Entity $OldScheduledBackups - Force
63- $DeletedCounts.Add ($OldScheduledBackups.Count )
64- Write-LogMessage - API ' BackupRetentionCleanup' - message " Deleted $ ( $OldScheduledBackups.Count ) old tenant backups" - Sev ' Info'
99+ $CutoffFilter = " PartitionKey eq 'ScheduledBackup' and Timestamp lt datetime'$CutoffDate '"
100+
101+ # Delete blob files
102+ $BlobFilter = " $CutoffFilter and BackupIsBlob eq true"
103+ $BlobBackups = Get-AzDataTableEntity @ScheduledBackupTable - Filter $BlobFilter - Property @ (' PartitionKey' , ' RowKey' , ' Backup' )
104+
105+ $BlobDeletedCount = 0
106+ if ($BlobBackups ) {
107+ foreach ($Backup in $BlobBackups ) {
108+ if ($Backup.Backup ) {
109+ try {
110+ $BlobPath = $Backup.Backup
111+ # Extract container/blob path from URL
112+ if ($BlobPath -like ' *:10000/*' ) {
113+ # Azurite format: http://host:10000/devstoreaccount1/container/blob
114+ $parts = $BlobPath -split ' :10000/'
115+ if ($parts.Count -gt 1 ) {
116+ # Remove account name to get container/blob
117+ $BlobPath = ($parts [1 ] -split ' /' , 2 )[-1 ]
118+ }
119+ } elseif ($BlobPath -like ' *blob.core.windows.net/*' ) {
120+ # Azure Storage format: https://account.blob.core.windows.net/container/blob
121+ $BlobPath = ($BlobPath -split ' .blob.core.windows.net/' , 2 )[-1 ]
122+ }
123+ $null = New-CIPPAzStorageRequest - Service ' blob' - Resource $BlobPath - Method ' DELETE' - ConnectionString $ConnectionString
124+ $BlobDeletedCount ++
125+ Write-Host " Deleted blob: $BlobPath "
126+ } catch {
127+ Write-LogMessage - API ' BackupRetentionCleanup' - message " Failed to delete blob $ ( $Backup.Backup ) : $ ( $_.Exception.Message ) " - Sev ' Warning'
128+ }
129+ }
130+ }
131+ # Delete blob table entities
132+ Remove-AzDataTableEntity @ScheduledBackupTable - Entity $BlobBackups - Force
133+ }
134+
135+ # Delete table-only backups (no blobs)
136+ # Fetch all old entries and filter out blob entries client-side (null check is unreliable in filters)
137+ $AllOldBackups = Get-AzDataTableEntity @ScheduledBackupTable - Filter $CutoffFilter - Property @ (' PartitionKey' , ' RowKey' , ' ETag' , ' BackupIsBlob' )
138+ $TableBackups = $AllOldBackups | Where-Object { $_.BackupIsBlob -ne $true }
139+
140+ $TableDeletedCount = 0
141+ if ($TableBackups ) {
142+ Remove-AzDataTableEntity @ScheduledBackupTable - Entity $TableBackups - Force
143+ $TableDeletedCount = ($TableBackups | Measure-Object ).Count
144+ }
145+
146+ $TotalDeleted = $BlobDeletedCount + $TableDeletedCount
147+ if ($TotalDeleted -gt 0 ) {
148+ $DeletedCounts.Add ($TotalDeleted )
149+ Write-LogMessage - API ' BackupRetentionCleanup' - message " Deleted $TotalDeleted old tenant backups ($BlobDeletedCount blobs, $TableDeletedCount table entries)" - Sev ' Info'
150+ Write-Host " Deleted $TotalDeleted old tenant backups"
65151 } else {
66152 Write-Host ' No old tenant backups found'
67153 }
68154 }
69155
70156 $TotalDeleted = ($DeletedCounts | Measure-Object - Sum).Sum
71157 Write-LogMessage - API ' BackupRetentionCleanup' - message " Backup cleanup completed. Total backups deleted: $TotalDeleted (retention: $RetentionDays days)" - Sev ' Info'
72-
158+
73159 } catch {
74160 $ErrorMessage = Get-CippException - Exception $_
75161 Write-LogMessage - API ' BackupRetentionCleanup' - message " Failed to run backup cleanup: $ ( $ErrorMessage.NormalizedError ) " - Sev ' Error' - LogData $ErrorMessage
0 commit comments