Delete messages older than N days

Use this forum if you have problems with a hMailServer script, such as hMailServer WebAdmin or code in an event handler.
Post Reply
palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Delete messages older than N days

Post by palinka » 2020-11-01 18:49

There's a few of these around here, so this is nothing new, of course. But I do think I've improved on it, so I'm sharing it here.

As part of my hmailserver backup offsite routine I also made a fairly simplistic cleardown script, which was basically Jimimaseye's backup and cleardown script but rewritten in powershell. My first go at it was very linear in fashion which looked for a foldername match at the top level, then pruned messages older than N days for that folder plus all subfolders 3 levels down. There are big limitations on that method, of course. What if a you want to prune a subfolder, but the top level doesn't match your list of folders?

I've been playing around with this for a couple of days and have come up with *almost* perfection. The logic of it goes like this:

* Looks down every level of subfolders for name matches. For example, if you have a folder called PRUNE located at TOPLEVELFOLDER > SECONDLEVELFOLDER > PRUNE, it will find the folder and prune messages from it.

* CONFIG:PruneSubFolders - If you set this to true, it will prune all subfolders WITHIN the matching folder. For example, if your folder structure contains TOPLEVELFOLDER > SECONDLEVELFOLDER > PRUNE > FOURTHLEVELFOLDER, it will prune both PRUNE and FOURTHLEVELFOLDER

* CONFIG:DeleteEmptySubFolders - If you set this to true, it will delete empty subfolders WITHIN the matching folder UNLESS there is a subfolder of the empty subfolder that contains messages. The idea behind this is that message pruning is for trash and spam, mainly. Therefore if you delete a folder which then ends up in the trash folder, at some point the folder should also be deleted. This function will never delete name matching folders - it only deletes empty subfolders of matching folders if there are no folders within the empty folder that contain messages. Of course, if you delete a folder, all messages within the folder and all subfolders and the messages they contain will be deleted as well, so that's why it only works when there are no subfolders with messages.

* All searching is infinite - it looks down as many levels as exist to find what it needs.

* Folder name matching uses regex, so if you have, for example, automatically created folders you will be able to find them via regex match.

Above, I said *almost* perfection because there's one thing that's nagging me. In my original simplistic approach, which only looked down specified levels, I was able to output breadcrumb folder names like this: "Deleted 4 messages in user@mydomain.tld > Trash > hoohah > ddd". For the life of me, I cannot figure out how to breadcrumb the folders using the approach I took on this script. Maybe its not a big deal, but I found breadcrumbs to be useful information. Currently the output goes like this: "Deleted 1 messages from 3rdlevel in user@mydomain.tld". Of course, in this instance, I know its a third level folder but only because that's how I named the folder when testing things out. It could be a subfolder 8 levels down from the matching folder and you'd never know what level it is unless you had the folder structure memorized.

I think the solution to the breadcrumb could be in exploding the com command but I haven't figured out how to do it yet. Maybe someone has a tip that can put me in the right direction.

Anyway, here's the script. For testing, set $DoDelete to false and you will get the report without actually deleting any messages or folders. I haven't put this into my hMailServer Offsite Backup script yet because I want to test it some more before implementing it on a live server. Everything works for me so far. Any feedback would be greatly appreciated.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in cleanup folders
$PruneFolders          = "2ndlevel|Trash|Deleted|Junk|Spam|(2020-[0-1][0-9]-[0-3][0-9])$|ListMail|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

$Error.Clear()

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope

Function Debug ($DebugOutput) {Write-Host $DebugOutput}

Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$ASFName = $Folder.SubFolders.ItemByDBID($_).Name
			If (SubFoldersEmpty $Subfolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $ASFName in $AccountAddress"
				}
				Catch {
					Debug "[ERROR] Deleting empty subfolder $ASFName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	$Return = $False
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				$Return = $True
			} Else {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	Return $Return
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayDeletedMessages = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayDeletedMessages += $Message.ID
				$ArrayCountDeletedMessages += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayDeletedMessages | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayDeletedMessages.Clear()
}

Function DeleteOldMessages {
	
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$EnumDomain = 0
	
	Do {
		$hMSDomain = $hMS.Domains.Item($EnumDomain)
		If ($hMSDomain.Active) {
			$EnumAccount = 0
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($EnumAccount)
				If ($hMSAccount.Active) {
					$AccountAddress = $hMSAccount.Address
					$EnumFolder = 0
					If ($hMSAccount.IMAPFolders.Count -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($EnumFolder)
							If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
								If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {GetMatchFolders $hMSIMAPFolder}
						$EnumFolder++
						} Until ($EnumFolder -eq $hMSAccount.IMAPFolders.Count)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$EnumAccount++
			} Until ($EnumAccount -eq $hMSDomain.Accounts.Count)
		} # IF DOMAIN ACTIVE
		$EnumDomain++
	} Until ($EnumDomain -eq $hMS.Domains.Count)

	If ($TotalDeletedMessages -gt 0) {
		Debug "[OK] Finished deleting $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
	} Else {
		Debug "[OK] No messages older than $DaysBeforeDelete days to delete"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "[OK] Deleted $TotalDeletedFolders empty subfolders"
	}

} # END FUNCTION

DeleteOldMessages

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-01 19:30

OOPS... I wish the post edit time were longer. Already found a flaw in the delete folder > check subfolders for messages logic. It was doing the exact opposite of what was intended! :oops: Its fixed now.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 31               # Number of days to keep messages in pruned folders
$PruneFolders          = "2ndlevel|Trash|Deleted|Junk|Spam|APCUPSD|BrotherMFC|ServerMessages|Horde|SAUserList|Chase|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

$Error.Clear()

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name FolderHasMessages -Value 0 -Option AllScope

