Skip to content

Instantly share code, notes, and snippets.

@Smalls1652
Last active October 19, 2021 23:55
Show Gist options
  • Save Smalls1652/b75149d14f0b725eb8abfb5d31caceb4 to your computer and use it in GitHub Desktop.
Save Smalls1652/b75149d14f0b725eb8abfb5d31caceb4 to your computer and use it in GitHub Desktop.
Azure Storage - PST Upload Script

Upload PST File to Azure Storage Account

Changelog

2021.08.02 <-- Current version

Posted on 2021-10-19 at 07:51 PM EDT

  • Added a skip option if a blob for a file already exists.
  • Fixed the reticulation of spines. This should no longer be an issue.
  • Domestic coefficients should be properly balanced now.
  • Fixed the verbose output when a blob for a file didn't exist and the file name shown was blank.

2021.08.01

Posted on 2021-08-20 at 10:48 AM EDT.

  • Added overwrite prompt if a blob for the file already exists.

2021.08.00

  • Initial release.

Pre-requisites

PowerShell modules

Module Name Minimum required version
Az.Accounts 2.5.2
Az.Storage 3.10.0

To check the versions of the two modules listed above, run the following command:

Get-Module -Name @("Az.Accounts", "Az.Storage") -ListAvailable

If the modules ARE NOT installed, the easiest way to get them is by running the following command (This will install the latest versions of all of the Azure PowerShell modules):

Install-Module -Name "Az" -Scope "CurrentUser"

If the modules ARE installed but ARE BELOW the minimum required version, then run the following command to update the modules:

Update-Module -Name @("Az.Accounts", "Az.Storage")

Usage

To upload a PST file to the Azure Storage Account, follow these steps:

  1. Open up a PowerShell prompt.
  2. Change the current directory to the location of the script.
    • This can be done either with cd or Set-Location.
  3. Run the command Connect-AzAccount to login into Azure PowerShell.
  4. Once connected, type in .\Upload-PstFileToAzure.ps1 -PstFilePath ".\path\to\file".
    • Replace ".\path\to\file" with the path to the .pst file to upload.
    • The path can be a relative or absolute path.

Upload-PstFileToAzure.ps1 Help Data

SYNOPSIS

Upload a PST file to Azure Storage.

DESCRIPTION

Upload a PST file to our Azure Storage Account for backup.

PARAMETERS

PstFilePath

The path (relative or absolute) to the PST file to upload.

ResourceGroupName

The name of the resource group where the storage account is located.

StorageAccountName

The name of the storage account.

ContainerName

The name of the blob container to upload the file to.

UploadAsHotTier

Forcefully sets the access tier for the blob to the 'Hot' tier.

NOTES

  • Files that do not end with a .pst extension will throw an error. This is to prevent files that are not in the scope of backup from being uploaded.
  • The files will be set to the 'Cool' tier in the Azure Storage Account by default.

EXAMPLES

Example 01

In this example:

  • The .\Upload-PstFileToAzure.ps1 script is located in the user profile directory C:\Users\jwinger1\.
  • The PST file to upload is bperry.pst located in the directory C:\Users\jwinger1\exported-pst-files\.
#Connect to Azure PowerShell
Connect-AzAccount

#Upload the file to Azure
.\Upload-PstFileToAzure.ps1 -PstFilePath ".\exported-pst-files\bperry.pst"

After the file is uploaded, the script will return an object of the uploaded file item.

Example 02

In this example:

  • The .\Upload-PstFileToAzure.ps1 script is located in the user profile directory C:\Users\jwinger1\.
  • The file to upload is pierce sucks and why he should be kicked out of the group.docx located in the directory C:\Users\jwinger1\random-docs\.
#Connect to Azure PowerShell
Connect-AzAccount

#Upload the file to Azure
.\Upload-PstFileToAzure.ps1 -PstFilePath ".\random-docs\pierce sucks and why he should be kicked out of the group.docx"

The script will return an error saying:

