├── 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
--------------------------------------------------------------------------------