Function Debug ($DebugOutput) {Write-Host $DebugOutput}

Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$ASFName = $Folder.SubFolders.ItemByDBID($_).Name
			If (SubFoldersEmpty $Subfolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $ASFName in $AccountAddress"
				}
				Catch {
					Debug "[ERROR] Deleting empty subfolder $ASFName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
	$FolderHasMessages = 0
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	$Return = $True
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			} Else {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	Return $Return
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayDeletedMessages = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayDeletedMessages += $Message.ID
				$ArrayCountDeletedMessages += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayDeletedMessages | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayDeletedMessages.Clear()
}

Function DeleteOldMessages {
	
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$EnumDomain = 0
	
	Do {
		$hMSDomain = $hMS.Domains.Item($EnumDomain)
		If ($hMSDomain.Active) {
			$EnumAccount = 0
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($EnumAccount)
				If ($hMSAccount.Active) {
					$AccountAddress = $hMSAccount.Address
					$EnumFolder = 0
					If ($hMSAccount.IMAPFolders.Count -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($EnumFolder)
							If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
								If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {GetMatchFolders $hMSIMAPFolder}
						$EnumFolder++
						} Until ($EnumFolder -eq $hMSAccount.IMAPFolders.Count)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$EnumAccount++
			} Until ($EnumAccount -eq $hMSDomain.Accounts.Count)
		} # IF DOMAIN ACTIVE
		$EnumDomain++
	} Until ($EnumDomain -eq $hMS.Domains.Count)

	If ($TotalDeletedMessages -gt 0) {
		Debug "[OK] Finished deleting $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
	} Else {
		Debug "[OK] No messages older than $DaysBeforeDelete days to delete"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "[OK] Deleted $TotalDeletedFolders empty subfolders"
	}

} # END FUNCTION

DeleteOldMessages

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-01 19:53

:evil: Still now working.

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-01 22:45

OK, I think I fixed that search-for-empty-folders issue. Look at function SubFoldersEmpty.

Otherwise, I'm very confident in the message deleting. You can turn off DeleteEmptySubfolders in the config if you don't want to do that, but I think its good now. Anyway, set DoDelete to false for testing.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in pruned folders
$PruneFolders          = "2ndlevel|Trash|Deleted|Junk|Spam|(2020-[0-1][0-9]-[0-3][0-9])$|ListMail|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

$Error.Clear()

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope

Function Debug ($DebugOutput) {Write-Host $DebugOutput}

Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayDeletedMessages = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayDeletedMessages += $Message.ID
				$ArrayCountDeletedMessages += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayDeletedMessages | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayDeletedMessages.Clear()
}

Function DeleteOldMessages {
	
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$EnumDomain = 0
	
	Do {
		$hMSDomain = $hMS.Domains.Item($EnumDomain)
		If ($hMSDomain.Active) {
			$EnumAccount = 0
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($EnumAccount)
				If ($hMSAccount.Active) {
					$AccountAddress = $hMSAccount.Address
					$EnumFolder = 0
					If ($hMSAccount.IMAPFolders.Count -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($EnumFolder)
							If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
								If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {GetMatchFolders $hMSIMAPFolder}
						$EnumFolder++
						} Until ($EnumFolder -eq $hMSAccount.IMAPFolders.Count)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$EnumAccount++
			} Until ($EnumAccount -eq $hMSDomain.Accounts.Count)
		} # IF DOMAIN ACTIVE
		$EnumDomain++
	} Until ($EnumDomain -eq $hMS.Domains.Count)

	If ($TotalDeletedMessages -gt 0) {
		Debug "[OK] Finished deleting $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
	} Else {
		Debug "[OK] No messages older than $DaysBeforeDelete days to delete"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "[OK] Deleted $TotalDeletedFolders empty subfolders"
	}

} # END FUNCTION

DeleteOldMessages

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-02 01:28

I've been testing by creating several levels of subfolders and copying old messages. Seems to work perfectly now. I'm going to implement it tonight into my backup routine.

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-02 14:08

Worked as intended in last night's backup routine. :D

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-02 17:24

I cleaned up a bunch of stuff like variable naming so it makes more sense, plus better error checking. Working great now.

Everything below <# Begin hMailServer Backup Prune Messages #> is taken straight from my working backup script: https://github.com/palinkas-jo-reggelt/ ... ite_Backup

I still kept the switches for deleting messages and empty folders in case anyone running the backup wants to skip those, plus to make it easier to test and copy over.


Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in cleanup folders
$PruneFolders          = "Test 4th level|Trash|Deleted|Junk|Spam|(2020-[0-1][0-9]-[0-3][0-9])$|ListMail|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>

Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($EmailOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

<#  Begin hMailServer Backup Prune Messages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayMessagesToDelete | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	Do {
		$hMSDomain = $hMS.Domains.Item($IterateDomains)
		If ($hMSDomain.Active) {
			$IterateAccounts = 0
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
				If ($hMSAccount.Active) {
					$AccountAddress = $hMSAccount.Address
					$IterateIMAPFolders = 0
					If ($hMSAccount.IMAPFolders.Count -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
							If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
								If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {
								GetMatchFolders $hMSIMAPFolder
							} # IF NOT FOLDERNAME MATCH REGEX
						$IterateIMAPFolders++
						} Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$IterateAccounts++
			} Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
		} # IF DOMAIN ACTIVE
		$IterateDomains++
	} Until ($IterateDomains -eq $hMS.Domains.Count)

	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedMessages -gt 0) {
			Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
			Email "[OK] Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		} Else {
			Debug "No messages older than $DaysBeforeDelete days to prune"
			Email "[OK] No messages older than $DaysBeforeDelete days to prune"
		}
	}
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedFolders -gt 0) {
			Debug "Deleted $TotalDeletedFolders empty subfolders"
			Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
		} Else {
			Debug "No empty subfolders deleted"
			Email "[OK] No empty subfolders deleted"
		}
	}
}

PruneMessages

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-08 15:14

On (Work production) Server 2012, HMS 5.6.8 (PowerShell 3.0) I get a massive amount of errors like this when running a test run:

Code: Select all

Begin deleting messages older than 30 days
Delete disabled - Test Run ONLY

Exception getting "Item": "Invalid index. (Exception from HRESULT: 0x8002000B (DISP_E_BADINDEX))"
At C:\Users\RvdH\desktop\prunemessages.ps1:186 char:5
+                 $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], GetValueInvocationException
    + FullyQualifiedErrorId : CatchFromBaseAdapterParameterizedPropertyGetValueTI
On my own server, running Windows Server 2019 (HMS 5.7) it seems to run without issues (although i had nothing to delete there :lol:)

Code: Select all

Begin deleting messages older than 30 days
Delete disabled - Test Run ONLY
No messages older than 30 days to prune
No empty subfolders deleted
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-09 16:34

RvdH wrote:
2020-11-08 15:14
On (Work production) Server 2012, HMS 5.6.8 (PowerShell 3.0) I get a massive amount of errors like this when running a test run:

Code: Select all

Begin deleting messages older than 30 days
Delete disabled - Test Run ONLY

Exception getting "Item": "Invalid index. (Exception from HRESULT: 0x8002000B (DISP_E_BADINDEX))"
At C:\Users\RvdH\desktop\prunemessages.ps1:186 char:5
+                 $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], GetValueInvocationException
    + FullyQualifiedErrorId : CatchFromBaseAdapterParameterizedPropertyGetValueTI
On my own server, running Windows Server 2019 (HMS 5.7) it seems to run without issues (although i had nothing to delete there :lol:)

Code: Select all

Begin deleting messages older than 30 days
Delete disabled - Test Run ONLY
No messages older than 30 days to prune
No empty subfolders deleted
All powershell are not created equal... :roll: That's why you try before you buy...

Can you try on your server again, but change the number of days to 1?

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-09 18:41

My powershell (win 10) is version 5.

Code: Select all

PS C:\Users\palinka> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.546
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.546
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
DISP_E_BADINDEX would indicate its trying to access a null variable, most likely. Such as a folder with no messages or a folder with no subfolders.

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-10 13:35

palinka wrote:
2020-11-09 16:34
All powershell are not created equal... :roll: That's why you try before you buy...

Can you try on your server again, but change the number of days to 1?
Yea seems like it....can't get this to work (to bad)
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-10 16:46

RvdH wrote:
2020-11-10 13:35
palinka wrote:
2020-11-09 16:34
All powershell are not created equal... :roll: That's why you try before you buy...

Can you try on your server again, but change the number of days to 1?
Yea seems like it....can't get this to work (to bad)
I don't believe the script uses any "new" modules. Its just com stuff.

Is recursive functions a "new" feature of PS 3/4/5?

Its possible variable scope works differently in older versions. I'll look into that later when I have time.

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-10 17:26

RvdH wrote:
2020-11-10 13:35
Yea seems like it....can't get this to work (to bad)
I just looked at your error a little closer. Line 186 is at the beginning - not even part of a recursive function.

Do ANY com scripts work on the affected server? Are you running the script on the same machine?

If that was the very first error, it seems to have made it past the first com object which is on line 183.

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-10 19:30

palinka wrote:
2020-11-10 17:26
RvdH wrote:
2020-11-10 13:35
Yea seems like it....can't get this to work (to bad)
I just looked at your error a little closer. Line 186 is at the beginning - not even part of a recursive function.

Do ANY com scripts work on the affected server? Are you running the script on the same machine?

If that was the very first error, it seems to have made it past the first com object which is on line 183.
Sure, like i said before, hmailserver 5.6.8 (32-bit) running on a Server 2012 instance. (the vbs cleanup script by jimimaseye works fine on the same box)
Looks like a incompatibility with PS version, but who know....
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-10 19:36

RvdH wrote:
2020-11-10 19:30
palinka wrote:
2020-11-10 17:26
RvdH wrote:
2020-11-10 13:35
Yea seems like it....can't get this to work (to bad)
I just looked at your error a little closer. Line 186 is at the beginning - not even part of a recursive function.

Do ANY com scripts work on the affected server? Are you running the script on the same machine?

If that was the very first error, it seems to have made it past the first com object which is on line 183.
Sure, like i said before, hmailserver 5.6.8 (32-bit) running on a Server 2012 instance. (the vbs cleanup script by jimimaseye works fine on the same box)
Looks like a incompatibility with PS version, but who know....
That's very unfortunate. I like this script better than Jimi's if only for the fact that there's no log spam - it only reports on messages actually deleted. But... working log spam is better than not working clean logging any day of the week.

Plus the "delete empty subfolders" is a pretty cool feature, if I may say so myself. :D

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-10 19:46

Just looking at it again. Your error was "bad index". I've noticed that sometimes operations will only accept a declared variable. In my DO loops, I end them with a com variable like this:

Code: Select all

} Until ($IterateDomains -eq $hMS.Domains.Count)
Sometimes things break unless you use a variable like this:

Code: Select all

$DomainCount = $hMS.Domains.Count
} Until ($IterateDomains -eq $DomainCount)
I'll go through and replace these when I have time. This is the only thing I can think of that could break between versions, since nothing here is particularly complicated.

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-15 05:26