Upload-PstFileToAzure.ps1: 'pierce sucks and why he should be kicked out of the group.docx' is not a '.pst' file.
<#PSScriptInfo
.VERSION 2021.08.02
.GUID 6631c556-8592-4282-8179-4dd9bd04ff9f
.AUTHOR Tim Small
.COMPANYNAME Smalls.Online
.COPYRIGHT Tim Small - 2021
.TAGS
.LICENSEURI
.PROJECTURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
.PRIVATEDATA
#>
#requires -modules @{ ModuleName = "Az.Accounts"; ModuleVersion = "2.5.2"}
#requires -modules @{ ModuleName = "Az.Storage"; ModuleVersion = "3.10.0"}
<#
.SYNOPSIS
Upload a PST file to Azure Storage.
.DESCRIPTION
Upload a PST file to our Azure Storage Account for backup.
.PARAMETER PstFilePath
The path (relative or absolute) to the PST file to upload.
.PARAMETER ResourceGroupName
The name of the resource group where the storage account is located.
.PARAMETER StorageAccountName
The name of the storage account.
.PARAMETER ContainerName
The name of the blob container to upload the file to.
.PARAMETER UploadAsHotTier
Forcefully sets the access tier for the blob to the 'Hot' tier.
.EXAMPLE
PS >#Connect to Azure PowerShell
PS > Connect-AzAccount
PS >
PS >#Upload the file to Azure
PS > .\Upload-PstFileToAzure.ps1 -PstFilePath ".\exported-pst-files\bperry.pst"
Upload a PST file to the storage account.
.NOTES
* Files that do not end with a `.pst` extension will throw an error. This is to prevent files that are not in the scope of backup from being uploaded.
* The files will be set to the 'Cool' tier in the Azure Storage Account by default.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Position = 0, Mandatory)]
[ValidateScript(
{
$inputPath = $PSItem
try {
$null = Resolve-Path -Path $inputPath -ErrorAction "Stop"
return $true
}
catch {
throw "Failed to resolve the file path to '$($inputPath)'."
}
}
)]
[string]$PstFilePath,
[Parameter(Position = 1, Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$ResourceGroupName,
[Parameter(Position = 2, Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$StorageAccountName,
[Parameter(Position = 3, Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$ContainerName,
[Parameter(Position = 4)]
[switch]$UploadAsHotTier
)
$progressSplat = @{
"Activity" = "Uploading PST to Azure Blob Storage";
"Status" = "Progress->";
"Id" = 0;
}
Write-Progress @progressSplat -PercentComplete 0 -CurrentOperation "Getting local file"
#Resolve the path to the input file and check to make sure it's a '.pst' file.
Write-Verbose "Resolving PST file path and ensuring it's a PST file."
$resolvedPstFilePath = (Resolve-Path -Path $PstFilePath -ErrorAction "Stop").Path
$pstFileItem = Get-Item -Path $resolvedPstFilePath -ErrorAction "Stop"
if ($pstFileItem.Extension -ne ".pst") {
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
[System.IO.FileFormatException]::new("'$($pstFileItem.Name)' is not a '.pst' file."),
"InvalidFileType",
[System.Management.Automation.ErrorCategory]::InvalidType,
$pstFileItem
)
)
}
#Set the access tier for the blob file by checking if the 'UploadAsHotTier' switch parameter was provided by the user.
$accessTier = $null
switch ($UploadAsHotTier) {
$true {
#If it was provided, then warn the user about the increased costs of the tier.
Write-Warning "``-UploadAsHotTier`` parameter was provided."
Write-Warning "Supplying this parameter will change the access tier from being set to 'Cool' to 'Hot'."
Write-Warning "This will incur higher costs and should only be used for testing purposes."
if ($PSCmdlet.ShouldContinue("Upload '$($pstFileItem.Name)' in the 'Hot' tier?", "Confirm access tier selection")) {
#If the user said "Yes" (y), then set the access tier to 'Hot'.
$accessTier = "Hot"
}
else {
#If the user said "No" (n), then throw a terminating error.
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
[System.Exception]::new("User cancelled the script execution during prompt."),
"CancelledDuringPrompt",
[System.Management.Automation.ErrorCategory]::OperationStopped,
$pstFileItem
)
)
}
break
}
Default {
#If it wasn't provided, then set the access tier to 'Cool'.
$accessTier = "Cool"
break
}
}
Write-Verbose "Blob access tier will be set to '$($accessTier)'."
#Set the current Storage Account context.
Write-Verbose "Connecting to the storage account '$($StorageAccountName)'."
Write-Progress @progressSplat -PercentComplete 25 -CurrentOperation "Connecting to the storage account '$($StorageAccountName)'"
$azStgAcctSplat = @{
"ResourceGroupName" = $ResourceGroupName;
"Name" = $StorageAccountName;
"ErrorAction" = "Stop";
}
$stgAccount = Get-AzStorageAccount @azStgAcctSplat
$null = $stgAccount | Set-AzCurrentStorageAccount
Write-Verbose "Getting the container named '$($ContainerName)' in the storage account '$($StorageAccountName)'."
Write-Progress @progressSplat -PercentComplete 35 -CurrentOperation "Getting the container '$($ContainerName)'"
$storageContainer = Get-AzStorageContainer -Name $ContainerName -ErrorAction "Stop"
#Check to see if the provided file already exists in the storage account.
Write-Progress @progressSplat -PercentComplete 40 -CurrentOperation "Checking to see if a blob already exists for '$($pstFileItem.Name)'"
$blobExistsCheck = $null
try {
#If `Get-AzStorageBlob` doesn't throw a 'ResourceNotFoundException', then an already existing blob exists.
$null = Get-AzStorageBlob -Container $storageContainer.Name -Blob $pstFileItem.Name -ErrorAction "Stop"
$blobExistsCheck = $true
Write-Verbose "'$($pstFileItem.Name)' already exists in the storage account."
}
catch [Microsoft.WindowsAzure.Commands.Storage.Common.ResourceNotFoundException] {
#If the 'ResourceNotFoundException' has been thrown, then a blob doesn't already exist.
$blobExistsCheck = $false
}
catch {
#If any other exception is thrown, then terminate the script with a generic exception that contains the original exception that was thrown.
$errorDetails = $PSItem
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
[System.Exception]::new(
"An error occurred while checking to see if a blob for '$($pstFileItem.Name)' already existed. Check the inner exception for more details.",
$errorDetails
),
"BlobExistsCheckUnknownError",
[System.Management.Automation.ErrorCategory]::InvalidResult,
$pstFileItem
)
)
}
#Process how to name the blob if it exists already or not.
$pstBlobName = $null
$setToSkip = $false
switch ($blobExistsCheck) {
$true {
#If the blob already exists, then create a potential name for the blob that adds the current date and time to the file name.
$currentDateTime = [System.DateTime]::Now.ToString("yyyy-MM-ddTHH-mm-ss")
$potentialPstBlobName = "$($pstFileItem.BaseName).$($currentDateTime)$($pstFileItem.Extension)"
#Create the prompt choices for if the user wants to create a new file or overwrite the already existing file.
#Then prompt the user to select an option.
$potentialNamePromptChoices = @(
[System.Management.Automation.Host.ChoiceDescription]::new(
"&Create new file",
"A new file will be created with the name '$($potentialPstBlobName)'."
),
[System.Management.Automation.Host.ChoiceDescription]::new(
"&Overwrite",
"The file will be overwritten."
),
[System.Management.Automation.Host.ChoiceDescription]::new(
"&Skip",
"The file will be skipped."
)
)
$getOverwriteAction = $Host.UI.PromptForChoice(
"A file for '$($pstFileItem.Name)' already exists in the storage account.",
"Do you want to create a new file with the name '$($potentialPstBlobName)' or overwrite the current file?`nSelect an option:",
$potentialNamePromptChoices,
0
)
#Process the user's choice.
switch ($getOverwriteAction) {
2 {
Write-Warning "Skipping file."
$setToSkip = $true
break;
}
1 {
#If they chose to overwrite, then the already existing blob will be overwritten.
Write-Warning "The current file will be overwritten."
$pstBlobName = $pstFileItem.Name
break
}
Default {
#Otherwise, a new blob file will be created.
Write-Verbose "The file will be uploaded as '$($potentialPstBlobName)'."
$pstBlobName = $potentialPstBlobName
break
}
}
break
}
Default {
#If the blob doesn't already exist, then set the name of the blob to the file's name.
Write-Verbose "The file will be uploaded as '$($pstFileItem.Name)'."
$pstBlobName = $pstFileItem.Name
break
}
}
if (!$setToSkip) {
#Set the blob metadata to include the original creation date and time of the file and then upload it.
Write-Progress @progressSplat -PercentComplete 50 -CurrentOperation "Uploading file '$($pstFileItem.Name)' to blob storage path '$($storageContainer.CloudBlobContainer.Uri.ToString())'"
$blobFileMetadata = @{
"originalCreationDateTime" = $pstFileItem.CreationTime.ToString("yyyy-MM-dd HH:mm:ss zzz")
}
$setBlobContentSplat = @{
"File" = $pstFileItem.FullName;
"Blob" = $pstBlobName;
"Metadata" = $blobFileMetadata;
"BlobType" = "Block";
"StandardBlobTier" = $accessTier;
"Force" = $true;
}
#Upload the file to the storage account.
if ($PSCmdlet.ShouldProcess($pstFileItem.Name, "Upload to blob storage container: '$($storageContainer.CloudBlobContainer.Uri.ToString())'")) {
#Disable the progress output from `Set-AzStorageBlobContent`.
$ProgressPreference = "SilentlyContinue"
$uploadedFile = $storageContainer | Set-AzStorageBlobContent @setBlobContentSplat
#Set the progress preference back to 'Continue', so the script's progress output can continue.
$ProgressPreference = "Continue"
}
Write-Progress @progressSplat -Completed
return $uploadedFile
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment