Skip to content

Instantly share code, notes, and snippets.

@wandersick
Last active July 16, 2020 15:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wandersick/1cae7f2d542940dcf5b7336130f635a2 to your computer and use it in GitHub Desktop.
Save wandersick/1cae7f2d542940dcf5b7336130f635a2 to your computer and use it in GitHub Desktop.
# To simplify run script from Task Sheduler with Program/script: powershell.exe and Argument: D:\Scripts\ws_email_archiving.ps1 or your path
# And "Run with highest privileges"
#
# If script is runed manual it must be "Run as Administrator"
#
# This mod of script assume exportin every 10 days - on 1st, 11 and 21 date every month
#
# I dont touch original notes! Just add some my.
# Add "en-US" localization because New-MailboxExportRequest -ContentFilter cant work with dates with other localizations
[System.Reflection.Assembly]::LoadWithPartialName("System.Threading")
[System.Reflection.Assembly]::LoadWithPartialName("System.Globalization")
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::CreateSpecificCulture("en-US")
[System.Threading.Thread]::CurrentThread.CurrentCulture = "en-US"
Add-PSSnapin *exchange*
# WS Email Archiving - Journal Mailbox Archiving PowerShell Script for Exchange 2013
# Written by wandersick (https://wandersick.blogspot.com/2016/07/powershell-automation-of-journal.html)
# Version: 1.2 (20171022)
# The default parameters of this script assume monthly exporting. See above URL for more information.
# The [Output Examples] in the comment sections below assume:
# Date of script execution: 02 Feburary 2016
# Date of archive period: Between 01 and 31 January 2016 (previous month)
# ----------------------------------------
# 1. DEFINING MAIN VARIABLES
# ----------------------------------------
# Specify name of a journal mailbox where this script will process
# [Output Examples]
# journalMailboxName = "messagejournal"
$journalMailboxName = "Specify a mailbox name here"
# Period of messages to be exported to PST by New-MailboxExportRequest cmdlet
# Note: Previous month is assumed for the default parameters
# Get last day of last month, start day of this month (not converted to string yet)
# [Output Examples]
# endDate = Monday, February 1, 2016 12:00:00 AM
# startDate = Friday, Jaunuary 1, 2016 12:00:00 AM
#$endDate = Get-Date -Day 1 "00:00:00"
#$startDate = $endDate.AddMonths(-1)
#$endDate = Get-Date -Day 11 "00:00:00"
#$startDate = Get-Date -Day 1 "00:00:00"
#$endDate = (Get-Date "00:00:00").AddDays(-10)
# To archive 10 day before today. Because Search-Mailbox -SearchQuery $searchDateRange
If (([System.DateTime]::Now).IsDaylightSavingTime()) {
$endDate = (Get-Date ([TimeZoneInfo]::Local).BaseUtcOffset.ToString()).AddDays(-10).AddHours(1).AddMinutes(1)
} Else {
$endDate = (Get-Date ([TimeZoneInfo]::Local).BaseUtcOffset.ToString()).AddDays(-10).AddMinutes(1)
}
$startDate = $endDate.AddMonths(-1)
# (continued)
# Get last day of last month in "yyyy_MM_dd" string
# Name PST file using: specified string + last day of last month
# Set file path from it for storing the first copy of PST file
# [Output Examples]
# archiveDate = 2016_01_31
# archiveFile = archive 2016_01_31.pst
# archiveFileDir = \\localhost\e$\Archive_PST\
# archiveFilePath = \\localhost\e$\Archive_PST\archive 2016_01_31.pst
#$archiveDate = (Get-Date).AddDays(- (Get-Date).Day).ToString('yyyy_MM_dd')
$archiveDate = $endDate.ToString('yyyy-MM-dd')
#$archiveDate = (Get-Date -Day 10).ToString('yyyy_MM_dd')
$archiveFile = "journaling " + $archiveDate + ".pst"
$archiveFileDir = "\\servername\d$\"
$archiveFilePath = $archiveFileDir + $archivefile
# Make a secondary copy at the end (optional)
# [Output Examples]
# archiveEnable2ndCopy = $true
# archiveFileDir2ndCopy = "\\FileServer\Archive_PST\Secondary_Backup\"
# archiveFilePath2ndCopy = "\\FileServer\Archive_PST\Secondary_Backup\archive 2016_01_31.pst"
$archiveEnable2ndCopy = $true
$archiveFileDir2ndCopy = "\\FileServer\Archive_PST\Secondary_Backup\"
$archiveFilePath2ndCopy = "$archiveFileDir2ndCopy" + "$archivefile"
# Log date & time (Set current date and time for use as archive job name and log file name)
$dateTime = Get-Date -format "yyyyMMdd_hhmmsstt"
# Define mailbox export request job name (for New-MailboxExportRequest cmdlet)
# Note: To ensure uniqueness in order for Get-MailboxExportRequest cmdlet to identify the correct job as Complete,
# the default setting of $archiveJobName below defines the archive job string using journal mailbox name, archive date and current date-time
# In addition, the script also checks whether the same mailbox export request job name exists and will attempt to remove the job entry beforehand if it does.
# [Output Examples]
# archiveJobName = messagejournal 2016_01_31 20160202_030034AM
$archiveJobName = $journalMailboxName + " " + $archiveDate + " " + $dateTime
# Define search date string for email message deletion after archiving (by Search-Mailbox cmdlet)
# Note (again): The default settings specify email messages of previous month.
# [Output Examples]
# searchStartDate = 01/01/2016
# searchEndDate = 01/31/2016
# searchDateRange = Received:01/01/2016..31/01/2016
$searchStartDate = $startDate.ToString('MM/dd/yyyy')
#$searchEndDate = (Get-Date).AddDays(- (Get-Date).Day).ToString('MM/dd/yyyy')
#$searchEndDate = $endDate.ToString('MM/dd/yyyy')
$searchEndDate = (get-date).AddDays(-11).ToString('MM/dd/yyyy')
$searchDateRange = "Received:" + $searchStartDate + ".." + $searchEndDate
# ----------------------------------------
# 2. DEFINING LOG VARIABLES
# ----------------------------------------
# Log file (stdout, stderr)
$logFilePath = "D:\Scripts\Log\Log_Email_Archive_$($archiveDate)_$($dateTime).log"
# ----------------------------------------------------------------------------------------------------------------
# 3. DEFINING SEARCH-MAILBOX VARIABLES FOR LOGGING MESSAGES SEARCHED AND DELETED FROM JOURNAL MAILBOX
# ----------------------------------------------------------------------------------------------------------------
# Specify a mailbox (TargetMailbox) and a folder (TargetFolder, within the mailbox) different from the journal mailbox for
# logging email messages searched and deleted under a specified folder within a specified mailbox
# An email message with a zip attachment (CSV inside, listing all email messages returned by the search) will be generated in
# the specified mailbox under the specified folder. Multiple email messages may be generated as needed by Search-Mailbox cmdlet
# This logging features is Exchange-native and provided by Search-Mailbox cmdlet.
# (In contrast, the logging feature in comment section 4, further down below, is provided by this script)
# Definitons from TechNet on Search-Mailbox: https://technet.microsoft.com/en-us/library/dd298173(v=exchg.150).aspx
# TargetMailbox: "The TargetMailbox parameter specifies the identity of the destination mailbox where search results are copied."
# (The mailbox should be precreated by Exchange administrator)
# TargetFolder: "The TargetFolder parameter specifies a folder name in which search results are saved in the target mailbox."
# (The folder, within the mailbox, is created in the target mailbox upon execution.)
$saveSearchLogMailbox = "Specify a different mailbox name here for saving log file of Search-Mailbox results"
$saveSearchLogFolder = "Specify a different folder name Here for saving log file of Search-Mailbox results"
# --------------------------------------------------------------------
# 4. DEFINING EMAIL VARIABLES FOR MAILING LOG AND RESULTS
# --------------------------------------------------------------------
$emailSender = "Sender <sender@wandersick.com>"
$emailRecipient = "Recipient A <recipienta@wandersick.com>", "Recipient B <recipientb@wandersick.com>"
#$emailCc = "Recipient C <recipientc@wandersick.com>"
#$emailBcc = "Recipient D <recipientd@wandersick.com>"
$emailSubject = "Journaling Email Archive COMPLETE - " + $endDate.ToString('yyyy/MM/dd') + " - Log Attached"
$emailBody = "This is an automated message after email archiving scheduled task has completed. Please check attached log file as well as $saveSearchLogFolder (folder) within $saveSearchLogMailbox (mailbox) for any problems."
$emailSubjectFailure = "Journaling Email Archive FAILED - " + $endDate.ToString('yyyy/MM/dd') + " - Log Attached"
$emailBodyFailure = "This is an automated message after email archiving scheduled task has failed. Please check attached log file as well as $saveSearchLogFolder (folder) within $saveSearchLogMailbox (mailbox) for any problems."
$emailServer = "172.16.123.123"
$emailAttachment = $logFilePath
# ----------------------------------------
# RECORDING VARILABLES TO LOG FILE
# ----------------------------------------
Write-Output "" | tee $logFilePath -Append
Write-Output "[Variables]" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$dateTime = ' $dateTime | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$endDate = ' $endDate | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$journalMailboxName = ' $journalMailboxName | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$startDate = ' $startDate | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveDate = ' $archiveDate | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveFile = ' $archiveFile | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveFileDir = ' $archiveFileDir | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveFilePath = ' $archiveFilePath | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveEnable2ndCopy = ' $archiveEnable2ndCopy | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveFileDir2ndCopy = ' $archiveFileDir2ndCopy | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveFilePath2ndCopy = ' $archiveFilePath2ndCopy | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$archiveJobName = ' $archiveJobName | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$searchStartDate = ' $searchStartDate | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$searchEndDate = ' $searchEndDate | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$searchDateRange = ' $searchDateRange | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailSender = ' $emailSender | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailRecipient = ' $emailRecipient | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailCc = ' $emailCc | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailSubject = ' $emailSubject | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailBody = ' $emailBody | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailSubjectFailure = ' $emailSubjectFailure | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailBodyFailure = ' $emailBodyFailure | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailServer = ' $emailServer | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$emailAttachment = ' $emailAttachment | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$PSScriptRoot = ' $PSScriptRoot | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output '$logFilePath = ' $logFilePath | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# ----------------------------------------
# EXECUTING MAIN PROCEDURE
# ----------------------------------------
# Create an archiving job (an export request of journal mailbox within last month, with a job name that is uniquely identified, to specified location)
Write-Output "" | tee $logFilePath -Append
Write-Output "Exporting items between $startDate and $endDate" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Ensure no job of the same name exists.
# Get-MailboxExportRequest -Name $archiveJobName | Remove-MailboxExportRequest -confirm:$false
Get-MailboxExportRequest -Name $archiveJobName | Remove-MailboxExportRequest -confirm:$false
New-MailboxExportRequest -ContentFilter "(Received -ge '$startDate') -and (Received -lt '$endDate')" -Mailbox $journalMailboxName -FilePath $archiveFilePath -Name $archiveJobName -IncludeFolders "#Inbox#" -ExcludeDumpster:$true | tee $logFilePath -Append
#New-MailboxExportRequest -ContentFilter "(Received -ge '$startDate') -and (Received -lt '$endDate')" -Mailbox $journalMailboxName -FilePath $archiveFilePath -Name $archiveJobName | tee $logFilePath -Append
#New-MailboxExportRequest -ContentFilter "(Received -ge 'Nov 22 2017')" -Mailbox $journalMailboxName -FilePath $archiveFilePath -Name $archiveJobName | tee $logFilePath -Append
# Wait for the archiving job to complete
while (!(Get-MailboxExportRequest -Name $archiveJobName -Status Completed))
{
Write-Output "" | tee $logFilePath -Append
Write-Output "Still exporting... Waiting 30 minutes for it to complete..." | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Get-MailboxExportRequest -Name $archiveJobName | Get-MailboxExportRequestStatistics | fl | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Start-Sleep -s 1800
}
# To be safe before deletion, double-check the Complete status of email archiving
$emailArchivingStatus = Get-MailboxExportRequest -Name $archiveJobName -Status Completed | select -expand status
if ($emailArchivingStatus -eq "Completed")
{
Write-Output "" | tee $logFilePath -Append
Write-Output "Export completed." | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Getting more details for manual verification/troubleshooting in log file
Get-MailboxExportRequest -Name $archiveJobName | fl | tee $logFilePath -Append
Get-MailboxExportRequest -Name $archiveJobName | Get-MailboxExportRequestStatistics | fl | tee $logFilePath -Append
# Optional: Clean up (but Get-MailboxExportRequest will no longer return information of the previous job for troubleshooting; instead, check log file for troubleshooting)
# Get-MailboxExportRequest -Name $archiveJobName | Remove-MailboxExportRequest -confirm:$false
}
else
{
# Report failure via email and exit script
Write-Output "" | tee $logFilePath -Append
Write-Output "Export failed." | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Getting more details for manual verification/troubleshooting in log file
Get-MailboxExportRequest -Name $archiveJobName | fl | tee $logFilePath -Append
Get-MailboxExportRequest -Name $archiveJobName | Get-MailboxExportRequestStatistics | fl | tee $logFilePath -Append
# Optional: Clean up (but Get-MailboxExportRequest will no longer return information of the previous job for troubleshooting; instead, check log file for troubleshooting)
# Get-MailboxExportRequest -Name $archiveJobName | Remove-MailboxExportRequest -confirm:$false
Send-MailMessage -From $emailSender -To $emailRecipient -Cc $emailCc -Subject $emailSubjectFailure -Body $emailBodyFailure -SmtpServer $emailServer -Attachments $emailAttachment
Exit
}
# Check exported Archive file if is it locked
Do {
$fileinfo = [System.IO.FileInfo] (gi $archiveFilePath).fullname
try {
$stream = $fileInfo.Open([System.IO.FileMode]"Open",[System.IO.FileAccess]"ReadWrite",[System.IO.FileShare]"None")
$stream.Dispose()
$result = $false
Write-Output "Time: $(Get-Date). Archive is unlocked!"
} catch [System.IO.IOException] {
$result = $true
Write-Output "Time: $(Get-Date). Archive is still locked"
Out-File -FilePath $logFilePath -Append -InputObject ("Time: " + (Get-Date) + " Archive is still locked")
Start-Sleep -Seconds 10
}
$result
}
Until ( $result -eq $false )
# Creating a second copy to specified location
if ($archiveEnable2ndCopy) { Copy-Item -Path "$archiveFilePath" -Destination "$archiveFilePath2ndCopy" -Force -Confirm:$false -ErrorVariable faultCopy | tee $logFilePath -Append }
Out-File -FilePath $logFilePath -Append -InputObject $faultCopy
# In case there is any error during copying, send email and exit script so that no deletion will occur.
$fileHashSrc = Get-FileHash $archiveFilePath | select -expand hash
$fileHashDst = Get-FileHash $archiveFilePath2ndCopy | select -expand hash
if ($fileHashSrc -eq $fileHashDst)
{
Write-Output "" | tee $logFilePath -Append
Write-Output "Second file copy completed and verified successfully!!! `r`n Archive Hash $archiveFilePath : $fileHashSrc `r`n Second Archive Hash $archiveFilePath2ndCopy : $fileHashDst" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
}
else
{
# Report failure via email and exit script
Write-Output "" | tee $logFilePath -Append
Write-Output "Second file copy verification failed!!! `n Archive Hash: $fileHashSrc `n Second Archive Hash: $fileHashDst" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Send-MailMessage -From $emailSender -To $emailRecipient -Cc $emailCc -Subject $emailSubjectFailure -Body $emailBodyFailure -SmtpServer $emailServer -Attachments $emailAttachment
Exit
}
# Creating a log file of email messages to be deleted, then delete email (after PST export task is complete)
# This might have to be run multiple times (automatically) in order to get rid of all (>10000) email messages as 10000 is the limit of a single Search-Mailbox query.
# Except the first run, any subsequent run of the mail deletion command only happens when remaining item count is 10000.
do
{
Write-Output "" | tee $logFilePath -Append
Write-Output "Logging a list of items in journal mailbox to be deleted into $saveSearchLogFolder (Folder) of $saveSearchLogMailbox (Mailbox)" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Creating a log file of email messages to be deleted
$remainingItemCount = Get-Mailbox -Identity $journalMailboxName | Search-Mailbox -SearchQuery $searchDateRange -LogOnly -LogLevel Full -TargetFolder $saveSearchLogFolder -TargetMailbox $saveSearchLogMailbox | select -expand ResultItemsCount
Write-Output '$remainingItemCount = ' $remainingItemCount | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
Write-Output "Deleting archived messages from journal mailbox with specified date range: $searchDateRange" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Delete email
Get-Mailbox -Identity $journalMailboxName | Search-Mailbox -SearchQuery $searchDateRange -DeleteContent -force | tee $logFilePath -Append
# Sleep for 5 minutes only when there are more email messages to delete (may not be required)
if ($remainingItemCount -eq 10000)
{
Write-Output "" | tee $logFilePath -Append
Write-Output "Sleep for 5 minutes" | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
start-sleep -s 300
}
}
while ($remainingItemCount -eq 10000)
Write-Output "" | tee $logFilePath -Append
Write-Output "Sending an email with log to $emailRecipient cc $emailCc bcc $emailBcc " | tee $logFilePath -Append
Write-Output "" | tee $logFilePath -Append
# Send an email using local SMTP server with log when done.
# Send-MailMessage -From $emailSender -To $emailRecipient -Cc $emailCc -Bcc $emailBcc -Subject $emailSubject -Body $emailBody -SmtpServer $emailServer -Attachments $emailAttachment
Send-MailMessage -From $emailSender -To $emailRecipient -Subject $emailSubject -Body $emailBody -SmtpServer $emailServer -Attachments $emailAttachment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment