|
|
|
<#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 |
|
} |