Exchange cleanup improvements (#3551)
Exchange cleanup improvements: * Explicitly clean-up folders in DeletedItems - Empty folder does not seem to work reliably * Cleanup any folders in DeletedItems - helps get rid of random manual deletions * Harden paging - issues where if no items were deleted initial page, we'd never make progress * More focused search on `msgfolderroot` --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [x] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
6fd453a699
commit
aa271fe09b
@ -185,77 +185,109 @@ function Get-FoldersToPurge {
|
|||||||
[string[]]$FolderNamePurgeList = @(),
|
[string[]]$FolderNamePurgeList = @(),
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Purge folders with these prefixes")]
|
[Parameter(Mandatory = $False, HelpMessage = "Purge folders with these prefixes")]
|
||||||
[string[]]$FolderPrefixPurgeList = @()
|
[string[]]$FolderPrefixPurgeList = @(),
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $False, HelpMessage = "Perform shallow traversal only")]
|
||||||
|
[bool]$PurgeTraversalShallow = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
$foldersToDelete = @()
|
Write-Host "`nLooking for folders under well-known folder: $WellKnownRoot matching folders: $FolderNamePurgeList or prefixes: $FolderPrefixPurgeList for user: $User"
|
||||||
|
|
||||||
# SOAP message for getting the folders
|
$foldersToDelete = @()
|
||||||
$body = @"
|
$traversal = "Deep"
|
||||||
<FindFolder Traversal="Deep" xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
|
if ($PurgeTraversalShallow) {
|
||||||
|
$traversal = "Shallow"
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = 0
|
||||||
|
$moreToList = $true
|
||||||
|
|
||||||
|
# get all folder pages
|
||||||
|
while ($moreToList) {
|
||||||
|
# SOAP message for getting the folders
|
||||||
|
$body = @"
|
||||||
|
<FindFolder Traversal="$traversal" xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
|
||||||
<FolderShape>
|
<FolderShape>
|
||||||
<t:BaseShape>Default</t:BaseShape>
|
<t:BaseShape>Default</t:BaseShape>
|
||||||
<t:AdditionalProperties>
|
<t:AdditionalProperties>
|
||||||
<t:ExtendedFieldURI PropertyTag="0x3007" PropertyType="SystemTime"/>
|
<t:ExtendedFieldURI PropertyTag="0x3007" PropertyType="SystemTime"/>
|
||||||
</t:AdditionalProperties>
|
</t:AdditionalProperties>
|
||||||
</FolderShape>
|
</FolderShape>
|
||||||
|
<m:IndexedPageFolderView MaxEntriesReturned="1000" Offset="$offset" BasePoint="Beginning" />
|
||||||
<ParentFolderIds>
|
<ParentFolderIds>
|
||||||
<t:DistinguishedFolderId Id="$WellKnownRoot"/>
|
<t:DistinguishedFolderId Id="$WellKnownRoot"/>
|
||||||
</ParentFolderIds>
|
</ParentFolderIds>
|
||||||
</FindFolder>
|
</FindFolder>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
Write-Host "`nLooking for folders under well-known folder: $WellKnownRoot matching folders: $FolderNamePurgeList or prefixes: $FolderPrefixPurgeList for user: $User"
|
try {
|
||||||
$getFolderIdMsg = Initialize-SOAPMessage -User $User -Body $body
|
Write-Host "`nRetrieving folders starting from offset: $offset"
|
||||||
$response = Invoke-SOAPRequest -Token $Token -Message $getFolderIdMsg
|
|
||||||
|
|
||||||
# Get the folders from the response
|
$getFolderIdMsg = Initialize-SOAPMessage -User $User -Body $body
|
||||||
$folders = $response | Select-Xml -XPath "//t:Folders/*" -Namespace @{t = "http://schemas.microsoft.com/exchange/services/2006/types" } |
|
$response = Invoke-SOAPRequest -Token $Token -Message $getFolderIdMsg
|
||||||
Select-Object -ExpandProperty Node
|
|
||||||
|
|
||||||
# Are there more folders to list
|
# Are there more folders to list
|
||||||
$rootFolder = $response | Select-Xml -XPath "//m:RootFolder" -Namespace @{m = "http://schemas.microsoft.com/exchange/services/2006/messages" } |
|
$rootFolder = $response | Select-Xml -XPath "//m:RootFolder" -Namespace @{m = "http://schemas.microsoft.com/exchange/services/2006/messages" } |
|
||||||
Select-Object -ExpandProperty Node
|
Select-Object -ExpandProperty Node
|
||||||
$moreToList = ![System.Convert]::ToBoolean($rootFolder.IncludesLastItemInRange)
|
$moreToList = ![System.Convert]::ToBoolean($rootFolder.IncludesLastItemInRange)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Error retrieving folders"
|
||||||
|
|
||||||
# Loop through folders
|
Write-Host $response.OuterXml
|
||||||
foreach ($folder in $folders) {
|
Exit
|
||||||
$folderName = $folder.DisplayName
|
}
|
||||||
$folderCreateTime = $folder.ExtendedProperty
|
|
||||||
| Where-Object { $_.ExtendedFieldURI.PropertyTag -eq "0x3007" }
|
|
||||||
| Select-Object -ExpandProperty Value
|
|
||||||
| Get-Date
|
|
||||||
|
|
||||||
if ($FolderNamePurgeList.count -gt 0) {
|
# Get the folders from the response
|
||||||
$IsNameMatchParams = @{
|
$folders = $response | Select-Xml -XPath "//t:Folders/*" -Namespace @{t = "http://schemas.microsoft.com/exchange/services/2006/types" } |
|
||||||
'FolderName' = $folderName;
|
Select-Object -ExpandProperty Node
|
||||||
'FolderNamePurgeList' = $FolderNamePurgeList
|
|
||||||
|
# Loop through folders
|
||||||
|
foreach ($folder in $folders) {
|
||||||
|
$folderName = $folder.DisplayName
|
||||||
|
$folderCreateTime = $folder.ExtendedProperty
|
||||||
|
| Where-Object { $_.ExtendedFieldURI.PropertyTag -eq "0x3007" }
|
||||||
|
| Select-Object -ExpandProperty Value
|
||||||
|
| Get-Date
|
||||||
|
|
||||||
|
if ($FolderNamePurgeList.count -gt 0) {
|
||||||
|
$IsNameMatchParams = @{
|
||||||
|
'FolderName' = $folderName;
|
||||||
|
'FolderNamePurgeList' = $FolderNamePurgeList
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((IsNameMatch @IsNameMatchParams)) {
|
||||||
|
Write-Host "• Found name match: $folderName ($folderCreateTime)"
|
||||||
|
$foldersToDelete += $folder
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((IsNameMatch @IsNameMatchParams)) {
|
if ($FolderPrefixPurgeList.count -gt 0) {
|
||||||
Write-Host "• Found name match: $folderName ($folderCreateTime)"
|
$IsPrefixAndAgeMatchParams = @{
|
||||||
$foldersToDelete += $folder
|
'FolderName' = $folderName;
|
||||||
continue
|
'FolderCreateTime' = $folderCreateTime;
|
||||||
|
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
||||||
|
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((IsPrefixAndAgeMatch @IsPrefixAndAgeMatchParams)) {
|
||||||
|
Write-Host "• Found prefix match: $folderName ($folderCreateTime)"
|
||||||
|
$foldersToDelete += $folder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($FolderPrefixPurgeList.count -gt 0) {
|
if (!$moreToList -or $null -eq $folders) {
|
||||||
$IsPrefixAndAgeMatchParams = @{
|
Write-Host "Retrieved all folders."
|
||||||
'FolderName' = $folderName;
|
}
|
||||||
'FolderCreateTime' = $folderCreateTime;
|
else {
|
||||||
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
$offset += $folders.count
|
||||||
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((IsPrefixAndAgeMatch @IsPrefixAndAgeMatchParams)) {
|
|
||||||
Write-Host "• Found prefix match: $folderName ($folderCreateTime)"
|
|
||||||
$foldersToDelete += $folder
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# powershel does not do well when returning empty arrays
|
# powershel does not do well when returning empty arrays
|
||||||
return $foldersToDelete, $moreToList
|
return , $foldersToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
function Empty-Folder {
|
function Empty-Folder {
|
||||||
@ -355,7 +387,10 @@ function Purge-Folders {
|
|||||||
[string[]]$FolderPrefixPurgeList = @(),
|
[string[]]$FolderPrefixPurgeList = @(),
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Purge folders before this date time (UTC)")]
|
[Parameter(Mandatory = $False, HelpMessage = "Purge folders before this date time (UTC)")]
|
||||||
[datetime]$PurgeBeforeTimestamp
|
[datetime]$PurgeBeforeTimestamp,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $False, HelpMessage = "Perform shallow traversal only")]
|
||||||
|
[bool]$PurgeTraversalShallow = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (($FolderNamePurgeList.count -eq 0) -and
|
if (($FolderNamePurgeList.count -eq 0) -and
|
||||||
@ -364,9 +399,6 @@ function Purge-Folders {
|
|||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "`nPurging CI-produced folders..."
|
|
||||||
Write-Host "--------------------------------"
|
|
||||||
|
|
||||||
if ($FolderNamePurgeList.count -gt 0) {
|
if ($FolderNamePurgeList.count -gt 0) {
|
||||||
Write-Host "Folders with names: $FolderNamePurgeList"
|
Write-Host "Folders with names: $FolderNamePurgeList"
|
||||||
}
|
}
|
||||||
@ -382,30 +414,27 @@ function Purge-Folders {
|
|||||||
'WellKnownRoot' = $WellKnownRoot;
|
'WellKnownRoot' = $WellKnownRoot;
|
||||||
'FolderNamePurgeList' = $FolderNamePurgeList;
|
'FolderNamePurgeList' = $FolderNamePurgeList;
|
||||||
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
||||||
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp
|
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp;
|
||||||
|
'PurgeTraversalShallow' = $PurgeTraversalShallow
|
||||||
}
|
}
|
||||||
|
|
||||||
$moreToList = $True
|
$foldersToDelete = Get-FoldersToPurge @foldersToDeleteParams
|
||||||
# only get max of 1000 results so we may need to iterate over eligible folders
|
$foldersToDeleteCount = $foldersToDelete.count
|
||||||
while ($moreToList) {
|
$foldersToDeleteIds = @()
|
||||||
$foldersToDelete, $moreToList = Get-FoldersToPurge @foldersToDeleteParams
|
$folderNames = @()
|
||||||
$foldersToDeleteCount = $foldersToDelete.count
|
|
||||||
$foldersToDeleteIds = @()
|
|
||||||
$folderNames = @()
|
|
||||||
|
|
||||||
if ($foldersToDeleteCount -eq 0) {
|
if ($foldersToDeleteCount -eq 0) {
|
||||||
Write-Host "`nNo folders to purge matching the criteria"
|
Write-Host "`nNo folders to purge matching the criteria"
|
||||||
break
|
return
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($folder in $foldersToDelete) {
|
|
||||||
$foldersToDeleteIds += $folder.FolderId.Id
|
|
||||||
$folderNames += $folder.DisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
Empty-Folder -FolderIdList $foldersToDeleteIds -FolderNameList $folderNames
|
|
||||||
Delete-Folder -FolderIdList $foldersToDeleteIds -FolderNameList $folderNames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($folder in $foldersToDelete) {
|
||||||
|
$foldersToDeleteIds += $folder.FolderId.Id
|
||||||
|
$folderNames += $folder.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
Empty-Folder -FolderIdList $foldersToDeleteIds -FolderNameList $folderNames
|
||||||
|
Delete-Folder -FolderIdList $foldersToDeleteIds -FolderNameList $folderNames
|
||||||
}
|
}
|
||||||
|
|
||||||
function Create-Contact {
|
function Create-Contact {
|
||||||
@ -459,7 +488,7 @@ function Get-ItemsToPurge {
|
|||||||
$foldersToSearchBody = "<t:DistinguishedFolderId Id='$WellKnownRoot'/>"
|
$foldersToSearchBody = "<t:DistinguishedFolderId Id='$WellKnownRoot'/>"
|
||||||
|
|
||||||
if (![String]::IsNullOrEmpty($SubFolderName)) {
|
if (![String]::IsNullOrEmpty($SubFolderName)) {
|
||||||
$subFolders, $moreToList = Get-FoldersToPurge -WellKnownRoot $WellKnownRoot -FolderNamePurgeList $SubFolderName -PurgeBeforeTimestamp $PurgeBeforeTimestamp
|
$subFolders = Get-FoldersToPurge -WellKnownRoot $WellKnownRoot -FolderNamePurgeList $SubFolderName -PurgeBeforeTimestamp $PurgeBeforeTimestamp
|
||||||
|
|
||||||
if ($subFolders.count -gt 0 ) {
|
if ($subFolders.count -gt 0 ) {
|
||||||
$foldersToSearchBody = ""
|
$foldersToSearchBody = ""
|
||||||
@ -615,6 +644,8 @@ function Purge-Items {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### MAIN ####
|
||||||
|
|
||||||
Write-Host 'Authenticating with Exchange Web Services ...'
|
Write-Host 'Authenticating with Exchange Web Services ...'
|
||||||
$global:Token = Get-AccessToken | ConvertTo-SecureString -AsPlainText -Force
|
$global:Token = Get-AccessToken | ConvertTo-SecureString -AsPlainText -Force
|
||||||
|
|
||||||
@ -622,14 +653,17 @@ $global:Token = Get-AccessToken | ConvertTo-SecureString -AsPlainText -Force
|
|||||||
$FolderNamePurgeList = $FolderNamePurgeList | ForEach-Object { @($_.Split(',').Trim()) }
|
$FolderNamePurgeList = $FolderNamePurgeList | ForEach-Object { @($_.Split(',').Trim()) }
|
||||||
$FolderPrefixPurgeList = $FolderPrefixPurgeList | ForEach-Object { @($_.Split(',').Trim()) }
|
$FolderPrefixPurgeList = $FolderPrefixPurgeList | ForEach-Object { @($_.Split(',').Trim()) }
|
||||||
|
|
||||||
|
Write-Host "`nPurging CI-produced folders under 'msgfolderroot' ..."
|
||||||
|
Write-Host "--------------------------------------------------------"
|
||||||
|
|
||||||
$purgeFolderParams = @{
|
$purgeFolderParams = @{
|
||||||
'WellKnownRoot' = "root";
|
'WellKnownRoot' = "msgfolderroot";
|
||||||
'FolderNamePurgeList' = $FolderNamePurgeList;
|
'FolderNamePurgeList' = $FolderNamePurgeList;
|
||||||
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
'FolderPrefixPurgeList' = $FolderPrefixPurgeList;
|
||||||
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp
|
'PurgeBeforeTimestamp' = $PurgeBeforeTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
#purge older prefix folders
|
#purge older prefix folders from msgfolderroot
|
||||||
Purge-Folders @purgeFolderParams
|
Purge-Folders @purgeFolderParams
|
||||||
|
|
||||||
#purge older contacts
|
#purge older contacts
|
||||||
@ -647,4 +681,20 @@ Purge-Items -ItemsFolder "calendar" -ItemsSubFolder "Birthdays" -PurgeBeforeTime
|
|||||||
# -/Recoverable Items/SubstrateHolds
|
# -/Recoverable Items/SubstrateHolds
|
||||||
Write-Host "`nProcess well-known folders that are always purged"
|
Write-Host "`nProcess well-known folders that are always purged"
|
||||||
Write-Host "---------------------------------------------------"
|
Write-Host "---------------------------------------------------"
|
||||||
Empty-Folder -WellKnownRoot "deleteditems", "recoverableitemsroot"
|
|
||||||
|
# We explicitly also clean direct folders under Deleted Items since there is some evidence
|
||||||
|
# that suggests that emptying alone may not be reliable
|
||||||
|
Write-Host "`nExplicit delete of all folders under 'DeletedItems' ..."
|
||||||
|
Write-Host "----------------------------------------------------------"
|
||||||
|
|
||||||
|
$purgeFolderParams = @{
|
||||||
|
'WellKnownRoot' = "deleteditems";
|
||||||
|
'FolderNamePurgeList' = $FolderNamePurgeList;
|
||||||
|
'FolderPrefixPurgeList' = @('*');
|
||||||
|
'PurgeBeforeTimestamp' = (Get-Date);
|
||||||
|
'PurgeTraversalShallow' = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Purge-Folders @purgeFolderParams
|
||||||
|
|
||||||
|
Empty-Folder -WellKnownRootList "deleteditems", "recoverableitemsroot"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user