<# .SYNOPSIS Creates a certificate based connection to an Azure Storage Account container using an authorized App Registration as a Service Principal, then uploads any files (usually backup files) newer than an inputted number of hours to the container while deleting older backup files both locally and from the container that are older than an inputted number of days .EXAMPLE Backup-ToAzureBlobContainer -certificateThumbprint "346a3702c8488c49eb14858cf8be8414002cc5dc" -tenantID "074437f3-ab5a-497a-a903-d5cee636e412" ` -applicationId "781e2021-c070-497b-8442-094f796ab4d0" -subscriptionID "b85263b5-ff7b-4b5a-ab91-16da2cee1863" -storageAccountName "somestorageaccount" ` -backupPath "C:\Backups" -containerName "somecontainer" -storageTier "Cool" -ageOfBackupFileInHours 6 -ageOfToBeDeletedBackupsInDays 30 .INPUTS [String]$certificateThumbprint - The certificate's thumbprint which has been uploaded as a client secret to the App Registration / Service Principal. It can be a self-signed cert as hosted on the backup server or a purchased signed cert [String]$tenantID - The ID of the tenant which can be found in the App Registration's overview page in Azure [String]$applicationId - The ID of the App Registration, again found in the App Registration's overview page in Azure [String]$subscriptionID - The ID of the Subscription of the Storage Account [String]$storageAccountName - The nme of the Storage Account [String]$backupPath - The file path to where the backup files are stored [Int]$ageOfBackupFileInHours - NEGATIVE INTEGER: How old in hours the backup file to be uploaded can be. It is used to only upload the most recent backup file [Int]$ageOfToBeDeletedBackupsInDays - NEGATIVE INTEGER: How old in days should the oldest backup file(s) be deleted from the local storage and the Azure Storage Account container [String]$containerName - The name of the container in the Azure Storage account (aka the destination for the uploaded files) The App Registration / Service Principal needs to be assigned "Storage Blob Data Contributor" to this container [String]$storageTier - In what storage tier should the files be saved (Hot, Cool, or Archive) .OUTPUTS None #> Function Backup-ToAzureBlobContainer { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String]$certificateThumbprint, [Parameter(Mandatory = $true)] [String]$tenantID, [Parameter(Mandatory = $true)] [String]$applicationId, [Parameter(Mandatory = $false)] [String]$subscriptionID, [Parameter(Mandatory = $true)] [String]$storageAccountName, [Parameter(Mandatory = $true)] [String]$backupPath, [Parameter(Mandatory = $true)] [String]$loggingPath, [Parameter(Mandatory = $true)] [Int]$ageOfBackupFileInHours, [Parameter(Mandatory = $false)] [Int]$ageOfToBeDeletedBackupsInDays, [Parameter(Mandatory = $true)] [String]$containerName, [Parameter(Mandatory = $true)] [ValidateSet('Hot','Cool','Archive')] [String]$storageTier ) #Log the uploads for follow-up and troubleshooting Start-Transcript -Path $loggingPath -Append Write-Host "Script has started at $(Get-Date)" Import-Module Az.Storage #Connect to Azure using the App Registration / Service Principal and locally installed certificate Try { Connect-AzAccount -CertificateThumbprint $certificateThumbprint -ApplicationId $applicationId -Tenant $tenantID -ServicePrincipal -Subscription $subscriptionID -Verbose } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Check the certificate with thumbprint of $certificateThumbprint is installed properly in the local certificate store" Write-Host "And that the certificate is registered as a client certificate in Azure on the service principal with the id of $applicationId" } Try { #Create a storage context using the "Storage Blob Data Contributor" role assigned to the Storage Account container $context = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount -Verbose } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Unable to create a storage context to the Azure Storage account of $storageAccountName" Write-Host "Check that the service principal with ID of $applicationId has Storage Blob Data Contributor rights to the Container being accessed" } Try { #Get the locally or network stored backup files #A Where-Object clause could be added here depending on how many backup files there are # | Where{$_.LastWriteTime -le (GetDate).AddDays(-30)} for instance $backupFiles = Get-ChildItem -Path $backupPath -Verbose If($backupFiles -eq $null) { Throw } } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Unable to access backup files; is the file path of $backupPath correct?" Write-Host "Or is the folder empty?" Write-Host "Or does the script's account have access to the folder $backupPath; whether permissions or network problems?" } #Loop through the backup files and look for files older than 30 days then remove them Foreach($backupFile in $backupFiles) { If((Test-Path $backupFile.fullName -OlderThan (Get-Date).AddDays(-$ageOfToBeDeletedBackupsInDays)) -and $ageOfToBeDeletedBackupsInDays > 0) { Try { #COMMENTED OUT FOR SAFETY ↓ #Remove-Item -Path $backupFile.fullName -Force -Verbose } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Unable to delete backup file $backupFile.fullName which is older than 30 days" Write-Host "Does the script or account running the script have the necessary rights to delete the backup file?" } } #If the file is newer than an inputted negative number of hours, upload it to the Storage Account container ElseIf(Test-Path $backupFile.fullName -NewerThan (Get-Date).AddHours($ageOfBackupFileInHours)) { Try { Set-AzStorageBlobContent -File $backupFile.fullName -Container $containerName -Blob $BackupFile.Name -Context $context -StandardBlobTier $storageTier -Verbose } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Unable to upload the backup file $backupFile.fullName to the Azure storage account container: $containerName" } } } Try { #Get the backup files already uploaded to the Storage Account container and delete them if they are older than an inputted negative number of days $cloudBackupFiles = Get-AzStorageBlob -Container $containerName -Blob * -Context $context If($cloudBackupFiles -eq $null) { Throw } Foreach($cloudBackupFile in $cloudBackupFiles) { If($cloudBackupFile.LastModified.DateTime -lt (Get-Date).AddDays($ageOfToBeDeletedBackupsInDays)) { #COMMENTED OUT FOR SAFETY ↓ #Remove-AzStorageBlob -Container $containerName -Blob $cloudBackupFile.Name -Context $context -Verbose } } } Catch { Write-Host "Encountered Error:"$_.Exception.Message Write-Host "Unable to access the backup files in the container; is the file path of $backupPath correct?" Write-Host "Check that the service principal with ID of $applicationId has Storage Blob Data Contributor rights to the Container being accessed" } #Stop logging Write-Host "Script has finished at $(Get-Date)" Stop-Transcript } Backup-ToAzureBlobContainer -certificateThumbprint "xxxxxxxc8488c49eb14858cf8be8414002cc5dc" -tenantID "xxxxxxx-ab5a-497a-a903-d5cee636e412" ` -applicationId "xxxxxxx-c070-497b-8442-094f796ab4d0" -subscriptionID "xxxxxxxx-ff7b-4b5a-ab91-16da2cee1863" -storageAccountName "somestorageaccountname" ` -backupPath "C:\Backups" -loggingPath "C:\Scripts" -containerName "somecontainername" -storageTier "Cool" -ageOfBackupFileInHours -6 -ageOfToBeDeletedBackupsInDays -30