├── version.txt
├── .gitignore
├── .gitattributes
├── README.md
├── hMailServerBackup.ps1
├── hMailServerBackupDownload.ps1
├── hMailServerBackupConfig.dist.ps1
├── hMailServerBackupPruneMessagesTEST.ps1
└── hMailServerBackupFunctions.ps1
/version.txt:
--------------------------------------------------------------------------------
1 | 1.23
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore config file
2 | /hMailServerBackupConfig.ps1
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hMailServer Offsite Backup
2 | hMailServer backup routine with extras
3 |
4 | Discussion thread: https://hmailserver.com/forum/viewtopic.php?f=9&t=35447
5 |
6 | # NEW
7 | OpenPhish database files updater. New variables added to config.
8 | See topic for further info: https://hmailserver.com/forum/viewtopic.php?t=40295
9 |
10 | Removed offsite upload to letsupload.io because they are now defunct
11 |
12 |
13 | # What does it do?
14 | 1) Stops hMailServer and SpamAssassin Services
15 | 2) Updates SpamAssassin
16 | 3) Cycles hMailServer and SpamAssassin logs
17 | 4) Backs up hMailServer data directory using RoboCopy
18 | 5) Dumps MySQL database -or- internal database and adds to backup directory
19 | 6) Backs up miscellaneous files
20 | 7) Updates OpenPhish database files
21 | 8) Restarts SpamAssassin and hMailServer
22 | 9) Prunes messages older than specified number of days in specified folders and subfolders (eg Trash, Spam, etc)
23 | 10) Feeds messages newer than specified number of days to Bayes database through spamc.exe
24 | 11) Prunes hMailServer logs older than specified number of days
25 | 12) Prunes local backup copies older than specified number of days
26 | 13) Compresses the backup directory into a multi-volume 7z archive with AES-256 encryption (including header encyrption)
27 | 14) Sends email with debug log attached
28 |
29 | # Requirements
30 | Working hMailServer using either internal database or MySQL
31 | If using SpamAssassin, must configure service for --allow-tell in order to feed spamc
32 | OpenPhish update requires WGET in the system path
33 |
34 | # Instructions
35 | Create account at LetsUpload.io and create API keys
36 | Fill in variables in hMailServerBackupConfig.ps1.dist and rename to hMailServerBackupConfig.ps1
37 | Run hMailServerBackup.ps1 from task scheduler at 11:58 PM (time allows for properly cycling logs)
38 |
39 | # Notes
40 |
--------------------------------------------------------------------------------
/hMailServerBackup.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | .SYNOPSIS
4 | hMailServer Backup
5 |
6 | .DESCRIPTION
7 | hMailServer Backup
8 |
9 | .FUNCTIONALITY
10 | Backs up hMailServer, compresses backup and uploads to LetsUpload.io
11 |
12 | .PARAMETER
13 |
14 |
15 | .NOTES
16 | Run at 11:58PM from task scheduler in order to properly cycle log files.
17 |
18 | .EXAMPLE
19 |
20 |
21 | #>
22 |
23 | <### LOAD SUPPORTING FILES ###>
24 | Try {
25 | .("$PSScriptRoot\hMailServerBackupConfig.ps1")
26 | .("$PSScriptRoot\hMailServerBackupFunctions.ps1")
27 | }
28 | Catch {
29 | Write-Output "$(Get-Date -f G) : ERROR : Unable to load supporting PowerShell Scripts" | Out-File "$PSScriptRoot\PSError.log" -Append
30 | Write-Output "$(Get-Date -f G) : ERROR : $($Error[0])" | Out-File "$PSScriptRoot\PSError.log" -Append
31 | Exit
32 | }
33 |
34 | <### BEGIN SCRIPT ###>
35 | $StartScript = Get-Date
36 | $DateString = (Get-Date).ToString("yyyy-MM-dd")
37 | $BackupName = "$DateString-hMailServer"
38 |
39 | <# Clear out error variable #>
40 | $Error.Clear()
41 |
42 | <# Set counting variables that pass through functions #>
43 | Set-Variable -Name BackupSuccess -Value 0 -Option AllScope
44 | Set-Variable -Name DoBackupDataDir -Value 0 -Option AllScope
45 | Set-Variable -Name DoBackupDB -Value 0 -Option AllScope
46 | Set-Variable -Name MiscBackupSuccess -Value 0 -Option AllScope
47 | Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
48 | Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
49 | Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
50 | Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope
51 | Set-Variable -Name TotalHamFedMessages -Value 0 -Option AllScope
52 | Set-Variable -Name TotalSpamFedMessages -Value 0 -Option AllScope
53 | Set-Variable -Name HamFedMessageErrors -Value 0 -Option AllScope
54 | Set-Variable -Name SpamFedMessageErrors -Value 0 -Option AllScope
55 | Set-Variable -Name LearnedHamMessages -Value 0 -Option AllScope
56 | Set-Variable -Name LearnedSpamMessages -Value 0 -Option AllScope
57 |
58 | <# Remove trailing slashes from folder locations #>
59 | $hMSDir = $hMSDir -Replace('\\$','')
60 | $SevenZipDir = $SevenZipDir -Replace('\\$','')
61 | $MailDataDir = $MailDataDir -Replace('\\$','')
62 | $BackupTempDir = $BackupTempDir -Replace('\\$','')
63 | $BackupLocation = $BackupLocation -Replace('\\$','')
64 | $SADir = $SADir -Replace('\\$','')
65 | $SAConfDir = $SAConfDir -Replace('\\$','')
66 | $MySQLBINdir = $MySQLBINdir -Replace('\\$','')
67 |
68 | <# Validate folders #>
69 | ValidateFolders $hMSDir
70 | ValidateFolders $SevenZipDir
71 | ValidateFolders $MailDataDir
72 | ValidateFolders $BackupTempDir
73 | ValidateFolders $BackupLocation
74 | If ($UseSA) {
75 | ValidateFolders $SADir
76 | ValidateFolders $SAConfDir
77 | }
78 | If ($UseMySQL) {
79 | ValidateFolders $MySQLBINdir
80 | }
81 |
82 | <# Create hMailData folder if it doesn't exist #>
83 | If (-not(Test-Path "$BackupTempDir\hMailData" -PathType Container)) {md "$BackupTempDir\hMailData"}
84 |
85 | <# Delete old debug files and create new #>
86 | $EmailBody = "$PSScriptRoot\EmailBody.log"
87 | If (Test-Path $EmailBody) {Remove-Item -Force -Path $EmailBody}
88 | New-Item $EmailBody
89 | $DebugLog = "$BackupLocation\hMailServerDebug-$DateString.log"
90 | If (Test-Path $DebugLog) {Remove-Item -Force -Path $DebugLog}
91 | New-Item $DebugLog
92 | Write-Output "::: hMailServer Backup Routine $(Get-Date -f D) :::" | Out-File $DebugLog -Encoding ASCII -Append
93 | Write-Output " " | Out-File $DebugLog -Encoding ASCII -Append
94 | If ($UseHTML) {
95 | Write-Output "
96 |
97 |
98 |
99 | " | Out-File $EmailBody -Encoding ASCII -Append
100 | }
101 |
102 | <# Authenticate hMailServer COM #>
103 | $hMS = New-Object -COMObject hMailServer.Application
104 | $hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
105 |
106 | <# Get hMailServer Status #>
107 | $BootTime = [DateTime]::ParseExact((((Get-WmiObject -Class win32_operatingsystem).LastBootUpTime).Split(".")[0]), 'yyyyMMddHHmmss', $null)
108 | $hMSStartTime = $hMS.Status.StartTime
109 | $hMSSpamCount = $hMS.Status.RemovedSpamMessages
110 | $hMSVirusCount = $hMS.Status.RemovedViruses
111 | Debug "Last Reboot Time : $(($BootTime).ToString('yyyy-MM-dd HH:mm:ss')) ($(ElapsedTime $BootTime))"
112 | Debug "HMS Start Time : $hMSStartTime ($(ElapsedTime $hMSStartTime))"
113 | Debug "HMS Daily Spam Reject : $hMSSpamCount"
114 | Debug "HMS Daily Viruses Removed : $hMSVirusCount"
115 | If ($UseHTML) {
116 | Email "::: hMailServer Backup Routine :::"
117 | Email "$(Get-Date -f D)"
118 | Email " "
119 | Email "Last Reboot Time: $(($BootTime).ToString('yyyy-MM-dd HH:mm:ss'))"
120 | Email "HMS Start Time: $hMSStartTime"
121 | If ($hMSSpamCount -gt 0) {
122 | Email "HMS Daily Spam Reject count: $hMSSpamCount"
123 | } Else {
124 | Email "HMS Daily Spam Reject count: 0"
125 | }
126 | If ($hMSVirusCount -gt 0) {
127 | Email "HMS Daily Viruses Removed count: $hMSVirusCount"
128 | } Else {
129 | Email "HMS Daily Viruses Removed count: 0"
130 | }
131 | Email " "
132 | } Else {
133 | Email "::: hMailServer Backup Routine :::"
134 | Email " $(Get-Date -f D)"
135 | Email " "
136 | Email "Last Reboot Time: $(($BootTime).ToString('yyyy-MM-dd HH:mm:ss'))"
137 | Email "HMS Start Time: $hMSStartTime"
138 | Email "HMS Daily Spam Reject count: $hMSSpamCount"
139 | Email "HMS Daily Viruses Removed count: $hMSVirusCount"
140 | Email " "
141 | }
142 |
143 | <# Stop hMailServer & SpamAssassin services #>
144 | $BeginShutdownPeriod = Get-Date
145 | ServiceStop $hMSServiceName
146 | If ($UseSA) {ServiceStop $SAServiceName}
147 |
148 | <# Cycle Logs #>
149 | If ($CycleLogs) {CycleLogs}
150 |
151 | <# Update SpamAssassin #>
152 | If ($UseSA) {UpdateSpamassassin}
153 |
154 | <# Update Custom Rulesets #>
155 | If ($UseSA) {If ($UseCustomRuleSets) {UpdateCustomRulesets}}
156 |
157 | <# Update OpenPhish database files #>
158 | If ($UseSA) {If ($UseOpenPhish) {DownloadPhishFiles}}
159 |
160 | <# Backup files using RoboCopy #>
161 | If ($BackupDataDir) {BackuphMailDataDir}
162 |
163 | <# Backup database files #>
164 | If ($BackupDB) {BackupDatabases}
165 |
166 | <# Backup Miscellaneous Files #>
167 | If ($BackupMisc) {BackupMiscellaneousFiles}
168 |
169 | <# Report backup success #>
170 | Debug "----------------------------"
171 | If ($BackupSuccess -eq ($DoBackupDataDir + $DoBackupDB + $MiscBackupFiles.Count)) {
172 | Debug "All files backed up successfully"
173 | Email "[OK] Backed up data successfully"
174 | } Else {
175 | Debug "[ERROR] Backup count mismatch."
176 | Email "[ERROR] Backup count mismatch : Check Debug Log"
177 | }
178 |
179 | <# Restart SpamAssassin and hMailServer #>
180 | If ($UseSA) {ServiceStart $SAServiceName}
181 | ServiceStart $hMSServiceName
182 | Debug "----------------------------"
183 | Debug "hMailServer was out of service for $(ElapsedTime $BeginShutdownPeriod)"
184 |
185 | <# Prune hMailServer logs #>
186 | If ($PruneLogs) {PruneLogs}
187 |
188 | <# Prune backups #>
189 | If ($PruneBackups) {PruneBackups}
190 |
191 | <# Prune messages/empty folders older than N number of days #>
192 | If ($PruneMessages) {PruneMessages}
193 |
194 | <# Feed Beyesian database #>
195 | If ($UseSA) {If ($FeedBayes) {FeedBayes}}
196 |
197 | <# Compress backup into 7z archives #>
198 | If ($UseSevenZip) {MakeArchive}
199 |
200 | <# Check for updates #>
201 | CheckForUpdates
202 |
203 | <# Finish up and send email #>
204 | Debug "----------------------------"
205 | If (((Get-Item $DebugLog).length/1MB) -ge $MaxAttachmentSize) {
206 | Debug "Debug log larger than specified max attachment size. Do not attach to email message."
207 | Email "[INFO] Debug log larger than specified max attachment size. Log file stored in backup folder on server file system."
208 | }
209 | Debug "hMailServer Backup & Upload routine completed in $(ElapsedTime $StartScript)"
210 | Email " "
211 | Email "hMailServer Backup & Upload routine completed in $(ElapsedTime $StartScript)"
212 | If ($UseHTML) {Write-Output "
" | Out-File $EmailBody -Encoding ASCII -Append}
213 | EmailResults
--------------------------------------------------------------------------------
/hMailServerBackupDownload.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | .SYNOPSIS
4 | hMailServer Backup Download Utility
5 |
6 | .DESCRIPTION
7 | hMailServer Backup Download Utility
8 |
9 | .FUNCTIONALITY
10 | Looks for last backup at LetsUpload and downloads for restoration
11 |
12 | .PARAMETER
13 |
14 |
15 | .NOTES
16 |
17 |
18 | .EXAMPLE
19 |
20 |
21 | #>
22 |
23 | <### BEGIN SCRIPT ###>
24 | $StartScript = Get-Date
25 |
26 | <# Load required files #>
27 | Try {
28 | .("$PSScriptRoot\hMailServerBackupConfig.ps1")
29 | .("$PSScriptRoot\hMailServerBackupFunctions.ps1")
30 | }
31 | Catch {
32 | Write-Output "$(Get-Date) -f G) : ERROR : Unable to load supporting PowerShell Scripts" | out-file "$PSScriptRoot\PSError.log" -append
33 | Write-Output "$(Get-Date) -f G) : ERROR : $Error" | out-file "$PSScriptRoot\PSError.log" -append
34 | Exit
35 | }
36 |
37 | <# Clear out error variable #>
38 | $Error.Clear()
39 |
40 | <# Validate backup folder #>
41 | $BackupLocation = $BackupLocation -Replace('\\$','')
42 | ValidateFolders $BackupLocation
43 | $DownloadFolder = "$BackupLocation\hMailServer-Restoration"
44 | If (Test-Path $DownloadFolder) {Remove-Item -Force -Path $DownloadFolder -Recurse}
45 | New-Item -Path $DownloadFolder -ItemType Directory
46 |
47 | <# Delete old debug file and create new #>
48 | $EmailBody = "$PSScriptRoot\EmailBody.log"
49 | If (Test-Path $EmailBody) {Remove-Item -Force -Path $EmailBody}
50 | New-Item $EmailBody
51 | $DebugLog = "$BackupLocation\hMailServerDownloadRestore.log"
52 | If (Test-Path $DebugLog) {Remove-Item -Force -Path $DebugLog}
53 | New-Item $DebugLog
54 | Write-Output "::: hMailServer Download Offsite Backup Routine $(Get-Date -f D) :::" | Out-File $DebugLog -Encoding ASCII -Append
55 | Write-Output " " | Out-File $DebugLog -Encoding ASCII -Append
56 | If ($UseHTML) {
57 | Write-Output "
58 |
59 |
60 |
61 | hMailServer Backup & Offsite Upload
62 |
63 |
64 |
65 |
66 | " | Out-File $EmailBody -Encoding ASCII -Append
67 | }
68 |
69 | <# Authorize and get access token #>
70 | Debug "Getting access token from LetsUpload"
71 | $URIAuth = "https://letsupload.io/api/v2/authorize"
72 | $AuthBody = @{
73 | 'key1' = $APIKey1;
74 | 'key2' = $APIKey2;
75 | }
76 | Try{
77 | $Auth = Invoke-RestMethod -Method GET $URIAuth -Body $AuthBody -ContentType 'application/json; charset=utf-8'
78 | }
79 | Catch {
80 | Debug "[ERROR] LetsUpload Authentication : $Error"
81 | Email "[ERROR] LetsUpload Authentication : Check Debug Log"
82 | EmailResults
83 | Exit
84 | }
85 | $AccessToken = $Auth.data.access_token
86 | $AccountID = $Auth.data.account_id
87 | Debug "Access Token : $AccessToken"
88 | Debug "Account ID : $AccountID"
89 |
90 | <# Get folder_id of last upload #>
91 | $URIFolderListing = "https://letsupload.io/api/v2/folder/listing"
92 | $FLBody = @{
93 | 'access_token' = $AccessToken;
94 | 'account_id' = $AccountID;
95 | 'parent_folder_id' = "";
96 | }
97 | Try{
98 | $FolderListing = Invoke-RestMethod -Method GET $URIFolderListing -Body $FLBody -ContentType 'application/json; charset=utf-8'
99 | }
100 | Catch {
101 | Debug "[ERROR] obtaining backup folder ID : $Error"
102 | Email "[ERROR] obtaining backup folder ID : Check Debug Log"
103 | EmailResults
104 | Exit
105 | }
106 | $NewestBackup = $FolderListing.data.folders | Sort-Object date_added -Descending | Where {$_.folderName -match "hMailServer"} | Select -First 1
107 | $FolderID = $NewestBackup.id
108 | $FolderName = $NewestBackup.folderName
109 | Debug "Folder Name: $FolderName"
110 | Debug "Folder ID : $FolderID"
111 | Email "[OK] Latest backup : $FolderName"
112 |
113 | <# Get file listing within latest backup folder #>
114 | $URIFolderListing = "https://letsupload.io/api/v2/folder/listing"
115 | $FLBody = @{
116 | 'access_token' = $AccessToken;
117 | 'account_id' = $AccountID;
118 | 'parent_folder_id' = $FolderID;
119 | }
120 | Try{
121 | $FileListing = Invoke-RestMethod -Method GET $URIFolderListing -Body $FLBody -ContentType 'application/json; charset=utf-8'
122 | }
123 | Catch {
124 | Debug "[ERROR] obtaining backup file listing : $Error"
125 | Email "[ERROR] obtaining backup file listing : Check Debug Log"
126 | EmailResults
127 | Exit
128 | }
129 | $DownloadCount = ($FileListing.data.files).Count
130 | $DownloadNumber = 1
131 | Debug "File count: $DownloadCount"
132 | Debug "Starting file download"
133 | Email "[OK] $DownloadCount files to download"
134 |
135 | <# Loop through results and download files #>
136 | $FileListing.data.files | ForEach {
137 | $FileID = $_.id
138 | $FileName = $_.filename
139 | $FileURL = $_.url_file
140 | $RemoteFileSize = $_.fileSize
141 | $RemoteFileSizeFormatted = "{0:N2}" -f (($RemoteFileSize)/1MB)
142 | Debug "----------------------------"
143 | Debug "File $DownloadNumber of $DownloadCount"
144 | Debug "File ID : $FileID"
145 | Debug "File Name : $FileName"
146 | Debug "File Size : $RemoteFileSizeFormatted mb"
147 |
148 | $URIDownload = "https://letsupload.io/api/v2/file/download"
149 | $DLBody = @{
150 | 'access_token' = $AccessToken;
151 | 'account_id' = $AccountID;
152 | 'file_id' = $FileID;
153 | }
154 |
155 | <# Get download URL #>
156 | $GetURLTry = 1
157 | Do {
158 | $Error.Clear()
159 | Try{
160 | $FileDownload = Invoke-RestMethod -Method GET $URIDownload -Body $DLBody -ContentType 'application/json; charset=utf-8'
161 | If ($FileDownload._status -match "success") {
162 | $URLSuccess = $True
163 | } Else {
164 | Throw "[ERROR] Getting download URL on Try $GetURLTry"
165 | }
166 | $DownloadURL = $FileDownload.data.download_url
167 | Debug "Download URL: $DownloadURL"
168 | }
169 | Catch {
170 | Debug "[ERROR] Getting download URL : $Error"
171 | $URLSuccess = $False
172 | }
173 | $GetURLTry++
174 | } Until (($GetURLTry -eq 5) -or ($URLSuccess -eq $True))
175 |
176 | <# If get download URL success, then download file #>
177 | If ($URLSuccess -eq $False) {
178 | Debug "[ERROR] obtaining download URL : Tried $GetURLTry times : Giving up"
179 | Debug "[ERROR] obtaining download URL : $Error"
180 | Email "[ERROR] obtaining download URL : Check Debug Log"
181 | EmailResults
182 | Exit
183 | } Else {
184 | <# Download file using BITS #>
185 | $DownloadTries = 1
186 | Do {
187 | $Error.Clear()
188 | Try {
189 | Debug "Download Try $DownloadTries"
190 | $BeginDL = Get-Date
191 | Import-Module BitsTransfer
192 | Start-BitsTransfer -Source $DownloadURL -Destination "$DownloadFolder\$FileName"
193 | $LocalFileSize = (Get-Item "$DownloadFolder\$FileName").Length
194 | If ($RemoteFileSize -ne $LocalFileSize) {
195 | Throw "[ERROR] Remote and local file sizes do not match"
196 | }
197 | Debug "File $DownloadNumber downloaded in $(ElapsedTime $BeginDL)"
198 | $DownloadFileSuccess = $True
199 | }
200 | Catch {
201 | Debug "[ERROR] BITS downloading file $DownloadNumber of $FileCount : $Error"
202 | Debug "[ERROR] Remote file size: $RemoteFileSize"
203 | Debug "[ERROR] Local file size : $LocalFileSize"
204 | $DownloadFileSuccess = $False
205 | }
206 | $DownloadTries++
207 | } Until (($DownloadFileSuccess -eq $True) -or ($DownloadTries -eq 5))
208 | If ($DownloadFileSuccess -eq $False) {
209 | Debug "[ERROR] Downloading File : Tried $GetURLTry times : Giving up"
210 | Debug "[ERROR] Downloading File : $Error"
211 | Email "[ERROR] Downloading File : Check Debug Log"
212 | EmailResults
213 | Exit
214 | }
215 | }
216 | $DownloadNumber++
217 | }
218 |
219 | <# Count and compare remote to local files #>
220 | If ($DownloadCount -eq ((Get-ChildItem $DownloadFolder).Count)) {
221 | Debug "----------------------------"
222 | Debug "Download successful. $DownloadCount files downloaded to $DownloadFolder"
223 | Email "[OK] Download successful. $DownloadCount files downloaded to $DownloadFolder"
224 | } Else {
225 | Debug "[ERROR] Download unsuccessful : Remote and local file counts do not match!"
226 | Email "[ERROR] Download unsuccessful : Remote and local file counts do not match!"
227 | }
228 |
229 | <# Finish up and email results #>
230 | Debug "----------------------------"
231 | Debug "Script completed in $(ElapsedTime $StartScript)"
232 | Email " "
233 | Email "Script completed in $(ElapsedTime $StartScript)"
234 | Debug "Sending Email"
235 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -gt $MaxAttachmentSize)){
236 | Email "Debug log size exceeds maximum attachment size. Please see log file in script folder"
237 | }
238 | If ($UseHTML) {Write-Output "
" | Out-File $EmailBody -Encoding ASCII -Append}
239 | EmailResults
--------------------------------------------------------------------------------
/hMailServerBackupConfig.dist.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | .SYNOPSIS
4 | hMailServer Backup
5 |
6 | .DESCRIPTION
7 | Configuration for hMailServer Backup
8 |
9 | .FUNCTIONALITY
10 | Backs up hMailServer, compresses backup and uploads to LetsUpload.io
11 |
12 | .PARAMETER
13 |
14 |
15 | .NOTES
16 | Run at 11:58PM from task scheduler in order to properly cycle log files.
17 |
18 | .EXAMPLE
19 |
20 |
21 | #>
22 |
23 | <### USER VARIABLES ###>
24 | $VerboseConsole = $True # If true, will output debug to console
25 | $VerboseFile = $True # If true, will output debug to file
26 |
27 | <### DATA DIR BACKUP ###>
28 | $BackupDataDir = $True # If true, will backup data dir via robocopy
29 |
30 | <### MISCELLANEOUS BACKUP FILES ###>
31 | $BackupMisc = $True # True will backup misc files listed below
32 | $MiscBackupFiles = @( # Array of additional miscellaneous files to backup (use full path)
33 | "C:\hMailServer\Bin\hMailServer.INI"
34 | "C:\hMailServer\Events\EventHandlers.vbs"
35 | "C:\Program Files\JAM Software\SpamAssassin for Windows\etc\spamassassin\local.cf"
36 | )
37 |
38 | <### FOLDER LOCATIONS ###>
39 | $hMSDir = "C:\hMailServer" # hMailServer Install Directory
40 | $SADir = "C:\Program Files\JAM Software\SpamAssassin for Windows" # SpamAssassin Install Directory
41 | $SAConfDir = "C:\Program Files\JAM Software\SpamAssassin for Windows\etc\spamassassin" # SpamAssassin Conf Directory
42 | $MailDataDir = "C:\HMS-DATA" # hMailServer Data Dir
43 | $BackupTempDir = "C:\HMS-BACKUP-TEMP" # Temporary backup folder for RoboCopy to compare
44 | $BackupLocation = "C:\HMS-BACKUP" # Location archive files will be stored
45 | $MySQLBINdir = "C:\xampp\mysql\bin" # MySQL BIN folder location
46 |
47 | <### HMAILSERVER COM VARIABLES ###>
48 | $hMSAdminPass = "supersecretpassword" # hMailServer Admin password
49 |
50 | <### SPAMASSASSIN VARIABLES ###>
51 | $UseSA = $True # Specifies whether SpamAssassin is in use
52 | $UseCustomRuleSets = $True # Specifies whether to download and update KAM.cf
53 | $SACustomRules = @( # URLs of custom rulesets
54 | "https://www.pccc.com/downloads/SpamAssassin/contrib/KAM.cf"
55 | "https://www.pccc.com/downloads/SpamAssassin/contrib/nonKAMrules.cf"
56 | )
57 |
58 | <### OPENPHISH VARIABLES ###> # https://hmailserver.com/forum/viewtopic.php?t=40295
59 | $UseOpenPhish = $True # Specifies whether to update OpenPhish databases - for use with Phishing plugin for SA - requires wget in the system path
60 | $PhishFiles = @{
61 | "https://data.phishtank.com/data/online-valid.csv" = "$SAConfDir\phishtank-feed.csv"
62 | "https://openphish.com/feed.txt" = "$SAConfDir\openphish-feed.txt"
63 | }
64 | # "https://phishstats.info/phish_score.csv" = "$SAConfDir\phishstats-feed.csv" #OpenPhish is dead.
65 |
66 | <### WINDOWS SERVICE VARIABLES ###>
67 | $hMSServiceName = "hMailServer" # Name of hMailServer Service (check windows services to verify exact spelling)
68 | $SAServiceName = "SpamAssassin" # Name of SpamAssassin Service (check windows services to verify exact spelling)
69 | $ServiceTimeout = 5 # number of minutes to continue trying if service start or stop commands become unresponsive
70 |
71 | <### PRUNE BACKUPS VARIABLES ###>
72 | $PruneBackups = $True # If true, will delete local backups older than N days
73 | $DaysToKeepBackups = 5 # Number of days to keep backups - older backups will be deleted
74 |
75 | <### PRUNE MESSAGES VARIABLES ###>
76 | $DoDelete = $True # FOR TESTING - set to FALSE to run and report results without deleting messages and folders
77 | $PruneMessages = $True # True will run message pruning routine
78 | $PruneSubFolders = $True # True will prune messages in folders levels below name matching folders
79 | $PruneEmptySubFolders = $True # True will delete empty subfolders below the matching level unless a subfolder within contains messages
80 | $DaysBeforeDelete = 30 # Number of days to keep messages in pruned folders
81 | $SkipAccountPruning = "user@dom.com|a@b.com" # User accounts to skip - uses regex (disable with "" or $NULL)
82 | $SkipDomainPruning = "domain.tld|dom2.com" # Domains to skip - uses regex (disable with "" or $NULL)
83 | $PruneFolders = "Trash|Deleted|Junk|Spam|Folder-[0-9]{6}|Unsubscribes" # Names of IMAP folders you want to cleanup - uses regex
84 |
85 | <### FEED BAYES VARIABLES ###>
86 | $FeedBayes = $True # True will run Bayes feeding routine
87 | $DoSpamC = $True # FOR TESTING - set to FALSE to run and report results without feeding SpamC with spam/ham
88 | $BayesSubFolders = $True # True will feed messages from subfolders within regex name matching folders
89 | $BayesDays = 7 # Number of days worth of spam/ham to feed to bayes
90 | $HamFolders = "INBOX|Ham" # Ham folders to feed messages to spamC for bayes database - uses regex
91 | $SpamFolders = "Spam|Junk" # Spam folders to feed messages to spamC for bayes database - uses regex
92 | $SkipAccountBayes = "user@dom.com|a@b.com" # User accounts to skip - uses regex (disable with "" or $NULL)
93 | $SkipDomainBayes = "domain.tld|dom2.com" # Domains to skip - uses regex (disable with "" or $NULL)
94 | $SyncBayesJournal = $True # True will sync bayes_journal after feeding messages to SpamC
95 | $BackupBayesDatabase = $True # True will backup the bayes database to bayes_backup - NOT insert the file in the backup/upload routine
96 | $BayesBackupLocation = "C:\bayes_backup" # Bayes backup FILE
97 |
98 | <### MySQL VARIABLES ###>
99 | $BackupDB = $True # Specifies whether to run BackupDatabases function (options below)(FALSE will skip)
100 | $UseMySQL = $True # Specifies whether database used is MySQL
101 | $BackupAllMySQLDatbase = $True # True will backup all databases, not just hmailserver - must use ROOT user for this
102 | $MySQLDatabase = "hmailserver" # MySQL database name
103 | $MySQLUser = "root" # hMailServer database user
104 | $MySQLPass = "supersecretpassword" # hMailServer database password
105 | $MySQLPort = 3306 # MySQL port
106 |
107 | <### 7-ZIP VARIABLES ###>
108 | $UseSevenZip = $True # True will compress backup files into archive
109 | $PWProtectedArchive = $True # False = no-password zip archive, True = AES-256 encrypted multi-volume 7z archive
110 | $VolumeSize = "100m" # Size of archive volume parts - maximum 200m recommended - valid suffixes for size units are (b|k|m|g)
111 | $ArchivePassword = "supersecretpassword" # Password to 7z archive
112 |
113 | <### HMAILSERVER LOG VARIABLES ###>
114 | $PruneLogs = $True # If true, will delete logs in hMailServer \Logs folder older than N days
115 | $DaysToKeepLogs = 10 # Number of days to keep old hMailServer Logs
116 |
117 | <### CYCLE LOGS VARIABLES ###> # Array of logs to cycle - Full file path required - not limited to hmailserver log dir
118 | $CycleLogs = $True # True will cycle logs (rename with today's date)
119 | $LogsToCycle = @(
120 | "C:\hMailServer\Logs\hmailserver_events.log"
121 | "C:\hMailServer\Logs\spamd.log"
122 | )
123 |
124 | <### EMAIL VARIABLES ###>
125 | $EmailFrom = "notify@mydomain.tld"
126 | $EmailTo = "admin@mydomain.tld"
127 | $Subject = "hMailServer Nightly Backup"
128 | $SMTPServer = "mail.mydomain.tld"
129 | $SMTPAuthUser = "notify@mydomain.tld"
130 | $SMTPAuthPass = "supersecretpassword"
131 | $SMTPPort = 587
132 | $SSL = $True # If true, will use tls connection to send email
133 | $UseHTML = $True # If true, will format and send email body as html (with color!)
134 | $AttachDebugLog = $True # If true, will attach debug log to email report - must also select $VerboseFile
135 | $MaxAttachmentSize = 1 # Size in MB
136 |
137 | <### GMAIL VARIABLES ###>
138 | <# Alternate messaging in case of hMailServer failure #>
139 | <# "Less Secure Apps" must be enabled in gmail account settings #>
140 | $GmailUser = "notifier@gmail.com"
141 | $GmailPass = "supersecretpassword"
142 | $GmailTo = "1234567890@tmomail.net"
--------------------------------------------------------------------------------
/hMailServerBackupPruneMessagesTEST.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | .SYNOPSIS
4 | Prune Messages
5 |
6 | .DESCRIPTION
7 | Delete messages in specified folders older than N days
8 |
9 | .FUNCTIONALITY
10 | 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
11 | Deletes empty subfolders within matching folders if DeleteEmptySubFolders set to True in config
12 |
13 | .PARAMETER
14 |
15 |
16 | .NOTES
17 | Folder name matching occurs at any level folder
18 | Empty folders are assumed to be trash if they're located in this script
19 | Only empty folders found in levels BELOW matching level will be deleted
20 |
21 | .EXAMPLE
22 |
23 |
24 | #>
25 |
26 | <### USER VARIABLES ###>
27 | $hMSAdminPass = "secretpassword" # hMailServer Admin password
28 | $DoDelete = $False # FOR TESTING - set to false to run and report results without deleting messages and folders
29 | $PruneSubFolders = $True # True will prune all folders in levels below name matching folders
30 | $DeleteEmptySubFolders = $True # True will delete empty subfolders below the matching level unless a subfolder within contains messages
31 | $DaysBeforeDelete = 30 # Number of days to keep messages in pruned folders
32 | $PruneFolders = "2nd level test|2020-[0-1][0-9]-[0-3][0-9]$|Trash|Deleted|Junk|Spam|Unsubscribes" # Names of IMAP folders you want to cleanup - uses regex
33 |
34 | <# Functions copied from hMailServer Backup required for testing #>
35 | Function Debug ($DebugOutput) {Write-Host $DebugOutput}
36 | Function Email ($DebugOutput) {}
37 | Function ElapsedTime ($EndTime) {
38 | $TimeSpan = New-Timespan $EndTime
39 | If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "}
40 | If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "}
41 | If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"}
42 | If (($TimeSpan).TotalSeconds -lt 1) {
43 | $Return = "less than 1 second"
44 | } Else {
45 | $Return = "$Hours$Minutes$Seconds"
46 | }
47 | Return $Return
48 | }
49 | Function Plural ($Integer) {
50 | If ($Integer -eq 1) {$S = ""} Else {$S = "s"}
51 | Return $S
52 | }
53 |
54 | <# Begin hMailServerBackupPruneMessages #>
55 |
56 | Set-Variable -Name TotalDeletedMessages -Value 0 -Option AllScope
57 | Set-Variable -Name TotalDeletedFolders -Value 0 -Option AllScope
58 | Set-Variable -Name DeleteMessageErrors -Value 0 -Option AllScope
59 | Set-Variable -Name DeleteFolderErrors -Value 0 -Option AllScope
60 |
61 | Function GetSubFolders ($Folder) {
62 | $IterateFolder = 0
63 | $ArrayDeletedFolders = @()
64 | If ($Folder.SubFolders.Count -gt 0) {
65 | Do {
66 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
67 | $SubFolderName = $SubFolder.Name
68 | $SubFolderID = $SubFolder.ID
69 | If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder}
70 | If ($SubFolder.Messages.Count -gt 0) {
71 | If ($PruneSubFolders) {GetMessages $SubFolder}
72 | } Else {
73 | If ($DeleteEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
74 | }
75 | $IterateFolder++
76 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
77 | }
78 | If ($DeleteEmptySubFolders) {
79 | $ArrayDeletedFolders | ForEach {
80 | $CheckFolder = $Folder.SubFolders.ItemByDBID($_)
81 | $FolderName = $CheckFolder.Name
82 | If (SubFoldersEmpty $CheckFolder) {
83 | Try {
84 | If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
85 | $TotalDeletedFolders++
86 | Debug "Deleted empty subfolder $FolderName in $AccountAddress"
87 | }
88 | Catch {
89 | $DeleteFolderErrors++
90 | Debug "[ERROR] Deleting empty subfolder $FolderName in $AccountAddress"
91 | Debug "[ERROR] : $Error"
92 | }
93 | $Error.Clear()
94 | }
95 | }
96 | }
97 | $ArrayDeletedFolders.Clear()
98 | }
99 |
100 | Function SubFoldersEmpty ($Folder) {
101 | $IterateFolder = 0
102 | If ($Folder.SubFolders.Count -gt 0) {
103 | Do {
104 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
105 | If ($SubFolder.Messages.Count -gt 0) {
106 | Return $False
107 | Break
108 | }
109 | If ($SubFolder.SubFolders.Count -gt 0) {
110 | SubFoldersEmpty $SubFolder
111 | }
112 | $IterateFolder++
113 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
114 | } Else {
115 | Return $True
116 | }
117 | }
118 |
119 | Function GetMatchFolders ($Folder) {
120 | $IterateFolder = 0
121 | If ($Folder.SubFolders.Count -gt 0) {
122 | Do {
123 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
124 | $SubFolderName = $SubFolder.Name
125 | If ($SubFolderName -match [regex]$PruneFolders) {
126 | GetSubFolders $SubFolder
127 | GetMessages $SubFolder
128 | } Else {
129 | GetMatchFolders $SubFolder
130 | }
131 | $IterateFolder++
132 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
133 | }
134 | }
135 |
136 | Function GetMessages ($Folder) {
137 | $IterateMessage = 0
138 | $ArrayMessagesToDelete = @()
139 | $DeletedMessages = 0
140 | If ($Folder.Messages.Count -gt 0) {
141 | Do {
142 | $Message = $Folder.Messages.Item($IterateMessage)
143 | If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
144 | $ArrayMessagesToDelete += $Message.ID
145 | }
146 | $IterateMessage++
147 | } Until ($IterateMessage -eq $Folder.Messages.Count)
148 | }
149 | $ArrayMessagesToDelete | ForEach {
150 | $AFolderName = $Folder.Name
151 | Try {
152 | If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
153 | $DeletedMessages++
154 | $TotalDeletedMessages++
155 | }
156 | Catch {
157 | $DeleteMessageErrors++
158 | Debug "[ERROR] Deleting messages from folder $AFolderName in $AccountAddress"
159 | Debug "[ERROR] $Error"
160 | }
161 | $Error.Clear()
162 | }
163 | If ($DeletedMessages -gt 0) {
164 | Debug "Deleted $DeletedMessages message$(Plural $DeletedMessages) from $AFolderName in $AccountAddress"
165 | }
166 | $ArrayMessagesToDelete.Clear()
167 | }
168 |
169 | Function PruneMessages {
170 |
171 | $Error.Clear()
172 | $BeginDeletingOldMessages = Get-Date
173 | Debug "----------------------------"
174 | Debug "Begin deleting messages older than $DaysBeforeDelete days"
175 | If (-not($DoDelete)) {
176 | Debug "Delete disabled - Test Run ONLY"
177 | }
178 |
179 | <# Authenticate hMailServer COM #>
180 | $hMS = New-Object -COMObject hMailServer.Application
181 | $hMS.Authenticate("Administrator", $hMSAdminPass) | Out-Null
182 |
183 | $IterateDomains = 0
184 | Do {
185 | $hMSDomain = $hMS.Domains.Item($IterateDomains)
186 | If ($hMSDomain.Active) {
187 | $IterateAccounts = 0
188 | Do {
189 | $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
190 | If ($hMSAccount.Active) {
191 | $AccountAddress = $hMSAccount.Address
192 | $IterateIMAPFolders = 0
193 | If ($hMSAccount.IMAPFolders.Count -gt 0) {
194 | Do {
195 | $hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
196 | If ($hMSIMAPFolder.Name -match [regex]$PruneFolders) {
197 | If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
198 | GetSubFolders $hMSIMAPFolder
199 | } # IF SUBFOLDER COUNT > 0
200 | GetMessages $hMSIMAPFolder
201 | } # IF FOLDERNAME MATCH REGEX
202 | Else {
203 | GetMatchFolders $hMSIMAPFolder
204 | } # IF NOT FOLDERNAME MATCH REGEX
205 | $IterateIMAPFolders++
206 | } Until ($IterateIMAPFolders -eq $hMSAccount.IMAPFolders.Count)
207 | } # IF IMAPFOLDER COUNT > 0
208 | } #IF ACCOUNT ACTIVE
209 | $IterateAccounts++
210 | } Until ($IterateAccounts -eq $hMSDomain.Accounts.Count)
211 | } # IF DOMAIN ACTIVE
212 | $IterateDomains++
213 | } Until ($IterateDomains -eq $hMS.Domains.Count)
214 |
215 | If ($DeleteMessageErrors -gt 0) {
216 | Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
217 | Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
218 | } Else {
219 | If ($TotalDeletedMessages -gt 0) {
220 | Debug "Finished pruning $TotalDeletedMessages message$(Plural $TotalDeletedMessages) in $(ElapsedTime $BeginDeletingOldMessages)"
221 | Email "[OK] Finished pruning $TotalDeletedMessages message$(Plural $TotalDeletedMessages) in $(ElapsedTime $BeginDeletingOldMessages)"
222 | } Else {
223 | Debug "No messages older than $DaysBeforeDelete days to prune"
224 | Email "[OK] No messages older than $DaysBeforeDelete days to prune"
225 | }
226 | }
227 | If ($DeleteFolderErrors -gt 0) {
228 | Debug "Deleting Empty Folders : $DeleteFolderErrors Error$(Plural $DeleteFolderErrors) present"
229 | Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Error$(Plural $DeleteFolderErrors) present : Check debug log"
230 | } Else {
231 | If ($TotalDeletedFolders -gt 0) {
232 | Debug "Finished deleting $TotalDeletedFolders empty subfolder$(Plural $TotalDeletedFolders)"
233 | Email "[OK] Deleted $TotalDeletedFolders empty subfolder$(Plural $TotalDeletedFolders)"
234 | } Else {
235 | Debug "No empty subfolders deleted"
236 | Email "[OK] No empty subfolders deleted"
237 | }
238 | }
239 | }
240 |
241 | PruneMessages
--------------------------------------------------------------------------------
/hMailServerBackupFunctions.ps1:
--------------------------------------------------------------------------------
1 | <#
2 |
3 | .SYNOPSIS
4 | hMailServer Backup
5 |
6 | .DESCRIPTION
7 | Common Code for hMailServer Backup
8 |
9 | .FUNCTIONALITY
10 | Backs up hMailServer, compresses backup and uploads to LetsUpload
11 |
12 | .PARAMETER
13 |
14 |
15 | .NOTES
16 | Run at 11:58PM from task scheduler in order to properly cycle log files.
17 |
18 | .EXAMPLE
19 |
20 |
21 | #>
22 |
23 | <# Miscellaneous Functions #>
24 |
25 | Function Debug ($DebugOutput) {
26 | If ($VerboseFile) {Write-Output "$(Get-Date -f G) : $DebugOutput" | Out-File $DebugLog -Encoding ASCII -Append}
27 | If ($VerboseConsole) {Write-Host "$(Get-Date -f G) : $DebugOutput"}
28 | }
29 |
30 | Function Email ($EmailOutput) {
31 | If ($UseHTML){
32 | If ($EmailOutput -match "\[OK\]") {$EmailOutput = $EmailOutput -Replace "\[OK\]","[OK]"}
33 | If ($EmailOutput -match "\[INFO\]") {$EmailOutput = $EmailOutput -Replace "\[INFO\]","[INFO]"}
34 | If ($EmailOutput -match "\[ERROR\]") {$EmailOutput = $EmailOutput -Replace "\[ERROR\]","[ERROR]"}
35 | If ($EmailOutput -match "^\s$") {$EmailOutput = $EmailOutput -Replace "\s"," "}
36 | Write-Output "| $EmailOutput |
" | Out-File $EmailBody -Encoding ASCII -Append
37 | } Else {
38 | Write-Output $EmailOutput | Out-File $EmailBody -Encoding ASCII -Append
39 | }
40 | }
41 |
42 | Function Plural ($Integer) {
43 | If ($Integer -eq 1) {$S = ""} Else {$S = "s"}
44 | Return $S
45 | }
46 |
47 | Function EmailResults {
48 | Try {
49 | $Body = (Get-Content -Path $EmailBody | Out-String )
50 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Attachment = New-Object System.Net.Mail.Attachment $DebugLog}
51 | $Message = New-Object System.Net.Mail.Mailmessage $EmailFrom, $EmailTo, $Subject, $Body
52 | $Message.IsBodyHTML = $UseHTML
53 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Message.Attachments.Add($DebugLog)}
54 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort
55 | $SMTP.EnableSsl = $SSL
56 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass);
57 | $SMTP.Send($Message)
58 | }
59 | Catch {
60 | Debug "Email ERROR : $($Error[0])"
61 | }
62 | }
63 |
64 | Function GmailResults ($GBody){
65 | Try {
66 | $Subject = "hMailServer Backup Problem"
67 | $Message = New-Object System.Net.Mail.Mailmessage $GmailUser, $GmailTo, $Subject, $GBody
68 | $Message.IsBodyHTML = $False
69 | $SMTP = New-Object System.Net.Mail.SMTPClient("smtp.gmail.com", 587)
70 | $SMTP.EnableSsl = $True
71 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($GmailUser, $GmailPass);
72 | $SMTP.Send($Message)
73 | }
74 | Catch {
75 | Debug "Gmail ERROR : $($Error[0])"
76 | }
77 | }
78 |
79 | Function ValidateFolders ($Folder) {
80 | If (-not(Test-Path $Folder)) {
81 | Debug "[ERROR] Folder location $Folder does not exist : Quitting script"
82 | Email "[ERROR] Folder location $Folder does not exist : Quitting script"
83 | EmailResults
84 | Exit
85 | }
86 | }
87 |
88 | Function ElapsedTime ($EndTime) {
89 | $TimeSpan = New-Timespan $EndTime
90 | If (([math]::Floor(($TimeSpan).Days)) -eq 0) {$Days = ""} ElseIf (([math]::Floor(($TimeSpan).Days)) -eq 1) {$Days = "1 day "} Else {$Days = "$([math]::Floor(($TimeSpan).Days)) days "}
91 | If (([math]::Floor(($TimeSpan).Hours)) -eq 0) {$Hours = ""} ElseIf (([math]::Floor(($TimeSpan).Hours)) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([math]::Floor(($TimeSpan).Hours)) hours "}
92 | If (([math]::Floor(($TimeSpan).Minutes)) -eq 0) {$Minutes = ""} ElseIf (([math]::Floor(($TimeSpan).Minutes)) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([math]::Floor(($TimeSpan).Minutes)) minutes "}
93 | If (([math]::Floor(($TimeSpan).Seconds)) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([math]::Floor(($TimeSpan).Seconds)) seconds"}
94 |
95 | If (($TimeSpan).TotalSeconds -lt 1) {
96 | $Return = "less than 1 second"
97 | } Else {
98 | $Return = "$Days$Hours$Minutes$Seconds"
99 | }
100 | Return $Return
101 | }
102 |
103 | <# Service start and stop functions #>
104 | Function ServiceStop ($ServiceName) {
105 | <# Check to see if already stopped #>
106 | $BeginShutdownRoutine = Get-Date
107 | Debug "----------------------------"
108 | Debug "Stop $ServiceName"
109 | $ServiceRunning = $False
110 | (Get-Service $ServiceName).Refresh()
111 | If ((Get-Service $ServiceName).Status -eq 'Stopped'){
112 | Debug "$ServiceName already STOPPED. Nothing to stop. Check event logs."
113 | Email "[INFO] $ServiceName : service already STOPPED. Check event logs."
114 | } Else {
115 | Debug "$ServiceName running. Preparing to stop service."
116 | $ServiceRunning = $True
117 | }
118 |
119 | <# Stop service routine #>
120 | If ($ServiceRunning) {
121 | Debug "$ServiceName shutting down."
122 | $BeginShutdown = Get-Date
123 | Do {
124 | Stop-Service $ServiceName
125 | # Start-Sleep -Seconds 5
126 | (Get-Service $ServiceName).Refresh()
127 | $ServiceStatus = (Get-Service $ServiceName).Status
128 | } Until (((New-Timespan $BeginShutdown).TotalMinutes -gt $ServiceTimeout) -or ($ServiceStatus -eq "Stopped"))
129 |
130 | If ($ServiceStatus -ne "Stopped"){
131 | Debug "$ServiceName failed to stop."
132 | GmailResults "$ServiceName failed to stop during backup routine! Check status NOW!"
133 | Break
134 | } Else {
135 | Debug "$ServiceName successfully stopped in $(ElapsedTime $BeginShutdownRoutine)"
136 | Email "[OK] $ServiceName stopped"
137 | }
138 | }
139 | }
140 |
141 | Function ServiceStart ($ServiceName) {
142 | <# Check to see if already running #>
143 | $BeginStartupRoutine = Get-Date
144 | Debug "----------------------------"
145 | Debug "Start $ServiceName"
146 | $ServiceStopped = $False
147 | (Get-Service $ServiceName).Refresh()
148 | If ((Get-Service $ServiceName).Status -eq 'Running'){
149 | Debug "$ServiceName already RUNNING. Nothing to start."
150 | Email "[INFO] $ServiceName : service already RUNNING. Check event logs."
151 | } Else {
152 | Debug "$ServiceName not running. Preparing to start service."
153 | $ServiceStopped = $True
154 | }
155 |
156 | <# Start service routine #>
157 | If ($ServiceStopped) {
158 | Debug "$ServiceName starting up"
159 | $BeginStartup = Get-Date
160 | Do {
161 | Start-Service $ServiceName
162 | # Start-Sleep -Seconds 5
163 | (Get-Service $ServiceName).Refresh()
164 | $ServiceStatus = (Get-Service $ServiceName).Status
165 | } Until (((New-Timespan $BeginStartup).TotalMinutes -gt $ServiceTimeout) -or ($ServiceStatus -eq "Running"))
166 |
167 | If ($ServiceStatus -ne "Running"){
168 | Debug "$ServiceName failed to start"
169 | GmailResults "$ServiceName failed to start during backup routine! Check status NOW!"
170 | Break
171 | } Else {
172 | Debug "$ServiceName successfully started in $(ElapsedTime $BeginStartupRoutine)"
173 | Email "[OK] $ServiceName started"
174 | }
175 | }
176 | }
177 |
178 | <# Update SpamAssassin Function #>
179 | Function UpdateSpamassassin {
180 | Debug "----------------------------"
181 | Debug "Updating SpamAssassin"
182 | $BeginSAUpdate = Get-Date
183 | $SAUD = "$SADir\sa-update.exe"
184 | Try {
185 | $SAUpdate = & $SAUD -v --nogpg --channel updates.spamassassin.org | Out-String
186 | Debug $SAUpdate
187 | Debug "Finished updating SpamAssassin in $(ElapsedTime $BeginSAUpdate)"
188 | Email "[OK] SpamAssassin updated"
189 | If ($SAUpdate -match "Update finished, no fresh updates were available"){
190 | Email "[INFO] No fresh updates available"
191 | }
192 | }
193 | Catch {
194 | Debug "[ERROR] SpamAssassin update : $($Error[0])"
195 | Email "[ERROR] SpamAssassin update : Check Debug Log"
196 | }
197 | }
198 |
199 | <# Update custom rulesets function #>
200 | Function UpdateCustomRulesets {
201 | Debug "----------------------------"
202 | Debug "Updating custom Spamassassin rule sets"
203 | $CustomRuleSuccess = 0
204 | $CustomRuleSetCount = $SACustomRules.Count
205 | $BeginUpdatingCustomRuleSets = Get-Date
206 | $SACustomRules | ForEach {
207 | $CustomRuleURI = $_
208 | $URLFileName = $_.Substring($_.LastIndexOf("/") + 1)
209 | $CustomRuleOutput = "$SAConfDir\$URLFileName"
210 | Try {
211 | Start-BitsTransfer -Source $CustomRuleURI -Destination $CustomRuleOutput -ErrorAction Stop
212 | Debug "$URLFileName update successful"
213 | $CustomRuleSuccess++
214 | }
215 | Catch {
216 | Debug "[ERROR] : Unable to update $URLFileName : $($Error[0])"
217 | }
218 | }
219 | If ($CustomRuleSuccess -eq $CustomRuleSetCount) {
220 | Debug "All $CustomRuleSetCount custom rule sets updated successfully in $(ElapsedTime $BeginUpdatingCustomRuleSets)"
221 | Email "[OK] $CustomRuleSetCount Custom rules updated"
222 | } Else {
223 | Email "[ERROR] : Custom ruleset update unsuccessful : Check debug log"
224 | }
225 | }
226 |
227 | <# Update OpenPhish database files #>
228 | Function DownloadPhishFiles {
229 | Debug "----------------------------"
230 | Debug "Start downloading OpenPhish databases"
231 | $BeginOpenPhish = Get-Date
232 | $PhishUpdateSuccess = 0
233 | ForEach($Source in $PhishFiles.Keys){
234 | $FileName = $(([System.IO.DirectoryInfo]$PhishFiles[$Source]).Name)
235 | $TempFile = "$BackupTempDir\$FileName"
236 | Try {
237 | & cmd /C wget --tries=3 -q -U "phishtank/downloader" -O $TempFile $Source
238 | If ($LastExitCode -ne 0) {Throw "[ERROR] Error downloading file $Source"}
239 | Debug "Successfully downloaded file $Source"
240 | Copy-Item $TempFile -Destination $PhishFiles[$Source] -Force
241 | Remove-Item $TempFile -Force
242 | $PhishUpdateSuccess++
243 | }
244 | Catch {
245 | Debug $($Error[0])
246 | Switch ($LastExitCode) {
247 | 0{Debug "[ERROR] No problems occurred."}
248 | 1{Debug "[ERROR] Generic error code."}
249 | 2{Debug "[ERROR] Parse error---for instance, when parsing command-line options, the .wgetrc or .netrc..."}
250 | 3{Debug "[ERROR] File I/O error."}
251 | 4{Debug "[ERROR] Network failure."}
252 | 5{Debug "[ERROR] SSL verification failure."}
253 | 6{Debug "[ERROR] Username/password authentication failure."}
254 | 7{Debug "[ERROR] Protocol errors."}
255 | 8{Debug "[ERROR] Server issued an error response."}
256 | }
257 | }
258 | }
259 | <# Report Misc Backup Success #>
260 | If ($PhishUpdateSuccess -eq $PhishFiles.Count) {
261 | Debug "All $($PhishFiles.Count) OpenPhish files successfully downloaded in $(ElapsedTime $BeginOpenPhish)"
262 | Email "[OK] $($PhishFiles.Count) OpenPhish files updated"
263 | } Else {
264 | Debug "[ERROR] Failed to download $($PhishFiles.Count - $PhishUpdateSuccess) of $($PhishFiles.Count) OpenPhish files"
265 | Debug "Finished updating OpenPhish files in $(ElapsedTime $BeginOpenPhish)"
266 | Email "[ERROR] $($PhishFiles.Count - $PhishUpdateSuccess) OpenPhish files failed to download : Check debug log"
267 | }
268 | }
269 |
270 | <# Backup hMailServer data dir function #>
271 | Function BackuphMailDataDir {
272 | $DoBackupDataDir++
273 | Debug "----------------------------"
274 | Debug "Start backing up datadir with RoboCopy"
275 | $BeginRobocopy = Get-Date
276 | Try {
277 | $RoboCopy = & robocopy $MailDataDir "$BackupTempDir\hMailData" /mir /ndl /r:43200 /np /w:1 | Out-String
278 | Debug $RoboCopy
279 | Debug "Finished backing up data dir in $(ElapsedTime $BeginRobocopy)"
280 | $RoboStats = $RoboCopy.Split([Environment]::NewLine) | Where-Object {$_ -match 'Files\s:\s+\d'}
281 | $RoboStats | ConvertFrom-String -Delimiter "\s+" -PropertyNames Nothing, Files, Colon, Total, Copied, Skipped, Mismatch, Failed, Extras | ForEach {
282 | $Copied = $_.Copied
283 | $Mismatch = $_.Mismatch
284 | $Failed = $_.Failed
285 | $Extras = $_.Extras
286 | }
287 | If (($Mismatch -gt 0) -or ($Failed -gt 0)) {
288 | Throw "Robocopy MISMATCH or FAILED exists"
289 | }
290 | $BackupSuccess++
291 | Debug "Robocopy backup success: $Copied new, $Extras deleted, $Mismatch mismatched, $Failed failed"
292 | Email "[OK] DataDir backed up: $Copied new, $Extras del"
293 | }
294 | Catch {
295 | Debug "[ERROR] DataDir RoboCopy : $($Error[0])"
296 | Email "[ERROR] DataDir RoboCopy : Check Debug Log"
297 | }
298 | }
299 |
300 | <# Backup database function #>
301 | Function BackupDatabases {
302 | $BeginDBBackup = Get-Date
303 | If ($UseMySQL) {
304 | $Error.Clear()
305 | $DoBackupDB++
306 | Debug "----------------------------"
307 | Debug "Begin backing up MySQL"
308 | If (Test-Path "$BackupTempDir\hMailData\MYSQLDump_*.sql") {
309 | Get-ChildItem "$BackupTempDir\hMailData" | Where {$_.Extension -match "sql"} | ForEach {
310 | $OldMySQLDump = $_.Name
311 | Debug "Deleting old MySQL database dump : $OldMySQLDump"
312 | Try {
313 | Remove-Item "$BackupTempDir\hMailData\$OldMySQLDump" -ErrorAction Stop
314 | Debug "$OldMySQLDump database successfully deleted"
315 | }
316 | Catch {
317 | Debug "[ERROR] Old MySQL database delete : $($Error[0])"
318 | Email "[ERROR] Old MySQL database delete : Check Debug Log"
319 | }
320 | }
321 | }
322 | $MySQLDump = "$MySQLBINdir\mysqldump.exe"
323 | $MySQLDumpPass = "-p$MySQLPass"
324 | [System.IO.FileInfo]$MySQLDumpFile = "$BackupTempDir\hMailData\MYSQLDump_$((Get-Date).ToString('yyyy-MM-dd')).sql"
325 | Try {
326 | If ($BackupAllMySQLDatbase) {
327 | & $MySQLDump -u $MySQLUser $MySQLDumpPass --all-databases --result-file=$MySQLDumpFile
328 | } Else {
329 | & $MySQLDump -u $MySQLUser $MySQLDumpPass $MySQLDatabase --result-file=$MySQLDumpFile
330 | }
331 | $BackupSuccess++
332 | Debug "MySQL successfully dumped $($MySQLDumpFile.Name) in $(ElapsedTime $BeginDBBackup)"
333 | }
334 | Catch {
335 | Debug "[ERROR] MySQL Dump : $($Error[0])"
336 | Email "[ERROR] MySQL Dump : Check Debug Log"
337 | }
338 | } Else {
339 | Debug "----------------------------"
340 | Debug "Begin backing up internal database"
341 | Debug "Copy internal database to backup folder"
342 | Try {
343 | $RoboCopyIDB = & robocopy "$hMSDir\Database" "$BackupTempDir\hMailData\Database" /mir /ndl /r:43200 /np /w:1 | Out-String
344 | $BackupSuccess++
345 | Debug $RoboCopyIDB
346 | Debug "Internal DB successfully backed up in $(ElapsedTime $BeginDBBackup)"
347 | }
348 | Catch {
349 | Debug "[ERROR] RoboCopy Internal DB : $($Error[0])"
350 | Email "[ERROR] RoboCopy Internal DB : Check Debug Log"
351 | }
352 | }
353 | }
354 |
355 | <# Backup misc files function #>
356 | Function BackupMiscellaneousFiles {
357 | Debug "----------------------------"
358 | Debug "Begin backing up miscellaneous files"
359 | $MiscBackupSuccess = 0
360 | $MiscBackupFiles | ForEach {
361 | $MBUF = $_
362 | $MBUFName = Split-Path -Path $MBUF -Leaf
363 | If (Test-Path "$BackupTempDir\hMailData\$MBUFName") {
364 | Try {
365 | Remove-Item -Force -Path "$BackupTempDir\hMailData\$MBUFName"
366 | # Debug "Previously backed up $MBUFName successfully deleted"
367 | }
368 | Catch {
369 | Debug "[ERROR] Could not delete previously backed up $MBUFName. Check Debug log for more info."
370 | }
371 | }
372 | If (Test-Path $MBUF) {
373 | Try {
374 | Copy-Item -Path $MBUF -Destination "$BackupTempDir\hMailData"
375 | $BackupSuccess++
376 | $MiscBackupSuccess++
377 | Debug "$MBUFName successfully backed up"
378 | }
379 | Catch {
380 | Debug "[ERROR] $MBUF Backup : $($Error[0])"
381 | Email "[ERROR] Backup $MBUFName : Check Debug Log"
382 | }
383 | } Else {
384 | Debug "$MBUF copy ERROR : File path not validated"
385 | }
386 | }
387 |
388 | <# Report Misc Backup Success #>
389 | If ($MiscBackupSuccess -eq $MiscBackupFiles.Count) {
390 | Debug "All $($MiscBackupFiles.Count) misc files backed up"
391 | } Else {
392 | Debug "[ERROR] Failed to backup $($MiscBackupFiles.Count - $MiscBackupSuccess) of $($MiscBackupFiles.Count) misc files"
393 | }
394 | }
395 |
396 | <# 7-zip archive creation function #>
397 | Function MakeArchive {
398 | $StartArchive = Get-Date
399 | Debug "----------------------------"
400 | Debug "Create archive : $BackupName"
401 | Debug "Archive folder : $BackupTempDir"
402 | $SevenZipExe = "$hMSDir\Bin\7za.exe"
403 | $VolumeSwitch = "-v$VolumeSize"
404 | $PWSwitch = "-p$ArchivePassword"
405 | Try {
406 | If ($PWProtectedArchive) {
407 | $SevenZip = & cmd /c $SevenZipExe a $VolumeSwitch -t7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -mhe=on $PWSwitch "$BackupLocation\$BackupName\$BackupName.7z" "$BackupTempDir\*" | Out-String
408 | } Else {
409 | $SevenZip = & cmd /c $SevenZipExe a -tzip "$BackupLocation\$BackupName\$BackupName.zip" "$BackupTempDir\*" | Out-String
410 | }
411 | Debug $SevenZip
412 | Debug "Archive creation finished in $(ElapsedTime $StartArchive)"
413 | Debug "Wait a few seconds to make sure archive is finished"
414 | Email "[OK] 7z archive created"
415 | Start-Sleep -Seconds 3
416 | }
417 | Catch {
418 | Debug "[ERROR] Archive Creation : $($Error[0])"
419 | Email "[ERROR] Archive Creation : Check Debug Log"
420 | Email "[ERROR] Archive Creation : $($Error[0])"
421 | EmailResults
422 | Exit
423 | }
424 | }
425 |
426 |
427 | <# Cycle Logs #>
428 | Function CycleLogs {
429 | Debug "----------------------------"
430 | Debug "Cycling Logs"
431 | $LogsToCycle | ForEach {
432 | $FullName = (Get-Item $_).FullName
433 | $BaseName = (Get-Item $_).BaseName
434 | $FileExt = (Get-Item $_).Extension
435 | If (Test-Path $FullName) {
436 | $NewLogName = $BaseName+"_"+$((Get-Date).ToString('yyyy-MM-dd'))+$FileExt
437 | Try {
438 | Rename-Item $FullName $NewLogName -ErrorAction Stop
439 | Debug "Cylcled $NewLogName"
440 | }
441 | Catch {
442 | Debug "[ERROR] $FullName log cycling ERROR : $($Error[0])"
443 | }
444 | } Else {
445 | Debug "[ERROR] $FullName not found"
446 | }
447 | }
448 | }
449 |
450 | <# Prune hMailServer logs #>
451 | Function PruneLogs {
452 | $FilesToDel = Get-ChildItem -Path "$hMSDir\Logs" | Where-Object {$_.LastWriteTime -lt ((Get-Date).AddDays(-$DaysToKeepLogs))}
453 | $CountDelLogs = $FilesToDel.Count
454 | If ($CountDelLogs -gt 0){
455 | Debug "----------------------------"
456 | Debug "Begin pruning hMailServer logs older than $DaysToKeepLogs days"
457 | $EnumCountDelLogs = 0
458 | Try {
459 | $FilesToDel | ForEach {
460 | $FullName = $_.FullName
461 | $Name = $_.Name
462 | If (Test-Path $_.FullName -PathType Leaf) {
463 | Remove-Item -Force -Path $FullName
464 | Debug "Deleted file : $Name"
465 | $EnumCountDelLogs++
466 | }
467 | }
468 | If ($CountDelLogs -eq $EnumCountDelLogs) {
469 | Debug "Successfully pruned $CountDelLogs hMailServer log$(Plural $CountDelLogs)"
470 | Email "[OK] Pruned hMailServer logs older than $DaysToKeepLogs days"
471 | } Else {
472 | Debug "[ERROR] Prune hMailServer logs : Filecount does not match delete count"
473 | Email "[ERROR] Prune hMailServer logs : Check Debug Log"
474 | }
475 | }
476 | Catch {
477 | Debug "[ERROR] Prune hMailServer logs : $($Error[0])"
478 | Email "[ERROR] Prune hMailServer logs : Check Debug Log"
479 | }
480 | }
481 | }
482 |
483 | <# Prune Backups Function #>
484 | Function PruneBackups {
485 | $FilesToDel = Get-ChildItem -Path $BackupLocation | Where-Object {$_.LastWriteTime -lt ((Get-Date).AddDays(-$DaysToKeepBackups))}
486 | $CountDel = $FilesToDel.Count
487 | If ($CountDel -gt 0) {
488 | Debug "----------------------------"
489 | Debug "Begin pruning local backups older than $DaysToKeepBackups days"
490 | $EnumCountDel = 0
491 | Try {
492 | $FilesToDel | ForEach {
493 | $FullName = $_.FullName
494 | $Name = $_.Name
495 | If (Test-Path $_.FullName -PathType Container) {
496 | Remove-Item -Force -Recurse -Path $FullName
497 | Debug "Deleted folder: $Name"
498 | $EnumCountDel++
499 | }
500 | If (Test-Path $_.FullName -PathType Leaf) {
501 | Remove-Item -Force -Path $FullName
502 | Debug "Deleted file : $Name"
503 | $EnumCountDel++
504 | }
505 | }
506 | If ($CountDel -eq $EnumCountDel) {
507 | Debug "Successfully pruned $CountDel item$(Plural $CountDel)"
508 | Email "[OK] Pruned backups older than $DaysToKeepBackups days"
509 | } Else {
510 | Debug "[ERROR] Prune backups : Filecount does not match delete count"
511 | Email "[ERROR] Prune backups : Check Debug Log"
512 | }
513 | }
514 | Catch {
515 | Debug "[ERROR] Prune backups : $($Error[0])"
516 | Email "[ERROR] Prune backups : Check Debug Log"
517 | }
518 | }
519 | }
520 |
521 | <# Prune Messages Functions #>
522 | Function GetSubFolders ($Folder) {
523 | $IterateFolder = 0
524 | $ArrayDeletedFolders = @()
525 | If ($Folder.SubFolders.Count -gt 0) {
526 | Do {
527 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
528 | $SubFolderName = $SubFolder.Name
529 | $SubFolderID = $SubFolder.ID
530 | If ($SubFolder.Subfolders.Count -gt 0) {GetSubFolders $SubFolder}
531 | If ($SubFolder.Messages.Count -gt 0) {
532 | If ($PruneSubFolders) {GetMessages $SubFolder}
533 | } Else {
534 | If ($PruneEmptySubFolders) {$ArrayDeletedFolders += $SubFolderID}
535 | }
536 | $IterateFolder++
537 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
538 | }
539 | If ($PruneEmptySubFolders) {
540 | $ArrayDeletedFolders | ForEach {
541 | $CheckFolder = $Folder.SubFolders.ItemByDBID($_)
542 | If (SubFoldersEmpty $CheckFolder) {
543 | Try {
544 | If ($DoDelete) {$Folder.SubFolders.DeleteByDBID($_)}
545 | $TotalDeletedFolders++
546 | Debug "Deleted empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
547 | }
548 | Catch {
549 | $DeleteFolderErrors++
550 | Debug "[ERROR] Deleting empty subfolder $($CheckFolder.Name) in $($hMSAccount.Address)"
551 | Debug "[ERROR] : $($Error[0])"
552 | }
553 | $Error.Clear()
554 | }
555 | }
556 | }
557 | $ArrayDeletedFolders.Clear()
558 | }
559 |
560 | Function SubFoldersEmpty ($Folder) {
561 | $IterateFolder = 0
562 | If ($Folder.SubFolders.Count -gt 0) {
563 | Do {
564 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
565 | If ($SubFolder.Messages.Count -gt 0) {
566 | Return $False
567 | Break
568 | }
569 | If ($SubFolder.SubFolders.Count -gt 0) {
570 | SubFoldersEmpty $SubFolder
571 | }
572 | $IterateFolder++
573 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
574 | } Else {
575 | Return $True
576 | }
577 | }
578 |
579 | Function GetMatchFolders ($Folder) {
580 | $IterateFolder = 0
581 | If ($Folder.SubFolders.Count -gt 0) {
582 | Do {
583 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
584 | $SubFolderName = $SubFolder.Name
585 | If ($SubFolderName -match $PruneFolders) {
586 | GetSubFolders $SubFolder
587 | GetMessages $SubFolder
588 | } Else {
589 | GetMatchFolders $SubFolder
590 | }
591 | $IterateFolder++
592 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
593 | }
594 | }
595 |
596 | Function GetMessages ($Folder) {
597 | $IterateMessage = 0
598 | $ArrayMessagesToDelete = @()
599 | $DeletedMessages = 0
600 | If ($Folder.Messages.Count -gt 0) {
601 | Do {
602 | $Message = $Folder.Messages.Item($IterateMessage)
603 | If ($Message.InternalDate -lt ((Get-Date).AddDays(-$DaysBeforeDelete))) {
604 | $ArrayMessagesToDelete += $Message.ID
605 | }
606 | $IterateMessage++
607 | } Until ($IterateMessage -eq $Folder.Messages.Count)
608 | }
609 | $ArrayMessagesToDelete | ForEach {
610 | Try {
611 | If ($DoDelete) {$Folder.Messages.DeleteByDBID($_)}
612 | $DeletedMessages++
613 | $TotalDeletedMessages++
614 | }
615 | Catch {
616 | $DeleteMessageErrors++
617 | Debug "[ERROR] Deleting messages from folder $($Folder.Name) in $($hMSAccount.Address)"
618 | Debug "[ERROR] $($Error[0])"
619 | }
620 | $Error.Clear()
621 | }
622 | If ($DeletedMessages -gt 0) {
623 | Debug "Deleted $DeletedMessages message$(Plural $DeletedMessages) from $($Folder.Name) in $($hMSAccount.Address)"
624 | }
625 | $ArrayMessagesToDelete.Clear()
626 | }
627 |
628 | Function PruneMessages {
629 |
630 | $Error.Clear()
631 | $BeginDeletingOldMessages = Get-Date
632 | Debug "----------------------------"
633 | Debug "Begin pruning messages older than $DaysBeforeDelete days"
634 | If (-not($DoDelete)) {
635 | Debug "Delete disabled - Test Run ONLY"
636 | }
637 |
638 | <# Authenticate hMailServer COM #>
639 | $hMS = New-Object -COMObject hMailServer.Application
640 | $AuthAdmin = $hMS.Authenticate("Administrator", $hMSAdminPass)
641 |
642 | If ($AuthAdmin) {
643 |
644 | <# Set required variables if blank #>
645 | If ([string]::IsNullOrEmpty($PruneFolders)) {$PruneFolders = "deleted|spam|junk"}
646 | If ([string]::IsNullOrEmpty($SkipAccountPruning)) {$SkipAccountPruning = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | % {[Char]$_}))}
647 | If ([string]::IsNullOrEmpty($SkipDomainPruning)) {$SkipDomainPruning = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | % {[Char]$_}))}
648 |
649 | <# Loop through IMAP folders #>
650 | If ($hMS.Domains.Count -gt 0) {
651 | For ($IterateDomains = 0; $IterateDomains -lt $hMS.Domains.Count; $IterateDomains++) {
652 | $hMSDomain = $hMS.Domains.Item($IterateDomains)
653 | If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch [regex]$SkipDomainPruning) -and ($hMSDomain.Accounts.Count -gt 0)) {
654 | For ($IterateAccounts = 0; $IterateAccounts -lt $hMSDomain.Accounts.Count; $IterateAccounts++) {
655 | $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
656 | If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch [regex]$SkipAccountPruning) -and ($hMSAccount.IMAPFolders.Count -gt 0)) {
657 | For ($IterateIMAPFolders = 0; $IterateIMAPFolders -lt $hMSAccount.IMAPFolders.Count; $IterateIMAPFolders++) {
658 | $hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
659 | If ($hMSIMAPFolder.Name -match $PruneFolders) {
660 | If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
661 | GetSubFolders $hMSIMAPFolder
662 | }
663 | GetMessages $hMSIMAPFolder
664 | } Else {
665 | GetMatchFolders $hMSIMAPFolder
666 | }
667 | }
668 | }
669 | }
670 | }
671 | }
672 | }
673 |
674 | <# Report message pruning #>
675 | If ($DeleteMessageErrors -gt 0) {
676 | Debug "Finished Message Pruning : $DeleteMessageErrors Errors present"
677 | Email "[ERROR] Message Pruning : $DeleteMessageErrors Errors present : Check debug log"
678 | }
679 | If ($TotalDeletedMessages -gt 0) {
680 | Debug "Finished pruning $TotalDeletedMessages messages in $(ElapsedTime $BeginDeletingOldMessages)"
681 | Email "[OK] Pruned $TotalDeletedMessages messages older than $DaysBeforeDelete days"
682 | } Else {
683 | Debug "No messages older than $DaysBeforeDelete days to prune"
684 | Email "[OK] No messages older than $DaysBeforeDelete days to prune"
685 | }
686 |
687 | <# Report folder pruning #>
688 | If ($DeleteFolderErrors -gt 0) {
689 | Debug "Deleting Empty Folders : $DeleteFolderErrors Errors present"
690 | Email "[ERROR] Deleting Empty Folders : $DeleteFolderErrors Errors present : Check debug log"
691 | }
692 | If ($TotalDeletedFolders -gt 0) {
693 | Debug "Deleted $TotalDeletedFolders empty subfolders"
694 | Email "[OK] Deleted $TotalDeletedFolders empty subfolders"
695 | } Else {
696 | Debug "No empty subfolders deleted"
697 | # Email "[OK] No empty subfolders deleted"
698 | }
699 |
700 | } Else {
701 | Debug "[ERROR] hMailServer COM authentication failed. Check password and/or Windows Event Log."
702 | Email "[ERROR] hMailServer COM authentication failed. Check password and/or Windows Event Log."
703 | }
704 | }
705 |
706 | <# Feed Bayes #>
707 | Function GetBayesSubFolders ($Folder) {
708 | $IterateFolder = 0
709 | $ArrayBayesMessages = @()
710 | If ($Folder.SubFolders.Count -gt 0) {
711 | Do {
712 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
713 | $SubFolderName = $SubFolder.Name
714 | $SubFolderID = $SubFolder.ID
715 | If ($SubFolder.Subfolders.Count -gt 0) {GetBayesSubFolders $SubFolder}
716 | If ($SubFolder.Messages.Count -gt 0) {
717 | If ($BayesSubFolders) {GetBayesMessages $SubFolder}
718 | }
719 | $IterateFolder++
720 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
721 | }
722 | $ArrayBayesMessages.Clear()
723 | }
724 |
725 | Function GetBayesMatchFolders ($Folder) {
726 | $IterateFolder = 0
727 | If ($Folder.SubFolders.Count -gt 0) {
728 | Do {
729 | $SubFolder = $Folder.SubFolders.Item($IterateFolder)
730 | $SubFolderName = $SubFolder.Name
731 | If (($SubFolderName -match $HamFolders) -or ($SubFolderName -match $SpamFolders)) {
732 | GetBayesSubFolders $SubFolder
733 | GetBayesMessages $SubFolder
734 | } Else {
735 | GetBayesMatchFolders $SubFolder
736 | }
737 | $IterateFolder++
738 | } Until ($IterateFolder -eq $Folder.SubFolders.Count)
739 | }
740 | }
741 |
742 | Function GetBayesMessages ($Folder) {
743 | $IterateMessage = 0
744 | $ArrayHamToFeed = @()
745 | $ArraySpamToFeed = @()
746 | $HamFedMessages = 0
747 | $SpamFedMessages = 0
748 | $LearnedHamMessagesFolder = 0
749 | $LearnedSpamMessagesFolder = 0
750 | If ($Folder.Messages.Count -gt 0) {
751 | If ($Folder.Name -match $HamFolders) {
752 | Do {
753 | $Message = $Folder.Messages.Item($IterateMessage)
754 | If ($Message.InternalDate -gt ((Get-Date).AddDays(-$BayesDays))) {
755 | $ArrayHamToFeed += $Message.FileName
756 | }
757 | $IterateMessage++
758 | } Until ($IterateMessage -eq $Folder.Messages.Count)
759 | }
760 | If ($Folder.Name -match $SpamFolders) {
761 | Do {
762 | $Message = $Folder.Messages.Item($IterateMessage)
763 | If ($Message.InternalDate -gt ((Get-Date).AddDays(-$BayesDays))) {
764 | $ArraySpamToFeed += $Message.FileName
765 | }
766 | $IterateMessage++
767 | } Until ($IterateMessage -eq $Folder.Messages.Count)
768 | }
769 | }
770 | $ArrayHamToFeed | ForEach {
771 | $FileName = $_
772 | Try {
773 | If ((Get-Item $FileName).Length -lt 512000) {
774 | If ($DoSpamC) {
775 | $SpamC = & cmd /c "`"$SADir\spamc.exe`" -d `"$SAHost`" -p `"$SAPort`" -L ham < `"$FileName`""
776 | $SpamCResult = Out-String -InputObject $SpamC
777 | If ($SpamCResult -match "Message successfully un/learned") {
778 | $LearnedHamMessages++
779 | $LearnedHamMessagesFolder++
780 | }
781 | If (($SpamCResult -notmatch "Message successfully un/learned") -and ($SpamCResult -notmatch "Message was already un/learned")) {
782 | Throw $SpamCResult
783 | }
784 | }
785 | $HamFedMessages++
786 | $TotalHamFedMessages++
787 | }
788 | }
789 | Catch {
790 | $HamFedMessageErrors++
791 | Debug "[ERROR] Feeding HAM message $FileName in $($hMSAccount.Address)"
792 | Debug "[ERROR] $($Error[0])"
793 | }
794 | }
795 | $ArraySpamToFeed | ForEach {
796 | $FileName = $_
797 | Try {
798 | If ((Get-Item $FileName).Length -lt 512000) {
799 | If ($DoSpamC) {
800 | $SpamC = & cmd /c "`"$SADir\spamc.exe`" -d `"$SAHost`" -p `"$SAPort`" -L spam < `"$FileName`""
801 | $SpamCResult = Out-String -InputObject $SpamC
802 | If ($SpamCResult -match "Message successfully un/learned") {
803 | $LearnedSpamMessages++
804 | $LearnedSpamMessagesFolder++
805 | }
806 | If (($SpamCResult -notmatch "Message successfully un/learned") -and ($SpamCResult -notmatch "Message was already un/learned")) {
807 | Throw $SpamCResult
808 | }
809 | }
810 | $SpamFedMessages++
811 | $TotalSpamFedMessages++
812 | }
813 | }
814 | Catch {
815 | $SpamFedMessageErrors++
816 | Debug "[ERROR] Feeding SPAM message $FileName in $($hMSAccount.Address)"
817 | Debug "[ERROR] $($Error[0])"
818 | }
819 | }
820 | If ($HamFedMessages -gt 0) {
821 | Debug "Learned tokens from $LearnedHamMessagesFolder of $HamFedMessages HAM message$(Plural $HamFedMessages) fed from $($Folder.Name) in $($hMSAccount.Address)"
822 | }
823 | If ($SpamFedMessages -gt 0) {
824 | Debug "Learned tokens from $LearnedSpamMessagesFolder of $SpamFedMessages SPAM message$(Plural $SpamFedMessages) fed from $($Folder.Name) in $($hMSAccount.Address)"
825 | }
826 | $ArraySpamToFeed.Clear()
827 | }
828 |
829 | Function FeedBayes {
830 |
831 | $Error.Clear()
832 |
833 | $BeginFeedingBayes = Get-Date
834 | Debug "----------------------------"
835 | Debug "Begin learning Bayes tokens from messages newer than $BayesDays days"
836 | If (-not($DoSpamC)) {
837 | Debug "SpamC disabled - Test Run ONLY"
838 | }
839 |
840 | <# Authenticate hMailServer COM #>
841 | $hMS = New-Object -COMObject hMailServer.Application
842 | $AuthAdmin = $hMS.Authenticate("Administrator", $hMSAdminPass)
843 |
844 | If ($AuthAdmin) {
845 |
846 | <# Set SA host and port variables #>
847 | $SAHost = $hMS.Settings.AntiSpam.SpamAssassinHost
848 | $SAPort = $hMS.Settings.AntiSpam.SpamAssassinPort
849 |
850 | <# Set required variables if blank #>
851 | If ([string]::IsNullOrEmpty($HamFolders)) {$HamFolders = "inbox"}
852 | If ([string]::IsNullOrEmpty($SpamFolders)) {$SpamFolders = "spam|junk"}
853 | If ([string]::IsNullOrEmpty($SkipAccountBayes)) {$SkipAccountBayes = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | ForEach {[Char]$_}))}
854 | If ([string]::IsNullOrEmpty($SkipDomainBayes)) {$SkipDomainBayes = $(-Join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | ForEach {[Char]$_}))}
855 |
856 | <# Loop through IMAP folders #>
857 | If ($hMS.Domains.Count -gt 0) {
858 | For ($IterateDomains = 0; $IterateDomains -lt $hMS.Domains.Count; $IterateDomains++) {
859 | $hMSDomain = $hMS.Domains.Item($IterateDomains)
860 | If (($hMSDomain.Active) -and ($hMSDomain.Name -notmatch [regex]$SkipDomainBayes) -and ($hMSDomain.Accounts.Count -gt 0)) {
861 | For ($IterateAccounts = 0; $IterateAccounts -lt $hMSDomain.Accounts.Count; $IterateAccounts++) {
862 | $hMSAccount = $hMSDomain.Accounts.Item($IterateAccounts)
863 | If (($hMSAccount.Active) -and ($hMSAccount.Address -notmatch [regex]$SkipAccountBayes) -and ($hMSAccount.IMAPFolders.Count -gt 0)) {
864 | For ($IterateIMAPFolders = 0; $IterateIMAPFolders -lt $hMSAccount.IMAPFolders.Count; $IterateIMAPFolders++) {
865 | $hMSIMAPFolder = $hMSAccount.IMAPFolders.Item($IterateIMAPFolders)
866 | If (($hMSIMAPFolder.Name -match $HamFolders) -or ($hMSIMAPFolder.Name -match $SpamFolders)) {
867 | If ($hMSIMAPFolder.SubFolders.Count -gt 0) {
868 | GetBayesSubFolders $hMSIMAPFolder
869 | }
870 | GetBayesMessages $hMSIMAPFolder
871 | } Else {
872 | GetBayesMatchFolders $hMSIMAPFolder
873 | }
874 | }
875 | }
876 | }
877 | }
878 | }
879 | }
880 |
881 | Debug "----------------------------"
882 | Debug "Finished feeding $($TotalHamFedMessages + $TotalSpamFedMessages) messages to Bayes in $(ElapsedTime $BeginFeedingBayes)"
883 | Debug "----------------------------"
884 |
885 | If ($HamFedMessageErrors -gt 0) {
886 | Debug "Errors feeding HAM to SpamC : $HamFedMessageErrors Error$(Plural $HamFedMessageErrors) present"
887 | Email "[ERROR] HAM SpamC : $HamFedMessageErrors Errors present : Check debug log"
888 | }
889 | If ($TotalHamFedMessages -gt 0) {
890 | Debug "Bayes learned from $LearnedHamMessages of $TotalHamFedMessages HAM message$(Plural $TotalHamFedMessages) found"
891 | Email "[OK] Bayes HAM from $LearnedHamMessages of $TotalHamFedMessages message$(Plural $TotalHamFedMessages)"
892 | } Else {
893 | Debug "No HAM messages newer than $BayesDays days to feed to Bayes"
894 | Email "[OK] No HAM messages to feed Bayes"
895 | }
896 |
897 | If ($SpamFedMessageErrors -gt 0) {
898 | Debug "Errors feeding SPAM to SpamC : $SpamFedMessageErrors Error$(Plural $SpamFedMessageErrors) present"
899 | Email "[ERROR] SPAM SpamC : $SpamFedMessageErrors Errors present : Check debug log"
900 | }
901 | If ($TotalSpamFedMessages -gt 0) {
902 | Debug "Bayes learned from $LearnedSpamMessages of $TotalSpamFedMessages SPAM message$(Plural $TotalSpamFedMessages) found"
903 | Email "[OK] Bayes SPAM from $LearnedSpamMessages of $TotalSpamFedMessages message$(Plural $TotalSpamFedMessages)"
904 | } Else {
905 | Debug "No SPAM messages newer than $BayesDays days to feed to Bayes"
906 | Email "[OK] No SPAM messages to feed Bayes"
907 | }
908 |
909 | If ($SyncBayesJournal) {
910 | Debug "----------------------------"
911 | Try {
912 | $BayesSync = & cmd /c "`"$SADir\sa-learn.exe`" --sync"
913 | $BayesSyncResult = Out-String -InputObject $BayesSync
914 | If ([string]::IsNullOrEmpty($BayesSyncResult)) {
915 | Throw "Nothing to sync"
916 | }
917 | Debug $BayesSyncResult
918 | }
919 | Catch {
920 | Debug "[INFO] Bayes Journal Sync: $($Error[0])"
921 | }
922 | }
923 |
924 | If ($BackupBayesDatabase) {
925 | Debug "----------------------------"
926 | Try {
927 | If (-not(Test-Path $BayesBackupLocation)) {
928 | Throw "Bayes backup file does not exist - Check Path"
929 | } Else {
930 | & cmd /c "`"$SADir\sa-learn.exe`" --backup > `"$BayesBackupLocation`""
931 | If ((Get-Item -Path $BayesBackupLocation).LastWriteTime -lt ((Get-Date).AddSeconds(-30))) {
932 | Throw "Unknown Error backing up Bayes database"
933 | }
934 | Debug "Successfully backed up Bayes database"
935 | }
936 | }
937 | Catch {
938 | Debug "[ERROR] Backing up Bayes : $($Error[0])"
939 | Email "[ERROR] Backing up Bayes db"
940 | }
941 | }
942 |
943 |
944 | } Else {
945 | Debug "[ERROR] hMailServer COM authentication failed. Check password and/or Windows Event Log."
946 | Email "[ERROR] hMailServer COM authentication failed. Check password and/or Windows Event Log."
947 | }
948 |
949 | }
950 |
951 | <# Check for updates #>
952 | Function CheckForUpdates {
953 | Debug "----------------------------"
954 | Debug "Checking for script update at GitHub"
955 | $GitHubVersion = $LocalVersion = $NULL
956 | $GetGitHubVersion = $GetLocalVersion = $False
957 | $GitHubVersionTries = 1
958 | Do {
959 | Try {
960 | $GitHubVersion = [decimal](Invoke-WebRequest -UseBasicParsing -Method GET -URI https://raw.githubusercontent.com/palinkas-jo-reggelt/hMailServer_Offsite_Backup/main/version.txt).Content
961 | $GetGitHubVersion = $True
962 | }
963 | Catch {
964 | Debug "[ERROR] Obtaining GitHub version : Try $GitHubVersionTries : Obtaining version number: $($Error[0])"
965 | }
966 | $GitHubVersionTries++
967 | } Until (($GitHubVersion -gt 0) -or ($GitHubVersionTries -eq 6))
968 | If (Test-Path "$PSScriptRoot\version.txt") {
969 | $LocalVersion = [decimal](Get-Content "$PSScriptRoot\version.txt")
970 | $GetLocalVersion = $True
971 | }
972 | If (($GetGitHubVersion) -and ($GetLocalVersion)) {
973 | If ($LocalVersion -lt $GitHubVersion) {
974 | Debug "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/hMailServer_Offsite_Backup"
975 | If ($UseHTML) {
976 | Email "[INFO] Upgrade to version $GitHubVersion available at GitHub"
977 | } Else {
978 | Email "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/hMailServer_Offsite_Backup"
979 | }
980 | } Else {
981 | Debug "Backup & Upload script is latest version: $GitHubVersion"
982 | }
983 | } Else {
984 | If ((-not($GetGitHubVersion)) -and (-not($GetLocalVersion))) {
985 | Debug "[ERROR] Version test failed : Could not obtain either GitHub nor local version information"
986 | Email "[ERROR] Version check failed"
987 | } ElseIf (-not($GetGitHubVersion)) {
988 | Debug "[ERROR] Version test failed : Could not obtain version information from GitHub"
989 | Email "[ERROR] Version check failed"
990 | } ElseIf (-not($GetLocalVersion)) {
991 | Debug "[ERROR] Version test failed : Could not obtain local install version information"
992 | Email "[ERROR] Version check failed"
993 | } Else {
994 | Debug "[ERROR] Version test failed : Unknown reason - file issue at GitHub"
995 | Email "[ERROR] Version check failed"
996 | }
997 | }
998 | }
--------------------------------------------------------------------------------