RvdH wrote:
2020-11-10 19:30
Sure, like i said before, hmailserver 5.6.8 (32-bit) running on a Server 2012 instance. (the vbs cleanup script by jimimaseye works fine on the same box)
Looks like a incompatibility with PS version, but who know....
Try this one on the old box.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in cleanup folders
$PruneFolders          = "Test 4th level|Trash|Deleted|Junk|Spam|(2020-[0-1][0-9]-[0-3][0-9])$|ListMail|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			$SubFolderSubfoldersCount = $SubFolder.Subfolders.Count
			If ($SubFolderSubfoldersCount -gt 0) {GetSubFolders $SubFolder} 
			$SubFolderMessagesCount = $SubFolder.Messages.Count
			If ($SubFolderMessagesCount -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderMessagesCount = $SubFolder.Messages.Count
			If ($SubFolderMessagesCount -gt 0) {
				Return $False
				Break
			}
			$SubFolderSubFoldersCount = $SubFolder.SubFolders.Count
			If ($SubFolderSubFoldersCount -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	$FolderMessagesCount = $Folder.Messages.Count
	If ($FolderMessagesCount -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			$MessageInternalDate = $Message.InternalDate
			If ($MessageInternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $FolderMessagesCount)
	}
	$ArrayMessagesToDelete | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	$hMSDomainsCount = $hMS.Domains.Count
	Do {
		$hMSDomain = $hMS.Domains.Item($IterateDomains)
		$hMSDomainActive = $hMSDomain.Active
		If ($hMSDomainActive) {
			$IterateAccounts = 0
			$hMSDomainAccountsCount = $hMSDomain.Accounts.Count
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
				$hMSAccountActive = $hMSAccount.Active
				If ($hMSAccountActive) {
					$AccountAddress = $hMSAccount.Address
					$IterateIMAPFolders = 0
					$hMSAccountIMAPFoldersCount = $hMSAccount.IMAPFolders.Count
					If ($hMSAccountIMAPFoldersCount -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
							$hMSIMAPFolderName = $hMSIMAPFolder.Name
							If ($hMSIMAPFolderName -match [regex]$PruneFolders) {
								$hMSIMAPFolderSubFoldersCount = $hMSIMAPFolder.SubFolders.Count
								If ($hMSIMAPFolderSubFoldersCount -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {
								GetMatchFolders $hMSIMAPFolder
							} # IF NOT FOLDERNAME MATCH REGEX
						$IterateIMAPFolders++
						} Until ($IterateIMAPFolders -eq $hMSAccountIMAPFoldersCount)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$IterateAccounts++
			} Until ($IterateAccounts -eq $hMSDomainAccountsCount)
		} # IF DOMAIN ACTIVE
		$IterateDomains++
	} Until ($IterateDomains -eq $hMSDomainsCount)

	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedMessages -gt 0) {
			Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
			Email "[OK] Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		} Else {
			Debug "No messages older than $DaysBeforeDelete days to prune"
			Email "[OK] No messages older than $DaysBeforeDelete days to prune"
		}
	}
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedFolders -gt 0) {
			Debug "Deleted $TotalDeletedFolders empty subfolders"
			Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
		} Else {
			Debug "No empty subfolders deleted"
			Email "[OK] No empty subfolders deleted"
		}
	}
}

PruneMessages

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-22 12:37

palinka wrote:
2020-11-15 05:26
RvdH wrote:
2020-11-10 19:30
Sure, like i said before, hmailserver 5.6.8 (32-bit) running on a Server 2012 instance. (the vbs cleanup script by jimimaseye works fine on the same box)
Looks like a incompatibility with PS version, but who know....
Try this one on the old box.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in cleanup folders
$PruneFolders          = "Test 4th level|Trash|Deleted|Junk|Spam|(2020-[0-1][0-9]-[0-3][0-9])$|ListMail|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			$SubFolderSubfoldersCount = $SubFolder.Subfolders.Count
			If ($SubFolderSubfoldersCount -gt 0) {GetSubFolders $SubFolder} 
			$SubFolderMessagesCount = $SubFolder.Messages.Count
			If ($SubFolderMessagesCount -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderMessagesCount = $SubFolder.Messages.Count
			If ($SubFolderMessagesCount -gt 0) {
				Return $False
				Break
			}
			$SubFolderSubFoldersCount = $SubFolder.SubFolders.Count
			If ($SubFolderSubFoldersCount -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	$FolderSubFoldersCount = $Folder.SubFolders.Count
	If ($FolderSubFoldersCount -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $FolderSubFoldersCount)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	$FolderMessagesCount = $Folder.Messages.Count
	If ($FolderMessagesCount -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			$MessageInternalDate = $Message.InternalDate
			If ($MessageInternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $FolderMessagesCount)
	}
	$ArrayMessagesToDelete | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	$hMSDomainsCount = $hMS.Domains.Count
	Do {
		$hMSDomain = $hMS.Domains.Item($IterateDomains)
		$hMSDomainActive = $hMSDomain.Active
		If ($hMSDomainActive) {
			$IterateAccounts = 0
			$hMSDomainAccountsCount = $hMSDomain.Accounts.Count
			Do {
				$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
				$hMSAccountActive = $hMSAccount.Active
				If ($hMSAccountActive) {
					$AccountAddress = $hMSAccount.Address
					$IterateIMAPFolders = 0
					$hMSAccountIMAPFoldersCount = $hMSAccount.IMAPFolders.Count
					If ($hMSAccountIMAPFoldersCount -gt 0) {
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
							$hMSIMAPFolderName = $hMSIMAPFolder.Name
							If ($hMSIMAPFolderName -match [regex]$PruneFolders) {
								$hMSIMAPFolderSubFoldersCount = $hMSIMAPFolder.SubFolders.Count
								If ($hMSIMAPFolderSubFoldersCount -gt 0) {
									GetSubFolders $hMSIMAPFolder
								} # IF SUBFOLDER COUNT > 0
								GetMessages $hMSIMAPFolder
							} # IF FOLDERNAME MATCH REGEX
							Else {
								GetMatchFolders $hMSIMAPFolder
							} # IF NOT FOLDERNAME MATCH REGEX
						$IterateIMAPFolders++
						} Until ($IterateIMAPFolders -eq $hMSAccountIMAPFoldersCount)
					} # IF IMAPFOLDER COUNT > 0
				} #IF ACCOUNT ACTIVE
				$IterateAccounts++
			} Until ($IterateAccounts -eq $hMSDomainAccountsCount)
		} # IF DOMAIN ACTIVE
		$IterateDomains++
	} Until ($IterateDomains -eq $hMSDomainsCount)

	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedMessages -gt 0) {
			Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
			Email "[OK] Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		} Else {
			Debug "No messages older than $DaysBeforeDelete days to prune"
			Email "[OK] No messages older than $DaysBeforeDelete days to prune"
		}
	}
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedFolders -gt 0) {
			Debug "Deleted $TotalDeletedFolders empty subfolders"
			Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
		} Else {
			Debug "No empty subfolders deleted"
			Email "[OK] No empty subfolders deleted"
		}
	}
}

