367 lines
14 KiB
PowerShell
367 lines
14 KiB
PowerShell
|
|
$default_reconcileFile = "##protect_transfer_reconcile_files##.csv"
|
|
$default_profile = "default"
|
|
$default_archiveFile = ".\ptr_file_##date##.7z"
|
|
|
|
|
|
|
|
function Get-B2ApiToken {
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $AccountId,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $AccountKey
|
|
)
|
|
|
|
|
|
Begin
|
|
{
|
|
if(-not $AccountID -or -not $AccountKey)
|
|
{
|
|
[PSCredential]$b2Creds = Get-Credential -Message 'Enter your B2 account ID and application key below.'
|
|
try
|
|
{
|
|
[String]$AccountId = $b2Creds.GetNetworkCredential().UserName
|
|
[String]$AccountKey = $b2Creds.GetNetworkCredential().Password
|
|
}
|
|
catch
|
|
{
|
|
throw 'You must specify the account ID and application key.'
|
|
}
|
|
}
|
|
|
|
[String]$plainCreds = "${AccountId}:${AccountKey}"
|
|
[String]$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($plainCreds))
|
|
[Hashtable]$sessionHeaders = @{'Authorization'="Basic $encodedCreds"}
|
|
[Uri]$b2ApiUri = 'https://api.backblaze.com/b2api/v1/b2_authorize_account'
|
|
}
|
|
Process
|
|
{
|
|
try
|
|
{
|
|
$b2Info = Invoke-RestMethod -Method Get -Uri $b2ApiUri -Headers $sessionHeaders
|
|
[String]$script:SavedB2AccountID = $b2Info.accountId
|
|
[Uri]$script:SavedB2ApiUri = $b2Info.apiUrl
|
|
[String]$script:SavedB2ApiToken = $b2Info.authorizationToken
|
|
[Uri]$script:SavedB2DownloadUri = $b2Info.downloadUrl
|
|
|
|
$b2ReturnInfo = [PSCustomObject]@{
|
|
'AccountID' = $b2Info.accountId
|
|
'ApiUri' = $b2Info.apiUrl
|
|
'DownloadUri' = $b2Info.downloadUrl
|
|
'Token' = $b2Info.authorizationToken
|
|
}
|
|
|
|
return $b2ReturnInfo
|
|
}
|
|
catch
|
|
{
|
|
$errorDetail = $_.Exception.Message
|
|
Write-Error -Exception "Unable to authenticate with given API Key.`n`r$errorDetail" `
|
|
-Message "Unable to authenticate with given API Key.`n`r$errorDetail" -Category AuthenticationError
|
|
#throw "Unable to authenticate with given APIKey.`n`r$errorDetail"
|
|
throw $_
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function Get-B2Bucket {
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $ApiToken,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $AccountId,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $ApiUri,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $BucketHost
|
|
)
|
|
|
|
Begin
|
|
{
|
|
[Hashtable]$sessionHeaders = @{'Authorization'=$ApiToken}
|
|
[String]$sessionBody = @{'accountId'=$AccountID} | ConvertTo-Json
|
|
[Uri]$b2ApiUri = "$ApiUri/b2api/v1/b2_list_buckets"
|
|
}
|
|
Process
|
|
{
|
|
$b2Info = Invoke-RestMethod -Method Post -Uri $b2ApiUri -Headers $sessionHeaders -Body $sessionBody
|
|
foreach($info in $b2Info.buckets)
|
|
{
|
|
if ($bucketHost -eq $info.bucketName) {
|
|
$b2ReturnInfo = [PSCustomObject]@{
|
|
'BucketName' = $info.bucketName
|
|
'BucketID' = $info.bucketId
|
|
'BucketType' = $info.bucketType
|
|
'AccountID' = $info.accountId
|
|
}
|
|
return $b2ReturnInfo
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
}
|
|
|
|
function Get-B2UploadUri {
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $BucketHost,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $FileName,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Uri] $ApiUri,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $ApiToken
|
|
)
|
|
|
|
Begin
|
|
{
|
|
[Hashtable]$sessionHeaders = @{'Authorization'=$ApiToken}
|
|
[Uri]$b2ApiUri = "$ApiUri/b2api/v1/b2_get_upload_url"
|
|
}
|
|
Process
|
|
{
|
|
try
|
|
{
|
|
[String]$sessionBody = @{'bucketId'=$bucketHost} | ConvertTo-Json
|
|
$b2Info = Invoke-RestMethod -Method Post -Uri $b2ApiUri -Headers $sessionHeaders -Body $sessionBody
|
|
$b2ReturnInfo = [PSCustomObject]@{
|
|
'BucketId' = $b2Info.BucketId
|
|
'UploadUri' = $b2Info.uploadUrl
|
|
'Token' = $b2Info.authorizationToken
|
|
}
|
|
|
|
return $b2ReturnInfo
|
|
}
|
|
catch
|
|
{
|
|
$errorDetail = $_.Exception.Message
|
|
Write-Error -Exception "Unable to retrieve the upload uri.`n`r$errorDetail" `
|
|
-Message "Unable to retrieve the upload uri.`n`r$errorDetail" -Category ReadError
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function Invoke-B2SUpload {
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $BucketHost,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $TargetPath,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $FileName,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Uri] $ApiUri,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $ApiToken
|
|
)
|
|
|
|
try
|
|
{
|
|
|
|
[String] $b2FileName = [System.Uri]::EscapeDataString($TargetPath)
|
|
[String] $b2FileMime = [System.Web.MimeMapping]::GetMimeMapping($fileName)
|
|
[String]$b2FileSHA1 = (Get-FileHash -Path $fileName -Algorithm SHA1).Hash
|
|
[String]$b2FileAuthor = (Get-Acl -Path $fileName).Owner
|
|
|
|
$b2FileAuthor = $b2FileAuthor.Substring($b2FileAuthor.IndexOf('\')+1)
|
|
|
|
[Hashtable]$sessionHeaders = @{
|
|
'Authorization' = $ApiToken
|
|
'X-Bz-File-Name' = $b2FileName
|
|
'Content-Type' = $b2FileMime
|
|
'X-Bz-Content-Sha1' = $b2FileSHA1
|
|
'X-Bz-Info-Author' = $b2FileAuthor
|
|
}
|
|
|
|
$b2Info = Invoke-RestMethod -Method Post -Uri $ApiUri -Headers $sessionHeaders -InFile $fileName
|
|
|
|
$b2ReturnInfo = [PSCustomObject]@{
|
|
'Name' = $b2Info.fileName
|
|
'FileInfo' = $b2Info.fileInfo
|
|
'Type' = $b2Info.contentType
|
|
'Length' = $b2Info.contentLength
|
|
'BucketID' = $b2Info.bucketId
|
|
'AccountID' = $b2Info.accountId
|
|
'SHA1' = $b2Info.contentSha1
|
|
'ID' = $b2Info.fileId
|
|
}
|
|
|
|
return $b2ReturnInfo
|
|
}
|
|
catch
|
|
{
|
|
$errorDetail = $_.Exception.Message
|
|
Write-Error -Exception "Unable to upload the file.`n`r$errorDetail" `
|
|
-Message "Unable to upload the file.`n`r$errorDetail" -Category InvalidOperation
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function Invoke-B2SDownload {
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $BucketHost,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $SourcePath,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $FileName,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Uri] $ApiDownloadUri,
|
|
[Parameter(Mandatory)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [String] $ApiToken
|
|
)
|
|
|
|
Begin
|
|
{
|
|
if(-not (Test-Path -Path $FileName -IsValid))
|
|
{
|
|
throw 'The file path given ($FileName) is not valid.`n`rThe file cannot be saved.'
|
|
}
|
|
[Hashtable]$sessionHeaders = @{'Authorization'=$ApiToken}
|
|
}
|
|
Process
|
|
{
|
|
[Uri]$b2ApiUri = "${ApiDownloadUri}file/$BucketHost/$SourcePath"
|
|
try
|
|
{
|
|
Invoke-RestMethod -Method Get -Uri $b2ApiUri -Headers $sessionHeaders -OutFile $FileName
|
|
}
|
|
catch
|
|
{
|
|
$errorDetail = $_.Exception.Message
|
|
Write-Error -Exception "Unable to upload the file.`n`r$errorDetail" `
|
|
-Message "Unable to upload the file.`n`r$errorDetail" -Category InvalidOperation
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Compress / Package
|
|
|
|
<#
|
|
.Synopsis
|
|
Packs a source folder(s) into an encrypted 7ZIP archive file
|
|
that can be securely transported to a remote lcoation or
|
|
even used as a secure permmanent backup.
|
|
|
|
PeterDocs : Protect, Transfer, Reconcile Document Files
|
|
|
|
.Description
|
|
Packages source folder contents into a 7ZIP file, adding a reconciliation
|
|
file to the 7ZIP file and then encrypting the contents. The source folder
|
|
is not altered and only read rights are required. A log file is written
|
|
at exceution to record activity.
|
|
|
|
|
|
.Parameter SourceFolder
|
|
The path to the files and folders to pack.
|
|
The path name can include a trailing * as a wildcard to only include a subset of
|
|
directories.
|
|
|
|
When using the trailing * for names, the filtering is only applied to immediate
|
|
folder names under the parent folder. The filter does not cascade to lower folders.
|
|
|
|
The path can be a local drive, mapped network drive or a network shared folder
|
|
location such as \\MediaStor\MyLibrary.
|
|
|
|
The source folder parameter can also be a file containing a list of paths, one per line.
|
|
To use a list file, prefix the source folder value with a "@" and name the file.
|
|
Do not use a folder for @ defined path.
|
|
|
|
A file (@ prefix) containing a list of paths cannot contain generic path names, that
|
|
is paths with trailing wildcard of "*"
|
|
|
|
.Parameter RecipientKey
|
|
The recipient of the package which is used to find the appropriate
|
|
certificate for encrypting with the public key. Either the RecipientKeyName
|
|
or the SecretKey is required for packing or unpacking the 7ZIP file.
|
|
Using the RecipientKeyName is the most secure transfer option as a
|
|
asymmetric cryptographic key is used that can only be decrypted by the
|
|
holder of the private key.
|
|
|
|
If you are using the RecipientKeyName, then the 7ZIP file contents can only
|
|
be unzipped by the holder of the private key and the SecretFileName file.
|
|
If you don't have the private, which you should not unless you are sending
|
|
to yourself, then you cannot unpack the 7ZIP file.
|
|
|
|
.Parameter SecretKey
|
|
A tradiitional secret to encrypt or decrypt the 7ZIP package. Either the RecipientKeyName
|
|
or the SecretKey is required for packing or unpacking the 7ZIP file. This method
|
|
uses a symmetric cryptographic key exchange which is less secure then the
|
|
RecipientKeyName approach.
|
|
|
|
Note: Currently the script doe snot user Secure Strings
|
|
|
|
.Parameter ArchiveFile
|
|
The location and name of the 7ZIP file. If not supplied a default 7ZIP file name
|
|
will be generated in the current directory for the pack action.
|
|
|
|
The default name will take the form ".\transfer_protect_yyyyMMdd_hhmm.7z"
|
|
|
|
For unpack actions, the archive file name parameter is mandatory.
|
|
|
|
.Parameter RootFolder
|
|
The root folder, which should be used if using wildcard (*) for the
|
|
path. A guess will be made as to value if not supplied, which will
|
|
work in many circumstances.
|
|
|
|
.Parameter FileFilter
|
|
A filter on file names. This does not filter directories.
|
|
An example to only include JPEG file is "*.jpg". You can also
|
|
filter on picture file names starting with "IMG*.jpg"
|
|
|
|
.Parameter ReconcileFile
|
|
The name of the reconfile file name to generate during pack or use
|
|
during unpack. This is a file name without path. If no value is
|
|
supplied, then a default name is generated.
|
|
The reconcile file is included into the root of the 7ZIP file.
|
|
Once a reconcile is executed, you can delete this file from the
|
|
restored location.
|
|
|
|
The default name is "##protect_transfer_reconcile_files##.csv"
|
|
|
|
.Parameter SecretFile
|
|
The secret file name is used with RecipientKey to secure the
|
|
internally generated password for the 7ZIP file. When unpacking the
|
|
7ZIP file you will need access to this file if RecipientKey
|
|
was used. If not supplied a default name is used. This file is
|
|
encrypted with RecipientKey.
|
|
|
|
The default name is the archive file name with postfix ".key"
|
|
|
|
.Parameter ExcludeHash
|
|
Exclude the file hash from the reconcile file. As producing a file
|
|
hash takes compute cycles during pack, you can select to bypass this
|
|
generation to speed up the packaging. Excluding the hash does reduce
|
|
the functionality of the reconciliation at unpack.
|
|
|
|
.Parameter LogPath
|
|
The log folder where log files are written. If the folder does not
|
|
exist then it is created. You need write access rights to this location.
|
|
|
|
.Notes
|
|
This script has been written to use the 7ZIP function as it is open source
|
|
and provides a secure encryption mechanism, plus portability on Windows,
|
|
Linux and MacOS.
|
|
|
|
It is also beneficial that 7ZIP has efficient compression algorithms.
|
|
|
|
Compressing and packing a large data set can take significant time and also
|
|
require storage space. The script does not check if you have sufficient
|
|
free storage to package the source contents into a single 7ZIP file. It is your
|
|
responsibility to ensure sufficient storage space exists.
|
|
|
|
If you need to copy files from one directory to another accessible directory from
|
|
your Windows desktop, you might consider using ROBOCOPY. If the target directory
|
|
is not accessible and you want to reconcile, then this tool is appropriate.
|
|
|
|
The following environment variables are supported:
|
|
- PETERDOCS_RECIPIENTKEY
|
|
|
|
|
|
.Example
|
|
# Pack and encrypt all files in folder ".\transferpack\" using a private-public key
|
|
# A file with the postifx ".key" is also generated alongside the 7ZIP file
|
|
Invoke-Pack -SourceFolder ".\transferpack\" -RecipientKeyName data@mycompany
|
|
|
|
#>
|
|
|