├── version.txt ├── .gitattributes ├── README.md ├── .gitignore ├── LetsUploadConfig.dist.ps1 ├── LetsDownload.ps1 └── LetsUpload.ps1 /version.txt: -------------------------------------------------------------------------------- 1 | 0.8 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LetsUpload-Compress-and-Upload 2 | Compresses and uploads a folder for backup to LetsUpload.io 3 | 4 | # Requirements 5 | Install 7-zip and put into system path 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore files 2 | /LetsUploadConfig.ps1 3 | /EmailBody.log 4 | /VerboseEmail.log 5 | /LetsUploadDebug.log 6 | 7 | # Ignoring directories 8 | # Both the directory itself and its contents will be ignored. 9 | tests/ 10 | -------------------------------------------------------------------------------- /LetsUploadConfig.dist.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | LetsUpload Backup Utility Config File 5 | 6 | .DESCRIPTION 7 | LetsUpload Backup Utility Config File 8 | 9 | .FUNCTIONALITY 10 | Compresses and uploads folder contents to LetsUpload.io 11 | 12 | .PARAMETER UploadFolder 13 | Specifies the folder on local filesystem to compress and upload 14 | DO NOT include trailing slash "\" 15 | 16 | .PARAMETER UploadName 17 | Specifies the name (description) of the archive to be created as well as letsupload folder name 18 | 19 | .NOTES 20 | Create account and get API keys from https://www.letsupload.io then fill in $APIKey variables under USER VARIABLES. 21 | Run from task scheduler daily. 22 | Windows only. 23 | API: https://letsupload.io/api 24 | Install latest 7-zip and put into system path. 25 | 26 | .EXAMPLE 27 | PS C:\Users\username> C:\scripts\letsupload.ps1 "C:\Path\To\Folder\To\Backup" "Backup Description (email, work, etc)" 28 | 29 | #> 30 | 31 | <### USER VARIABLES ###> 32 | $APIKey1 = "64_character_API_string" 33 | $APIKey2 = "64_character_API_string" 34 | $ArchivePassword = "supersecretpassword" # Password to 7z archive 35 | $BackupLocation = "C:\BACKUP" # Location archive files will be stored 36 | $VolumeSize = "100m" # Size of archive volume parts - maximum 200m recommended - valid suffixes for size units are (b|k|m|g) 37 | $IsPublic = 0 # 0 = Private, 1 = Unlisted, 2 = Public in site search 38 | $VerboseConsole = $True # If true, will output debug to console 39 | $VerboseFile = $True # If true, will output debug to file 40 | $DeleteOldBackups = $False # If true, will delete local backups older than N days 41 | $DaysToKeep = 5 # Number of days to keep backups - all others will be deleted at end of script 42 | $MaxUploadTries = 5 # If file upload error, number of times to retry before giving up 43 | 44 | <### EMAIL VARIABLES ###> 45 | $EmailFrom = "notify@mydomain.net" 46 | $EmailTo = "admin@mydomain.net" 47 | $Subject = "Offsite Backup: $UploadName" 48 | $SMTPServer = "mydomain.net" 49 | $SMTPAuthUser = "notify@mydomain.net" 50 | $SMTPAuthPass = "supersecretpassword" 51 | $SMTPPort = 587 52 | $UseSSL = $True 53 | $UseHTML = $True 54 | $AttachDebugLog = $True # If true, will attach debug log to email report - must also select $VerboseFile 55 | $MaxAttachmentSize = 1 # Size in MB -------------------------------------------------------------------------------- /LetsDownload.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | LetsUpload Download Utility 5 | 6 | .DESCRIPTION 7 | Download backup folder contents from LetsUpload.io 8 | 9 | .FUNCTIONALITY 10 | Download backup folder contents from LetsUpload.io 11 | 12 | .PARAMETER DownloadFolder 13 | Specifies the folder on local filesystem where the files will be saved 14 | 15 | .PARAMETER BackupName 16 | Specifies the name (description) of the archive previously uploaded to letsupload 17 | Script uses this parameter to find the most recent backup 18 | 19 | .NOTES 20 | Create account and get API keys from https://www.letsupload.io then fill in $APIKey variables under USER VARIABLES 21 | Run to obtain previously uploaded backups 22 | Windows only 23 | API: https://letsupload.io/api.html 24 | 25 | .EXAMPLE 26 | PS C:\Users\username> C:\scripts\letsupload.ps1 "C:\Path\To\Folder\To\Backup" "Backup Description (email, work, etc)" 27 | 28 | #> 29 | 30 | Param( 31 | [Parameter(Mandatory=$True)] 32 | [ValidatePattern("^[A-Z]\:\\")] 33 | [String]$DownloadFolder, 34 | 35 | [Parameter(Mandatory=$True)] 36 | [String]$BackupName 37 | ) 38 | 39 | <### CONFIG ###> 40 | Try { 41 | .("$PSScriptRoot\LetsUploadConfig.ps1") 42 | } 43 | Catch { 44 | Write-Output "$(Get-Date) -f G) : ERROR : Unable to load supporting PowerShell Scripts : $query `n$Error[0]" | out-file "$PSScriptRoot\PSError.log" -append 45 | } 46 | 47 | <### FUNCTIONS ###> 48 | Function Debug ($DebugOutput) { 49 | If ($VerboseFile) {Write-Output "$(Get-Date -f G) : $DebugOutput" | Out-File $DebugLog -Encoding ASCII -Append} 50 | If ($VerboseConsole) {Write-Host "$(Get-Date -f G) : $DebugOutput"} 51 | } 52 | 53 | Function Email ($EmailOutput) { 54 | Write-Output $EmailOutput | Out-File $VerboseEmail -Encoding ASCII -Append 55 | } 56 | 57 | Function EmailResults { 58 | Try { 59 | $Body = (Get-Content -Path $VerboseEmail | Out-String ) 60 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Attachment = New-Object System.Net.Mail.Attachment $DebugLog} 61 | $Message = New-Object System.Net.Mail.Mailmessage $EmailFrom, $EmailTo, $Subject, $Body 62 | $Message.IsBodyHTML = $HTML 63 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Message.Attachments.Add($DebugLog)} 64 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort 65 | $SMTP.EnableSsl = $SSL 66 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass); 67 | $SMTP.Send($Message) 68 | } 69 | Catch { 70 | Debug "Email ERROR : `n$Error[0]" 71 | } 72 | } 73 | 74 | Function ElapsedTime ($TimeSpan) { 75 | If (([int]($TimeSpan).TotalHours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).TotalHours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).TotalHours) hours "} 76 | If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "} 77 | If (([int]($TimeSpan).Seconds) -eq 0) {$Seconds = ""} ElseIf (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"} 78 | Return "$Hours$Minutes$Seconds" 79 | } 80 | 81 | <### BEGIN SCRIPT ###> 82 | $StartScript = Get-Date 83 | 84 | <# Clear out error variable #> 85 | $Error.Clear() 86 | 87 | <# Delete old debug file and create new #> 88 | $VerboseEmail = "$PSScriptRoot\VerboseEmail.log" 89 | If (Test-Path $VerboseEmail) {Remove-Item -Force -Path $VerboseEmail} 90 | New-Item $VerboseEmail 91 | $DebugLog = "$PSScriptRoot\LetsUploadDebug.log" 92 | If (Test-Path $DebugLog) {Remove-Item -Force -Path $DebugLog} 93 | New-Item $DebugLog 94 | 95 | <# Validate backup folder #> 96 | $DF = $DownloadFolder -Replace('\\$','') 97 | If (Test-Path $DF) { 98 | Debug "The folder to store downloaded files is $DF" 99 | } Else { 100 | Debug "Error : The folder to be backed up could not be found. Quitting Script" 101 | Debug "$DownloadFolder does not exist" 102 | Email "Error : The folder to be backed up could not be found. Quitting Script" 103 | Email "$DownloadFolder does not exist" 104 | EmailResults 105 | Exit 106 | } 107 | 108 | <# Authorize and get access token #> 109 | Debug "Getting access token from LetsUpload" 110 | $URIAuth = "https://letsupload.io/api/v2/authorize" 111 | $AuthBody = @{ 112 | 'key1' = $APIKey1; 113 | 'key2' = $APIKey2; 114 | } 115 | Try{ 116 | $Auth = Invoke-RestMethod -Method GET $URIAuth -Body $AuthBody -ContentType 'application/json; charset=utf-8' 117 | } 118 | Catch { 119 | Debug "LetsUpload Authentication ERROR : $Error" 120 | Email "LetsUpload Authentication ERROR : Check Debug Log" 121 | Email "LetsUpload Authentication ERROR : $Error" 122 | EmailResults 123 | Exit 124 | } 125 | $AccessToken = $Auth.data.access_token 126 | $AccountID = $Auth.data.account_id 127 | Debug "Access Token : $AccessToken" 128 | Debug "Account ID : $AccountID" 129 | 130 | <# Get folder_id of last upload #> 131 | $URIFolderListing = "https://letsupload.io/api/v2/folder/listing" 132 | $FLBody = @{ 133 | 'access_token' = $AccessToken; 134 | 'account_id' = $AccountID; 135 | } 136 | Try{ 137 | $FolderListing = Invoke-RestMethod -Method GET $URIFolderListing -Body $FLBody -ContentType 'application/json; charset=utf-8' 138 | } 139 | Catch { 140 | Debug "ERROR obtaining backup folder ID : $Error" 141 | Email "ERROR obtaining backup folder ID : Check Debug Log" 142 | Email "ERROR obtaining backup folder ID : $Error" 143 | EmailResults 144 | Exit 145 | } 146 | $NewestBackup = $FolderListing.data.folders | Sort-Object date_added -Descending | Where {$_.folderName -match $BackupName} | Select -First 1 147 | $FolderID = $NewestBackup.id 148 | $FolderName = $NewestBackup.folderName 149 | Debug "Folder Name: $FolderName" 150 | Debug "Folder ID : $FolderID" 151 | 152 | 153 | <# Get file listing within latest backup folder #> 154 | $URIFolderListing = "https://letsupload.io/api/v2/folder/listing" 155 | $FLBody = @{ 156 | 'access_token' = $AccessToken; 157 | 'account_id' = $AccountID; 158 | 'parent_folder_id' = $FolderID; 159 | } 160 | Try{ 161 | $FileListing = Invoke-RestMethod -Method GET $URIFolderListing -Body $FLBody -ContentType 'application/json; charset=utf-8' 162 | } 163 | Catch { 164 | Debug "ERROR obtaining backup file listing : $Error" 165 | Email "ERROR obtaining backup file listing : Check Debug Log" 166 | Email "ERROR obtaining backup file listing : $Error" 167 | EmailResults 168 | Exit 169 | } 170 | 171 | $Count = ($FileListing.data.files).Count 172 | Debug "File count: $Count" 173 | $N = 1 174 | $DLSuccess = 0 175 | Debug "Starting file download" 176 | 177 | <# Loop through results and download files #> 178 | $FileListing.data.files | ForEach { 179 | $FileID = $_.id 180 | $FileName = $_.filename 181 | $FileURL = $_.url_file 182 | Debug "----------------------------" 183 | Debug "File $N of $Count" 184 | Debug "File ID : $FileID" 185 | Debug "File Name : $FileName" 186 | 187 | $URIDownload = "https://letsupload.io/api/v2/file/download" 188 | $DLBody = @{ 189 | 'access_token' = $AccessToken; 190 | 'account_id' = $AccountID; 191 | 'file_id' = $FileID; 192 | } 193 | Try{ 194 | $FileDownload = Invoke-RestMethod -Method GET $URIDownload -Body $DLBody -ContentType 'application/json; charset=utf-8' 195 | } 196 | Catch { 197 | Debug "ERROR obtaining download URL : $Error" 198 | Email "ERROR obtaining download URL : Check Debug Log" 199 | Email "ERROR obtaining download URL : $Error" 200 | EmailResults 201 | Exit 202 | } 203 | 204 | $FDStatus = $FileDownload._status 205 | If ($FDStatus -notmatch "success") { 206 | Debug "Error : Could not obtain download URL" 207 | } Else { 208 | $DownloadURL = $FileDownload.data.download_url 209 | Debug "Download URL: $DownloadURL" 210 | <# Download file using BITS #> 211 | Try { 212 | $BeginDL = Get-Date 213 | Import-Module BitsTransfer 214 | Start-BitsTransfer -Source $DownloadURL -Destination "$DF\$FileName" 215 | Debug "File $N downloaded in $(ElapsedTime (New-TimeSpan $BeginDL))" 216 | If (Test-Path "$DF\$FileName") {$DLSuccess++} 217 | } 218 | Catch { 219 | Debug "BITS ERROR downloading file $N of $FileCount : $Error" 220 | } 221 | } 222 | 223 | $N++ 224 | } 225 | 226 | <# Finish up and email results #> 227 | Debug "----------------------------" 228 | If ($DLSuccess -eq $Count) { 229 | Debug "Download sucessful. $Count files downloaded to $DF" 230 | Email "Download sucessful. $Count files downloaded to $DF" 231 | } Else { 232 | Debug "Download FAILED to download $($Count - $DLSuccess) files - check debug log" 233 | Email "Download FAILED to download $($Count - $DLSuccess) files - check debug log" 234 | } 235 | 236 | Email " " 237 | Debug "Script completed in $(ElapsedTime (New-TimeSpan $StartScript))" 238 | Email "Script completed in $(ElapsedTime (New-TimeSpan $StartScript))" 239 | Debug "Sending Email" 240 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -gt $MaxAttachmentSize)){ 241 | Email "Debug log size exceeds maximum attachment size. Please see log file in script folder" 242 | } 243 | EmailResults -------------------------------------------------------------------------------- /LetsUpload.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | LetsUpload Backup Utility 5 | 6 | .DESCRIPTION 7 | Compresses and uploads folder contents to LetsUpload.io 8 | 9 | .FUNCTIONALITY 10 | Compresses and uploads folder contents to LetsUpload.io 11 | 12 | .PARAMETER UploadFolder 13 | Specifies the folder on local filesystem to compress and upload 14 | 15 | .PARAMETER UploadName 16 | Specifies the name (description) of the archive to be created as well as letsupload folder name 17 | 18 | .NOTES 19 | Create account and get API keys from https://www.letsupload.io then fill in $APIKey variables under USER VARIABLES 20 | Run from task scheduler daily 21 | Windows only 22 | API: https://letsupload.io/api 23 | Install latest 7-zip and put into system path 24 | 25 | .EXAMPLE 26 | PS C:\Users\username> C:\scripts\letsupload.ps1 "C:\Path\To\Folder\To\Backup" "Backup Description (email, work, etc)" 27 | 28 | #> 29 | 30 | Param( 31 | [Parameter(Mandatory=$True)] 32 | [ValidatePattern("^[A-Z]\:\\")] 33 | [String]$UploadFolder, 34 | 35 | [Parameter(Mandatory=$False)] 36 | [String]$UploadName 37 | ) 38 | 39 | <### CONFIG ###> 40 | Try { 41 | .("$PSScriptRoot\LetsUploadConfig.ps1") 42 | } 43 | Catch { 44 | Write-Output "$(Get-Date) -f G) : ERROR : Unable to load supporting PowerShell Scripts : $query $(Error[0])" | out-file "$PSScriptRoot\PSError.log" -append 45 | } 46 | 47 | <### FUNCTIONS ###> 48 | Function Debug ($DebugOutput) { 49 | If ($VerboseFile) {Write-Output "$(Get-Date -f G) : $DebugOutput" | Out-File $DebugLog -Encoding ASCII -Append} 50 | If ($VerboseConsole) {Write-Host "$(Get-Date -f G) : $DebugOutput"} 51 | } 52 | 53 | Function Email ($EmailOutput) { 54 | If ($UseHTML){ 55 | If ($EmailOutput -match "\[OK\]") {$EmailOutput = $EmailOutput -Replace "\[OK\]","[OK]"} 56 | If ($EmailOutput -match "\[INFO\]") {$EmailOutput = $EmailOutput -Replace "\[INFO\]","[INFO]"} 57 | If ($EmailOutput -match "\[ERROR\]") {$EmailOutput = $EmailOutput -Replace "\[ERROR\]","[ERROR]"} 58 | If ($EmailOutput -match "^\s$") {$EmailOutput = $EmailOutput -Replace "\s"," "} 59 | Write-Output "$EmailOutput" | Out-File $EmailBody -Encoding ASCII -Append 60 | } Else { 61 | Write-Output $EmailOutput | Out-File $EmailBody -Encoding ASCII -Append 62 | } 63 | } 64 | 65 | Function EmailResults { 66 | Try { 67 | $Body = (Get-Content -Path $EmailBody | Out-String ) 68 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Attachment = New-Object System.Net.Mail.Attachment $DebugLog} 69 | $Message = New-Object System.Net.Mail.Mailmessage $EmailFrom, $EmailTo, $Subject, $Body 70 | $Message.IsBodyHTML = $UseHTML 71 | If (($AttachDebugLog) -and (Test-Path $DebugLog) -and (((Get-Item $DebugLog).length/1MB) -lt $MaxAttachmentSize)){$Message.Attachments.Add($DebugLog)} 72 | $SMTP = New-Object System.Net.Mail.SMTPClient $SMTPServer,$SMTPPort 73 | $SMTP.EnableSsl = $UseSSL 74 | $SMTP.Credentials = New-Object System.Net.NetworkCredential($SMTPAuthUser, $SMTPAuthPass); 75 | $SMTP.Send($Message) 76 | } 77 | Catch { 78 | Debug "Email ERROR : $($Error[0])" 79 | } 80 | } 81 | 82 | Function Plural ($Integer) { 83 | If ($Integer -eq 1) {$S = ""} Else {$S = "s"} 84 | Return $S 85 | } 86 | 87 | Function ElapsedTime ($EndTime) { 88 | $TimeSpan = New-Timespan $EndTime 89 | If (([int]($TimeSpan).Hours) -eq 0) {$Hours = ""} ElseIf (([int]($TimeSpan).Hours) -eq 1) {$Hours = "1 hour "} Else {$Hours = "$([int]($TimeSpan).Hours) hours "} 90 | If (([int]($TimeSpan).Minutes) -eq 0) {$Minutes = ""} ElseIf (([int]($TimeSpan).Minutes) -eq 1) {$Minutes = "1 minute "} Else {$Minutes = "$([int]($TimeSpan).Minutes) minutes "} 91 | If (([int]($TimeSpan).Seconds) -eq 1) {$Seconds = "1 second"} Else {$Seconds = "$([int]($TimeSpan).Seconds) seconds"} 92 | 93 | If (($TimeSpan).TotalSeconds -lt 1) { 94 | $Return = "less than 1 second" 95 | } Else { 96 | $Return = "$Hours$Minutes$Seconds" 97 | } 98 | Return $Return 99 | } 100 | 101 | Function MakeArchive { 102 | $StartArchive = Get-Date 103 | Debug "----------------------------" 104 | Debug "Create archive : $BackupName" 105 | Debug "Archive folder : $UF" 106 | $VolumeSwitch = "-v$VolumeSize" 107 | $PWSwitch = "-p$ArchivePassword" 108 | Try { 109 | $SevenZip = & cmd /c 7z a $VolumeSwitch -t7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -mhe=on $PWSwitch "$BackupLocation\$BackupName\$BackupName.7z" "$UF\*" | Out-String 110 | Debug $SevenZip 111 | Debug "Archive creation finished in $(ElapsedTime $StartArchive)" 112 | Debug "Wait a few seconds to make sure archive is finished" 113 | Email "[OK] 7z archive created" 114 | Start-Sleep -Seconds 3 115 | } 116 | Catch { 117 | Debug "[ERROR] Archive Creation : $($Error[0])" 118 | Email "[ERROR] Archive Creation : Check Debug Log" 119 | Email "[ERROR] Archive Creation : $($Error[0])" 120 | EmailResults 121 | Exit 122 | } 123 | } 124 | 125 | Function OffsiteUpload { 126 | 127 | $BeginOffsiteUpload = Get-Date 128 | Debug "----------------------------" 129 | Debug "Begin offsite upload process" 130 | 131 | <# Authorize and get access token #> 132 | Debug "Getting access token from LetsUpload" 133 | $URIAuth = "https://letsupload.io/api/v2/authorize" 134 | $AuthBody = @{ 135 | 'key1' = $APIKey1; 136 | 'key2' = $APIKey2; 137 | } 138 | $GetAccessTokenTries = 1 139 | Do { 140 | Try{ 141 | $Auth = Invoke-RestMethod -Method GET $URIAuth -Body $AuthBody -ContentType 'application/json; charset=utf-8' 142 | $AccessToken = $Auth.data.access_token 143 | $AccountID = $Auth.data.account_id 144 | Debug "Access Token : $AccessToken" 145 | Debug "Account ID : $AccountID" 146 | } 147 | Catch { 148 | Debug "LetsUpload Authentication ERROR : Try $GetAccessTokenTries : $($Error[0])" 149 | } 150 | $GetAccessTokenTries++ 151 | } Until (($GetAccessTokenTries -gt $MaxUploadTries) -or ($AccessToken -match "^\w{128}$")) 152 | If ($GetAccessTokenTries -gt $MaxUploadTries) { 153 | Debug "LetsUpload Authentication ERROR : Giving up" 154 | Email "[ERROR] LetsUpload Authentication : Check Debug Log" 155 | EmailResults 156 | Exit 157 | } 158 | 159 | <# Create Folder #> 160 | Debug "----------------------------" 161 | Debug "Creating Folder $BackupName at LetsUpload" 162 | $URICF = "https://letsupload.io/api/v2/folder/create" 163 | $CFBody = @{ 164 | 'access_token' = $AccessToken; 165 | 'account_id' = $AccountID; 166 | 'folder_name' = $BackupName; 167 | 'is_public' = $IsPublic; 168 | } 169 | $CreateFolderTries = 1 170 | Do { 171 | Try { 172 | $CreateFolder = Invoke-RestMethod -Method GET $URICF -Body $CFBody -ContentType 'application/json; charset=utf-8' 173 | $CFResponse = $CreateFolder.response 174 | $FolderID = $CreateFolder.data.id 175 | $FolderURL = $CreateFolder.data.url_folder 176 | Debug "Response : $CFResponse" 177 | Debug "Folder ID : $FolderID" 178 | Debug "Folder URL : $FolderURL" 179 | } 180 | Catch { 181 | Debug "LetsUpload Folder Creation ERROR : Try $CreateFolderTries : $($Error[0])" 182 | } 183 | $CreateFolderTries++ 184 | } Until (($CreateFolderTries -gt $MaxUploadTries) -or ($FolderID -match "^\d+$")) 185 | If ($CreateFolderTries -gt $MaxUploadTries) { 186 | Debug "LetsUpload Folder Creation ERROR : Giving up" 187 | Email "[ERROR] LetsUpload Folder Creation Error : Check Debug Log" 188 | EmailResults 189 | Exit 190 | } 191 | 192 | <# Upload Files #> 193 | $StartUpload = Get-Date 194 | Debug "----------------------------" 195 | Debug "Begin uploading files to LetsUpload" 196 | $CountArchVol = (Get-ChildItem "$BackupLocation\$BackupName").Count 197 | Debug "There are $CountArchVol files to upload" 198 | $UploadCounter = 1 199 | $TotalUploadErrors = 0 200 | 201 | Get-ChildItem "$BackupLocation\$BackupName" | ForEach { 202 | 203 | $FileName = $_.Name; 204 | $FilePath = $_.FullName; 205 | $FileSize = $_.Length; 206 | 207 | $UploadURI = "https://letsupload.io/api/v2/file/upload"; 208 | Debug "----------------------------" 209 | Debug "Encoding file $FileName" 210 | $BeginEnc = Get-Date 211 | Try { 212 | $FileBytes = [System.IO.File]::ReadAllBytes($FilePath); 213 | $FileEnc = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetString($FileBytes); 214 | } 215 | Catch { 216 | Debug "Error in encoding file $UploadCounter." 217 | Debug "$($Error[0])" 218 | } 219 | Debug "Finished encoding file in $(ElapsedTime $BeginEnc)"; 220 | $Boundary = [System.Guid]::NewGuid().ToString(); 221 | $LF = "`r`n"; 222 | 223 | $BodyLines = ( 224 | "--$Boundary", 225 | "Content-Disposition: form-data; name=`"access_token`"", 226 | '', 227 | $AccessToken, 228 | "--$Boundary", 229 | "Content-Disposition: form-data; name=`"account_id`"", 230 | '', 231 | $AccountID, 232 | "--$Boundary", 233 | "Content-Disposition: form-data; name=`"folder_id`"", 234 | '', 235 | $FolderID, 236 | "--$Boundary", 237 | "Content-Disposition: form-data; name=`"upload_file`"; filename=`"$FileName`"", 238 | "Content-Type: application/json", 239 | '', 240 | $FileEnc, 241 | "--$Boundary--" 242 | ) -join $LF 243 | 244 | Debug "Uploading $FileName - $UploadCounter of $CountArchVol" 245 | $UploadTries = 1 246 | $BeginUpload = Get-Date 247 | Do { 248 | $Error.Clear() 249 | $Upload = $UResponse = $UURL = $USize = $UStatus = $NULL 250 | Try { 251 | $Upload = Invoke-RestMethod -Uri $UploadURI -Method POST -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $BodyLines 252 | $UResponse = $Upload.response 253 | $UURL = $Upload.data.url 254 | $USize = $Upload.data.size 255 | $USizeFormatted = "{0:N2}" -f (($USize)/1MB) 256 | $UStatus = $Upload._status 257 | $UFileID = $upload.data.file_id 258 | If ($USize -ne $FileSize) {Throw "Local and remote filesizes do not match! Local file: $Filesize ::: Remote file: $USize"} 259 | Debug "Upload try $UploadTries" 260 | Debug "Response : $UResponse" 261 | Debug "File ID : $UFileID" 262 | Debug "URL : $UURL" 263 | Debug "Size : $USizeFormatted mb" 264 | Debug "Status : $UStatus" 265 | Debug "Finished uploading file in $(ElapsedTime $BeginUpload)" 266 | } 267 | Catch { 268 | Debug "Upload try $UploadTries" 269 | Debug "[ERROR] : $($Error[0])" 270 | If (($USize -gt 0) -and ($UFileID -match '\d+')) { 271 | Debug "Deleting file due to size mismatch" 272 | $URIDF = "https://letsupload.io/api/v2/file/delete" 273 | $DFBody = @{ 274 | 'access_token' = $AccessToken; 275 | 'account_id' = $AccountID; 276 | 'file_id' = $UFileID; 277 | } 278 | Try { 279 | $DeleteFile = Invoke-RestMethod -Method GET $URIDF -Body $DFBody -ContentType 'application/json; charset=utf-8' 280 | Debug "Mismatched upload deleted. Trying again." 281 | } 282 | Catch { 283 | Debug "Mismatched upload file delete ERROR : $($Error[0])" 284 | Email "[ERROR] Un-Repairable Upload Mismatch! See debug log. Quitting script." 285 | EmailResults 286 | Exit 287 | } 288 | } 289 | $TotalUploadErrors++ 290 | } 291 | $UploadTries++ 292 | } Until (($UploadTries -gt $MaxUploadTries) -or ($UStatus -match "success")) 293 | 294 | If (-not($UStatus -Match "success")) { 295 | Debug "Error in uploading file number $UploadCounter. Check the log for errors." 296 | Email "[ERROR] in uploading file number $UploadCounter. Check the log for errors." 297 | EmailResults 298 | Exit 299 | } 300 | $UploadCounter++ 301 | } 302 | 303 | <# Count remote files #> 304 | Debug "----------------------------" 305 | Debug "Counting uploaded files at LetsUpload" 306 | $URIFL = "https://letsupload.io/api/v2/folder/listing" 307 | $FLBody = @{ 308 | 'access_token' = $AccessToken; 309 | 'account_id' = $AccountID; 310 | 'parent_folder_id' = $FolderID; 311 | } 312 | Try { 313 | $FolderListing = Invoke-RestMethod -Method GET $URIFL -Body $FLBody -ContentType 'application/json; charset=utf-8' 314 | } 315 | Catch { 316 | Debug "LetsUpload Folder Listing ERROR : $($Error[0])" 317 | Email "[ERROR] LetsUpload Folder Listing : Check Debug Log" 318 | Email "[ERROR] LetsUpload Folder Listing : $($Error[0])" 319 | } 320 | $FolderListingStatus = $FolderListing._status 321 | $RemoteFileCount = ($FolderListing.data.files.id).Count 322 | 323 | <# Report results #> 324 | If ($FolderListingStatus -match "success") { 325 | Debug "There are $RemoteFileCount file$(Plural $RemoteFileCount) in the remote folder" 326 | If ($RemoteFileCount -eq $CountArchVol) { 327 | Debug "----------------------------" 328 | Debug "Finished uploading $CountArchVol file$(Plural $CountArchVol) in $(ElapsedTime $BeginOffsiteUpload)" 329 | If ($TotalUploadErrors -gt 0){Debug "$TotalUploadErrors upload error$(Plural $TotalUploadErrors) successfully resolved"} 330 | Debug "Upload sucessful. $CountArchVol file$(Plural $CountArchVol) uploaded to $FolderURL" 331 | If ($UseHTML) { 332 | Email "[OK] $CountArchVol file$(Plural $CountArchVol) uploaded to letsupload.io" 333 | } Else { 334 | Email "[OK] $CountArchVol file$(Plural $CountArchVol) uploaded to $FolderURL" 335 | } 336 | } Else { 337 | Debug "----------------------------" 338 | Debug "Finished uploading in $(ElapsedTime $StartUpload)" 339 | Debug "[ERROR] Number of archive files uploaded does not match count in remote folder" 340 | Debug "[ERROR] Archive volumes : $CountArchVol" 341 | Debug "[ERROR] Remote file count : $RemoteFileCount" 342 | Email "[ERROR] Number of archive files uploaded does not match count in remote folder - see debug log" 343 | } 344 | } Else { 345 | Debug "----------------------------" 346 | Debug "Error : Unable to obtain file count from remote folder" 347 | Email "[ERROR] Unable to obtain uploaded file count from remote folder - see debug log" 348 | } 349 | 350 | } 351 | 352 | Function DeleteEmptyRemoteFolders { 353 | 354 | <# Authorize and get access token #> 355 | Debug "----------------------------" 356 | Debug "Begin delete empty folder process" 357 | Debug "Getting access token from LetsUpload" 358 | $URIAuth = "https://letsupload.io/api/v2/authorize" 359 | $AuthBody = @{ 360 | 'key1' = $APIKey1; 361 | 'key2' = $APIKey2; 362 | } 363 | $GetAccessTokenTries = 1 364 | Do { 365 | Try{ 366 | $Auth = Invoke-RestMethod -Method GET $URIAuth -Body $AuthBody -ContentType 'application/json; charset=utf-8' 367 | $AccessToken = $Auth.data.access_token 368 | $AccountID = $Auth.data.account_id 369 | Debug "Access Token : $AccessToken" 370 | Debug "Account ID : $AccountID" 371 | } 372 | Catch { 373 | Debug "LetsUpload Authentication ERROR : Try $GetAccessTokenTries : $($Error[0])" 374 | } 375 | $GetAccessTokenTries++ 376 | } Until (($GetAccessTokenTries -gt $MaxUploadTries) -or ($AccessToken -match "^\w{128}$")) 377 | If ($GetAccessTokenTries -gt $MaxUploadTries) { 378 | Debug "LetsUpload Authentication ERROR : Giving up" 379 | Email "[ERROR] LetsUpload Authentication : Check Debug Log" 380 | EmailResults 381 | Exit 382 | } 383 | 384 | Debug "----------------------------" 385 | Debug "Deleting Empty Folders at LetsUpload" 386 | $URIFL = "https://letsupload.io/api/v2/folder/listing" 387 | $FLBody = @{ 388 | 'access_token' = $AccessToken; 389 | 'account_id' = $AccountID; 390 | } 391 | $FolderInfo = Invoke-RestMethod -Method GET $URIFL -Body $FLBody -ContentType 'application/json; charset=utf-8' 392 | $FolderListing = $FolderInfo.data.folders 393 | $DeleteFolderCount = 0 394 | ForEach ($Folder in $FolderListing) { 395 | $FolderID = $Folder.id 396 | $FolderName = $Folder.folderName 397 | $FileCount = $Folder.file_count 398 | If ($FileCount -eq 0) { 399 | Try { 400 | Debug "----------------------------" 401 | Debug "Deleting Folder: $FolderName" 402 | $URIDF = "https://letsupload.io/api/v2/folder/delete" 403 | $DFBody = @{ 404 | 'access_token' = $AccessToken; 405 | 'account_id' = $AccountID; 406 | 'folder_id' = $FolderID; 407 | } 408 | $FolderDelete = Invoke-RestMethod -Method GET $URIDF -Body $DFBody -ContentType 'application/json; charset=utf-8' 409 | Debug $($FolderDelete.response) 410 | $DeleteFolderCount++ 411 | If ($FolderDelete._status -ne "success") {Throw "Empty folder $FolderName delete FAILED"} 412 | } 413 | Catch { 414 | Debug "Folder delete ERROR : $($Error[0])" 415 | Email "[ERROR] Folder delete ERROR : $($Error[0])" 416 | } 417 | } 418 | } 419 | If ($DeleteFolderCount -eq 0) { 420 | Debug "No empty folders to delete" 421 | } 422 | } 423 | 424 | Function CheckForUpdates { 425 | Debug "----------------------------" 426 | Debug "Checking for script update at GitHub" 427 | $GitHubVersion = $LocalVersion = $NULL 428 | $GetGitHubVersion = $GetLocalVersion = $False 429 | $GitHubVersionTries = 1 430 | Do { 431 | Try { 432 | $GitHubVersion = [decimal](Invoke-WebRequest -UseBasicParsing -Method GET -URI https://raw.githubusercontent.com/palinkas-jo-reggelt/LetsUpload-Compress-and-Upload/master/version.txt).Content 433 | $GetGitHubVersion = $True 434 | } 435 | Catch { 436 | Debug "[ERROR] Obtaining GitHub version : Try $GitHubVersionTries : Obtaining version number: $($Error[0])" 437 | } 438 | $GitHubVersionTries++ 439 | } Until (($GitHubVersion -gt 0) -or ($GitHubVersionTries -gt $MaxUploadTries)) 440 | If (Test-Path "$PSScriptRoot\version.txt") { 441 | $LocalVersion = [decimal](Get-Content "$PSScriptRoot\version.txt") 442 | $GetLocalVersion = $True 443 | } 444 | If (($GetGitHubVersion) -and ($GetLocalVersion)) { 445 | If ($LocalVersion -lt $GitHubVersion) { 446 | Debug "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/LetsUpload-Compress-and-Upload" 447 | If ($UseHTML) { 448 | Email "[INFO] Upgrade to version $GitHubVersion available at GitHub" 449 | } Else { 450 | Email "[INFO] Upgrade to version $GitHubVersion available at https://github.com/palinkas-jo-reggelt/LetsUpload-Compress-and-Upload" 451 | } 452 | } Else { 453 | Debug "Backup & Upload script is latest version: $GitHubVersion" 454 | } 455 | } Else { 456 | If ((-not($GetGitHubVersion)) -and (-not($GetLocalVersion))) { 457 | Debug "[ERROR] Version test failed : Could not obtain either GitHub nor local version information" 458 | Email "[ERROR] Version check failed" 459 | } ElseIf (-not($GetGitHubVersion)) { 460 | Debug "[ERROR] Version test failed : Could not obtain version information from GitHub" 461 | Email "[ERROR] Version check failed" 462 | } ElseIf (-not($GetLocalVersion)) { 463 | Debug "[ERROR] Version test failed : Could not obtain local install version information" 464 | Email "[ERROR] Version check failed" 465 | } Else { 466 | Debug "[ERROR] Version test failed : Unknown reason - file issue at GitHub" 467 | If ($UseHTML) { 468 | Email "[ERROR] Version test failed : Unknown reason - file issue at GitHub" 469 | } Else { 470 | Email "[ERROR] Version check failed" 471 | } 472 | } 473 | } 474 | } 475 | 476 | <### BEGIN SCRIPT ###> 477 | $StartScript = Get-Date 478 | 479 | <# Clear out error variable #> 480 | $Error.Clear() 481 | 482 | <# Use UploadName (or not) #> 483 | $UploadName = $UploadName -Replace '\s','_' 484 | $UploadName = $UploadName -Replace '[^a-zA-Z0-9_]','' 485 | If ($UploadName) { 486 | $BackupName = "$((Get-Date).ToString('yyyy-MM-dd'))_$UploadName" 487 | } Else { 488 | $BackupName = "$((Get-Date).ToString('yyyy-MM-dd'))_Backup" 489 | } 490 | 491 | <# Delete old debug files and create new #> 492 | $EmailBody = "$PSScriptRoot\EmailBody.log" 493 | If (Test-Path $EmailBody) {Remove-Item -Force -Path $EmailBody} 494 | New-Item $EmailBody 495 | $DebugLog = $BackupLocation + "\" + $BackupName + "_Debug.log" 496 | If (Test-Path $DebugLog) {Remove-Item -Force -Path $DebugLog} 497 | New-Item $DebugLog 498 | Write-Output "::: $UploadName Backup Routine $(Get-Date -f D) :::" | Out-File $DebugLog -Encoding ASCII -Append 499 | Write-Output " " | Out-File $DebugLog -Encoding ASCII -Append 500 | If ($UseHTML) { 501 | Write-Output " 502 | 503 | 504 | 505 | " | Out-File $EmailBody -Encoding ASCII -Append 506 | } 507 | 508 | <# Validate backup folder #> 509 | $UF = $UploadFolder -Replace('\\$','') 510 | If (Test-Path $UF) { 511 | Debug "The folder to be backed up is $UF" 512 | } Else { 513 | Debug "Error : The folder to be backed up could not be found. Quitting Script" 514 | Debug "$UploadFolder does not exist" 515 | Email "Error : The folder to be backed up could not be found. Quitting Script" 516 | Email "$UploadFolder does not exist" 517 | EmailResults 518 | Exit 519 | } 520 | 521 | <# Set Email Header #> 522 | If ($UseHTML) { 523 | Email "
:::   $UploadName Backup Routine   :::
" 524 | Email "
$(Get-Date -f D)
" 525 | Email " " 526 | } Else { 527 | Email "::: $UploadName Backup Routine :::" 528 | Email " $(Get-Date -f D)" 529 | Email " " 530 | } 531 | 532 | <# Compress backup into 7z archives #> 533 | MakeArchive 534 | 535 | <# Upload archive to LetsUpload.io #> 536 | OffsiteUpload 537 | 538 | <# Delete empty folders at LetsUpload.io #> 539 | DeleteEmptyRemoteFolders 540 | 541 | <# Check for updates #> 542 | CheckForUpdates 543 | 544 | <# Finish up and send email #> 545 | Debug "----------------------------" 546 | Debug "$UploadName Backup & Upload routine completed in $(ElapsedTime $StartScript)" 547 | Email " " 548 | Email "$UploadName Backup & Upload routine completed in $(ElapsedTime $StartScript)" 549 | If ($UseHTML) {Write-Output "
" | Out-File $EmailBody -Encoding ASCII -Append} 550 | EmailResults --------------------------------------------------------------------------------