PruneMessages
Sorry for the late reply, sorry to tell the script is still not working on our (the company i work for) production server.
Could it possibly be a variable casting issue? I added some debugging lines to the code and i noticed the first, lets say 30 domains get processed properly before the DISP_E_BADINDEX exception shows.
Funny thing, if i disable the domain where the DISP_E_BADINDEX exception occurs for the first time on next run the DISP_E_BADINDEX error occurs on previous domain, weird huh?
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-22 13:51

Got it (for real this time :) )

I had one domain without account (domain holds only a alias), wrap this around Do { $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts) ... } Until ($IterateAccounts -eq $hMSDomainAccountsCount)

Code: Select all

If ($hMSDomainAccountsCount -gt 0) {

}
And while we are on it, you also might wrap hMSDomainsCount in such check

Code: Select all

If ($hMSDomainsCount -gt 0) {
}
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-22 19:42

RvdH wrote:
2020-11-22 13:51
Got it (for real this time :) )

I had one domain without account (domain holds only a alias), wrap this around Do { $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts) ... } Until ($IterateAccounts -eq $hMSDomainAccountsCount)

Code: Select all

If ($hMSDomainAccountsCount -gt 0) {

}
And while we are on it, you also might wrap hMSDomainsCount in such check

Code: Select all

If ($hMSDomainsCount -gt 0) {
}
Right on, man! Thanks for the help on this. Its tough to troubleshoot errors that don't exist in your own environment.

I'll fix it up later as soon as I have time.

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-22 20:03

palinka wrote:
2020-11-22 19:42
RvdH wrote:
2020-11-22 13:51
Got it (for real this time :) )

I had one domain without account (domain holds only a alias), wrap this around Do { $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts) ... } Until ($IterateAccounts -eq $hMSDomainAccountsCount)

Code: Select all

If ($hMSDomainAccountsCount -gt 0) {

}
And while we are on it, you also might wrap hMSDomainsCount in such check

Code: Select all

If ($hMSDomainsCount -gt 0) {
}
Right on, man! Thanks for the help on this. Its tough to troubleshoot errors that don't exist in your own environment.

I'll fix it up later as soon as I have time.

PS, your original script (without defining variables) works fine as well with those checks added

Code: Select all

If ($hMS.Domains.Count -gt 0) { 
 ...
}

If ($hMSDomain.Accounts.Count -gt 0) {
 ...
}
And i would remove the else statement after 'If ($DeleteMessageErrors -gt 0) {}', as you can have both errors AND still purge messages successfully (i got 1 error, on DeleteByDBID on my first successful run)

Code: Select all

If ($DeleteMessageErrors -gt 0) {
 ...
}
If ($TotalDeletedMessages -gt 0) {
 ...
}
same probably applies to DeleteFolderErrors
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-22 20:16

I had more files being deleted after i changed the $PruneFolders to be lowercase and change the script like:

Code: Select all

$PruneFolders          = "test 4th level|trash|deleted|junk|spam|(2020-[0-1][0-9]-[0-3][0-9])$|listmail|unsubscribes"
If ($SubFolderName.ToLower() -match [regex]$PruneFolders) {

If ($hMSIMAPFolder.Name.ToLower() -match [regex]$PruneFolders) {


i think i read somewhere Powershell rexex is not case sensitive, but apparently it is
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-22 20:38

Done. Thanks for the troubleshooting.

One other thing I fixed was regex was case sensitive when it should have been case insensitive. I had:

Code: Select all

If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
removing [regex] did the trick.

Code: Select all

If ($hMSIMAPFolder.Name -match $PruneFolders) {

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "supersecretpassword"     # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in pruned folders
$PruneFolders          = "2nd level|Trash|Deleted|Junk|Spam|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayMessagesToDelete | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	If ($hMS.Domains.Count -gt 0) {
		Do {
			$hMSDomain = $hMS.Domains.Item($IterateDomains)
			If ($hMSDomain.Active) {
				$IterateAccounts = 0
				If ($hMSDomain.Accounts.Count -gt 0) {
					Do {
						$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
						If ($hMSAccount.Active) {
							$AccountAddress = $hMSAccount.Address
							$IterateIMAPFolders = 0
							If ($hMSAccount.IMAPFolders.Count -gt 0) {
								Do {
									$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
									If ($hMSIMAPFolder.Name -match $PruneFolders) {
										If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
											GetSubFolders $hMSIMAPFolder
										} # IF SUBFOLDER COUNT > 0
										GetMessages $hMSIMAPFolder
									} # IF FOLDERNAME MATCH REGEX
									Else {
										GetMatchFolders $hMSIMAPFolder
									} # IF NOT FOLDERNAME MATCH REGEX
								$IterateIMAPFolders++
								} Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
							} # IF IMAPFOLDER COUNT > 0
						} #IF ACCOUNT ACTIVE
						$IterateAccounts++
					} Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
				} # IF ACCOUNT COUNT > 0
			} # IF DOMAIN ACTIVE
			$IterateDomains++
		} Until ($IterateDomains -eq $hMS.Domains.Count)
	} # IF DOMAIN COUNT > 0

	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedMessages -gt 0) {
			Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
			Email "[OK] Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		} Else {
			Debug "No messages older than $DaysBeforeDelete days to prune"
			Email "[OK] No messages older than $DaysBeforeDelete days to prune"
		}
	}
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	} Else {
		If ($TotalDeletedFolders -gt 0) {
			Debug "Deleted $TotalDeletedFolders empty subfolders"
			Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
		} Else {
			Debug "No empty subfolders deleted"
			Email "[OK] No empty subfolders deleted"
		}
	}
}

PruneMessages

User avatar
SorenR
Senior user
Senior user
Posts: 4169
Joined: 2006-08-21 15:38
Location: Denmark

Re: Delete messages older than N days

Post by SorenR » 2020-11-22 20:42

Well... The -match / -imatch operators expect a string pattern to follow so perhaps not so strange after all...
SørenR.

Algorithm (noun.)
Word used by programmers when they do not want to explain what they did.

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-22 20:43

RvdH wrote:
2020-11-22 20:16
I had more files being deleted after i changed the $PruneFolders to be lowercase and change the script like:

Code: Select all

$PruneFolders          = "test 4th level|trash|deleted|junk|spam|(2020-[0-1][0-9]-[0-3][0-9])$|listmail|unsubscribes"
If ($SubFolderName.ToLower() -match [regex]$PruneFolders) {

If ($hMSIMAPFolder.Name.ToLower() -match [regex]$PruneFolders) {


i think i read somewhere Powershell rexex is not case sensitive, but apparently it is

Ha! I posted above before I saw your reply. Great minds and all that jazz.... :mrgreen:

Yes, powershell -match is NOT case sensitive. -cmatch is case sensitive. However, apparently placing [regex] in front of the matching string will force case sensitivity. I put it there because I had some issue with regex matching on a variable not working a long time ago. Force of habit...

User avatar
RvdH
Senior user
Senior user
Posts: 1193
Joined: 2008-06-27 14:42
Location: Netherlands

Re: Delete messages older than N days

Post by RvdH » 2020-11-22 20:44

RvdH wrote:
2020-11-22 20:03
And i would remove the else statement after 'If ($DeleteMessageErrors -gt 0) {}', as you can have both errors AND still purge messages successfully (i got 1 error, on DeleteByDBID on my first successful run)

Code: Select all

If ($DeleteMessageErrors -gt 0) {
 ...
}
If ($TotalDeletedMessages -gt 0) {
 ...
}
same probably applies to DeleteFolderErrors
CIDR to RegEx: d-fault.nl/CIDRtoRegEx
DNS Lookup: d-fault.nl/DNSTools
DNSBL Lookup: d-fault.nl/DNSBLLookup
GEOIP Lookup: d-fault.nl/GeoipLookup

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-22 22:50

RvdH wrote:
2020-11-22 20:44
RvdH wrote:
2020-11-22 20:03
And i would remove the else statement after 'If ($DeleteMessageErrors -gt 0) {}', as you can have both errors AND still purge messages successfully (i got 1 error, on DeleteByDBID on my first successful run)

Code: Select all

If ($DeleteMessageErrors -gt 0) {
 ...
}
If ($TotalDeletedMessages -gt 0) {
 ...
}
same probably applies to DeleteFolderErrors

Good catch.

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in pruned folders
$PruneFolders          = "2nd level|Trash|Deleted|Junk|Spam|APCUPSD|BrotherMFC|ServerMessages|Horde|SAUserList|Chase|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($DeleteEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			$FolderName = $CheckFolder.Name
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $FolderName in $AccountAddress"
				}
				Catch {
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
					Debug "[ERROR] : $Error"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match [regex]$PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayMessagesToDelete | ForEach {
		$AFolderName = $Folder.Name
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
			Debug "[ERROR] $Error"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages messages from $AFolderName in $AccountAddress"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin deleting messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	If ($hMS.Domains.Count -gt 0) {
		Do {
			$hMSDomain = $hMS.Domains.Item($IterateDomains)
			If ($hMSDomain.Active) {
				$IterateAccounts = 0
				If ($hMSDomain.Accounts.Count -gt 0) {
					Do {
						$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
						If ($hMSAccount.Active) {
							$AccountAddress = $hMSAccount.Address
							$IterateIMAPFolders = 0
							If ($hMSAccount.IMAPFolders.Count -gt 0) {
								Do {
									$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
									If ($hMSIMAPFolder.Name -match $PruneFolders) {
										If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
											GetSubFolders $hMSIMAPFolder
										} # IF SUBFOLDER COUNT > 0
										GetMessages $hMSIMAPFolder
									} # IF FOLDERNAME MATCH REGEX
									Else {
										GetMatchFolders $hMSIMAPFolder
									} # IF NOT FOLDERNAME MATCH REGEX
								$IterateIMAPFolders++
								} Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
							} # IF IMAPFOLDER COUNT > 0
						} #IF ACCOUNT ACTIVE
						$IterateAccounts++
					} Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
				} # IF ACCOUNT COUNT > 0
			} # IF DOMAIN ACTIVE
			$IterateDomains++
		} Until ($IterateDomains -eq $hMS.Domains.Count)
	} # IF DOMAIN COUNT > 0

	<#  Report message pruning  #>
	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	}
	If ($TotalDeletedMessages -gt 0) {
		Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		Email "[OK] Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
	} Else {
		Debug "No messages older than $DaysBeforeDelete days to prune"
		Email "[OK] No messages older than $DaysBeforeDelete days to prune"
	}

	<#  Report folder pruning  #>
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "Deleted $TotalDeletedFolders empty subfolders"
		Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
	} Else {
		Debug "No empty subfolders deleted"
		Email "[OK] No empty subfolders deleted"
	}
}

