PST Export
I really love this script. It started so simple. Put some usernames in a text doc, then run the script and it will try to start some PST exports. Keep an eye out for the red text though, it doesn't always work. I didn't write that version, but that's what it started as.
I really didn't like how it email reports afterwards, so I started looking into updating the code. While I was doing that, having to pre-stage usernames in a text doc? So 2001. I dropped in my multi line text box for input instead.
Then came exception handling. That turned out to be quite a bit because of the various reasons these could fail. Now I had to make sure that previous uses of the script had cleaned up after themselves and not left files that had the same name as files I needed to create.
Last, large PST exports. The exchange PST export process fails when the mailbox or archive is over 50 GB. Originally every time someone wanted to start an export, they would fire up Exchange Management Shell or the web GUI and check how big each mailbox was before beginning. I added the check into the script. For a while that was it, it would just write-warning "Archive is more than 50GB, please export another way" but that really wasn't good enough for me. I developed a way to use -ContentFilter argument to split the total timeframe into subsections and export each chunk of time into its own PST, staying under that 50GB limit. I originally wrote that as its own script, but later realized I could combine them into "one script to rule them all." The easiest way to do that turned out to be making the "BigPSTExport" a function, and just calling it when appropriate.
It ended up being one script, that pretty much by then end of it, will get you a PST exporting. I hope you enjoy it as much as I do.
Rhys
<#
.DESCRIPTION
Exports users' mailboxes and/or archives to PST via Exchange. Can accept multiple users. Handles exisiting files and PSTs over 50GB.
.EXAMPLE
.\PSTExport.ps1
.NOTES
Written by Rhys Ferris
Find me on:
LinkedIn: https://www.linkedin.com/in/rhys-ferris-2009ba138/
My Site : https://rhysferris.tech
License:
The MIT License (MIT)
Copyright (c) 2024 Rhys Ferris
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#>
#elevate to admin
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (!($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))){
write-host "Not running as admin"
Start-Process -FilePath powershell.exe -ArgumentList "-file $PSCommandPath" -verb runAs
return
}else{
write-host "Running as admin"
}
#Required functions snap ins and functions:
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
#Multi-line input box
function enter-info
{
$objForm1 = New-Object system.Windows.Forms.Form
$objForm1.Text = "Bulk Entry"
$objForm1.Size = New-Object System.Drawing.Size(300,630)
$objForm1.StartPosition = "CenterScreen"
$objForm1.Topmost = $True
$objForm1.FormBorderStyle = 'FixedToolWindow'
$textbox1label = New-Object System.Windows.Forms.Label
$textbox1label.Location = New-Object System.Drawing.Size(10,10)
$textbox1label.AutoSize = $true
$textbox1label.Text = "Names:"
$textbox1label.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",11,[System.Drawing.FontStyle]::BOLD)
$textbox1Message = New-Object System.Windows.Forms.Label
$textbox1Message.Location = New-Object System.Drawing.Size(10,35)
$textbox1Message.AutoSize = $true
$textbox1Message.Text = "Copy the User Names for the function below. Ensure that no trailing new line is left at the last line."
$textbox1Message.MaximumSize = new-Object System.Drawing.Size(250, 0)
$objTextBox1 = New-Object System.Windows.Forms.TextBox
$objTextBox1.Multiline = $True;
$objTextBox1.Location = New-Object System.Drawing.Size(10,105)
$objTextBox1.Size = New-Object System.Drawing.Size(273,450)
$objTextBox1.Scrollbars = "Vertical"
$AcceptButton = New-Object System.Windows.Forms.Button
$AcceptButton.Location = New-Object System.Drawing.Size(200,570)
$AcceptButton.Size = New-Object System.Drawing.Size(80,23)
$AcceptButton.Text = "Continue"
$AcceptButton.DialogResult=[System.Windows.Forms.DialogResult]::OK
$AbortButton = New-Object System.Windows.Forms.Button
$AbortButton.Location = New-Object System.Drawing.Size(120,570)
$AbortButton.Size = New-Object System.Drawing.Size(80,23)
$AbortButton.Text = "Abort"
$AbortButton.Add_Click({
$objForm1.Close();
$objForm1.Dispose()
$textbox1label.Dispose();
$textbox1Message.Dispose();
$objTextBox1.Dispose();
$AcceptButton.Dispose();
$AbortButton.Dispose();
$global:currentcompcount= 0
})
$AcceptButton.Add_Click({convert-inputtolist;
$objForm1.Close();
$objForm1.Dispose()
$textbox1label.Dispose();
$textbox1Message.Dispose();
$objTextBox1.Dispose();
$AcceptButton.Dispose();
$AbortButton.Dispose();
$global:currentcompcount= 0
})
$objForm1.Controls.Add($objTextBox1)
$objForm1.Controls.Add($AcceptButton)
$objForm1.Controls.Add($AbortButton)
$objForm1.Controls.Add($textbox1label)
$objForm1.Controls.Add($textbox1Message)
$objForm1.ShowDialog()
}
Function convert-inputtolist
{
$global:textboxresults = @()
$global:results = $objTextBox1.text -split '[\n]'
}
function BigPST{
param([string]$user,[bool]$archive)
$userCreatedDate = (get-aduser -Identity $user -Properties WhenCreated).WhenCreated
do{
$continue = $true
$data = Read-Host -Prompt "Start date in format MM/DD/YYYY (Press Enter to Accept Account Creation Date of $($userCreatedDate.ToString('MM/dd/yyyy')))"
if ($data -eq ""){
#default value
$startDate = $userCreatedDate
continue
}
if ($data -notmatch '(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/(19|20)\d{2}'){
Write-Warning "Input Validation Failed. Please Try Again."
$continue = $false
continue
}
$tempDate = [datetime]$data
if ($tempDate -lt ([datetime]'1/1/1980')){
Write-warning "Start Date older than 1980. Cowerdly refusing to continue."
$continue = $false
continue
}
$startDate = $tempDate
}while(!$continue)
do{
$continue = $true
$data = Read-Host -Prompt "End date in format MM/DD/YYYY (Press Enter to Accept Today)"
if ($data -eq ""){
$endDate = get-date
continue
}
if ($data -notmatch '(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/(19|20)\d{2}'){
Write-Warning "Input Validation Failed. Please Try Again."
$continue = $false
continue
}
$tempDate = [datetime]$data
if ($tempDate -lt ([datetime]'1/1/1980')){
Write-warning "End Date older than 1980. Cowderly refusing to continue."
$continue = $false
continue
}
$endDate = $tempDate
}while(!$continue)
do{
$continue = $true
$Divisions = (Read-Host "How many output files?")
if ($Divisions -notmatch '^\d+