PruneMessages

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-11-25 14:18

With option to skip matching accounts and/or domains if you don't want to include them in the routine:

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in pruned folders
$SkipAccountPruning    = "user@domain1.tld|spam@domain2.tld|dmarc@domain3.tld" # User accounts to skip - uses regex - If not used, leave blank (not "") or it will match EVERYTHING!
$SkipDomainPruning     = "spam.domain.tld" # Domains to skip - uses regex - If not used, leave blank (not "") or it will match EVERYTHING!
$PruneFolders          = "Trash|Deleted|Junk|Spam|folder-[0-9]{6}|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex

<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}
Function Plural ($Integer) {
	If ($Integer -eq 1) {$S = ""} Else {$S = "s"}
	Return $S
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($PruneEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($PruneEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
				}
				Catch {
					$Err = $Error[0]
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
					Debug "[ERROR] : $Err"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match $PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayMessagesToDelete | ForEach {
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$Err = $Error[0]
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $($Folder.Name) in $($hMSAccount.Address)"
			Debug "[ERROR] $Err"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages message$(Plural $DeletedMessages) from $($Folder.Name) in $($hMSAccount.Address)"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin pruning messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	$IterateDomains = 0
	If ($hMS.Domains.Count -gt 0) {
		Do {
			$hMSDomain = $hMS.Domains.Item($IterateDomains)
			If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch $SkipDomainPruning)) {
				$IterateAccounts = 0
				If ($hMSDomain.Accounts.Count -gt 0) {
					Do {
						$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
						If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch $SkipAccountPruning)) {
							$IterateIMAPFolders = 0
							If ($hMSAccount.IMAPFolders.Count -gt 0) {
								Do {
									$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
									If ($hMSIMAPFolder.Name -match $PruneFolders) {
										If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
											GetSubFolders $hMSIMAPFolder
										} # IF SUBFOLDER COUNT > 0
										GetMessages $hMSIMAPFolder
									} # IF FOLDERNAME MATCH REGEX
									Else {
										GetMatchFolders $hMSIMAPFolder
									} # IF NOT FOLDERNAME MATCH REGEX
								$IterateIMAPFolders++
								} Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
							} # IF IMAPFOLDER COUNT > 0
						} #IF ACCOUNT ACTIVE
						$IterateAccounts++
					} Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
				} # IF ACCOUNT COUNT > 0
			} # IF DOMAIN ACTIVE
			$IterateDomains++
		} Until ($IterateDomains -eq $hMS.Domains.Count)
	} # IF DOMAIN COUNT > 0

	<#  Report message pruning  #>
	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	}
	If ($TotalDeletedMessages -gt 0) {
		Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		Email "[OK] Pruned $TotalDeletedMessages messages older than $DaysBeforeDelete days"
	} Else {
		Debug "No messages older than $DaysBeforeDelete days to prune"
		Email "[OK] No messages older than $DaysBeforeDelete days to prune"
	}

	<#  Report folder pruning  #>
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "Deleted $TotalDeletedFolders empty subfolders"
		Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
	} Else {
		Debug "No empty subfolders deleted"
		# Email "[OK] No empty subfolders deleted"
	}
}

PruneMessages

palinka
Senior user
Senior user
Posts: 2455
Joined: 2017-09-12 17:57

Re: Delete messages older than N days

Post by palinka » 2020-12-16 16:38

I had one account that was not being processed, and in debugging I found that empty $SkipDomainPruning and $SkipAccountPruning were causing it. I'm not sure why, but adding modifier [regex] to them fixed the problem.

Code: Select all

If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch [regex]$SkipDomainPruning) -and ($hMSDomain.Accounts.Count -gt 0)) {
-and-

Code: Select all

If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch [regex]$SkipAccountPruning) -and ($hMSAccount.IMAPFolders.Count -gt 0)) {
Now it works on all accounts if those variables are empty -or- skips matching domains/accounts if they're not empty.

Revised powershell:

Code: Select all

<#

.SYNOPSIS
	Prune Messages

.DESCRIPTION
	Delete messages in specified folders older than N days

.FUNCTIONALITY
	Looks for folder name match at any folder level and if found, deletes all messages older than N days within that folder and all subfolders within
	Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config

.PARAMETER 

	
.NOTES
	Folder name matching occurs at any level folder
	Empty folders are assumed to be trash if they're located in this script
	Only empty folders found in levels BELOW matching level will be deleted
	
.EXAMPLE


#>

<###   USER VARIABLES   ###>
$hMSAdminPass          = "secretpassword" # hMailServer Admin password
$DoDelete              = $False           # FOR TESTING - set to false to run and report results without deleting messages and folders
$PruneSubFolders       = $True            # True will prune all folders in levels below name matching folders
$DeleteEmptySubFolders = $True            # True will delete empty subfolders below the matching level unless a subfolder within contains messages
$DaysBeforeDelete      = 30               # Number of days to keep messages in pruned folders
$SkipAccountPruning    = "user@domain1.tld|spam@domain2.tld|dmarc@domain3.tld" # User accounts to skip - uses regex - If not used, leave blank (not "") or it will match EVERYTHING!
$SkipDomainPruning     = "spam.domain.tld" # Domains to skip - uses regex - If not used, leave blank (not "") or it will match EVERYTHING!
$PruneFolders          = "Trash|Deleted|Junk|Spam|folder-[0-9]{6}|Unsubscribes"  # Names of IMAP folders you want to cleanup - uses regex
<#  Functions copied from hMailServer Backup required for testing  #>
Function Debug ($DebugOutput) {Write-Host $DebugOutput}
Function Email ($DebugOutput) {}
Function ElapsedTime ($EndTime) {
	$TimeSpan = New-Timespan $EndTime
	If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
	If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
	If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
	If (($TimeSpan).TotalSeconds -lt 1) {
		$Return = "less than 1 second"
	} Else {
		$Return = "$Hours$Minutes$Seconds"
	}
	Return $Return
}
Function Plural ($Integer) {
	If ($Integer -eq 1) {$S = ""} Else {$S = "s"}
	Return $S
}

<#  Begin hMailServerBackupPruneMessages  #>

Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope

Function GetSubFolders ($Folder) {
	$IterateFolder = 0
	$ArrayDeletedFolders = @()
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			$SubFolderID = $SubFolder.ID
			If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder} 
			If ($SubFolder.Messages.Count -gt 0) {
				If ($PruneSubFolders) {GetMessages $SubFolder}
			} Else {
				If ($PruneEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
			} 
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
	If ($PruneEmptySubFolders) {
		$ArrayDeletedFolders | ForEach {
			$CheckFolder = $Folder.SubFolders.ItemByDBID($_)
			If (SubFoldersEmpty $CheckFolder) {
				Try {
					If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
					$TotalDeletedFolders++
					Debug "Deleted empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
				}
				Catch {
					$Err = $Error[0]
					$DeleteFolderErrors++
					Debug "[ERROR] Deleting empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
					Debug "[ERROR] : $Err"
				}
				$Error.Clear()
			}
		}
	}
	$ArrayDeletedFolders.Clear()
}

Function SubFoldersEmpty ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			If ($SubFolder.Messages.Count -gt 0) {
				Return $False
				Break
			}
			If ($SubFolder.SubFolders.Count -gt 0) {
				SubFoldersEmpty $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	} Else {
		Return $True
	}
}

Function GetMatchFolders ($Folder) {
	$IterateFolder = 0
	If ($Folder.SubFolders.Count -gt 0) {
		Do {
			$SubFolder = $Folder.SubFolders.Item($IterateFolder)
			$SubFolderName = $SubFolder.Name
			If ($SubFolderName -match $PruneFolders) {
				GetSubFolders $SubFolder
				GetMessages $SubFolder
			} Else {
				GetMatchFolders $SubFolder
			}
			$IterateFolder++
		} Until ($IterateFolder -eq $Folder.SubFolders.Count)
	}
}

Function GetMessages ($Folder) {
	$IterateMessage = 0
	$ArrayMessagesToDelete = @()
	$DeletedMessages = 0
	If ($Folder.Messages.Count -gt 0) {
		Do {
			$Message = $Folder.Messages.Item($IterateMessage)
			If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
				$ArrayMessagesToDelete += $Message.ID
			}
			$IterateMessage++
		} Until ($IterateMessage -eq $Folder.Messages.Count)
	}
	$ArrayMessagesToDelete | ForEach {
		Try {
			If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
			$DeletedMessages++
			$TotalDeletedMessages++
		}
		Catch {
			$Err = $Error[0]
			$DeleteMessageErrors++
			Debug "[ERROR] Deleting messages from folder $($Folder.Name) in $($hMSAccount.Address)"
			Debug "[ERROR] $Err"
		}
		$Error.Clear()
	}
	If ($DeletedMessages -gt 0) {
		Debug "Deleted $DeletedMessages message$(Plural $DeletedMessages) from $($Folder.Name) in $($hMSAccount.Address)"
	}
	$ArrayMessagesToDelete.Clear()
}

Function PruneMessages {
	
	$Error.Clear()
	$BeginDeletingOldMessages = Get-Date
	Debug "----------------------------"
	Debug "Begin pruning messages older than $DaysBeforeDelete days"
	If (-not($DoDelete)) {
		Debug "Delete disabled - Test Run ONLY"
	}

	<#  Authenticate hMailServer COM  #>
	$hMS = New-Object -COMObject hMailServer.Application
	$hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
	
	If ($hMS.Domains.Count -gt 0) {
		$IterateDomains = 0
		Do {
			$hMSDomain = $hMS.Domains.Item($IterateDomains)
			If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch [regex]$SkipDomainPruning) -and ($hMSDomain.Accounts.Count -gt 0)) {
				$IterateAccounts = 0
				Do {
					$hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
					If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch [regex]$SkipAccountPruning) -and ($hMSAccount.IMAPFolders.Count -gt 0)) {
						$IterateIMAPFolders = 0
						Do {
							$hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
							If ($hMSIMAPFolder.Name -match $PruneFolders) {
								If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
									GetSubFolders $hMSIMAPFolder
								}
								GetMessages $hMSIMAPFolder
							} Else {
								GetMatchFolders $hMSIMAPFolder
							}
						$IterateIMAPFolders++
						} Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
					}
					$IterateAccounts++
				} Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
			}
			$IterateDomains++
		} Until ($IterateDomains -eq $hMS.Domains.Count)
	}

	<#  Report message pruning  #>
	If ($DeleteMessageErrors -gt 0) {
		Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
		Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
	}
	If ($TotalDeletedMessages -gt 0) {
		Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
		Email "[OK] Pruned $TotalDeletedMessages messages older than $DaysBeforeDelete days"
	} Else {
		Debug "No messages older than $DaysBeforeDelete days to prune"
		Email "[OK] No messages older than $DaysBeforeDelete days to prune"
	}

	<#  Report folder pruning  #>
	If ($DeleteFolderErrors -gt 0) {
		Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
		Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
	}
	If ($TotalDeletedFolders -gt 0) {
		Debug "Deleted $TotalDeletedFolders empty subfolders"
		Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
	} Else {
		Debug "No empty subfolders deleted"
		# Email "[OK] No empty subfolders deleted"
	}
}

PruneMessages

Post Reply