Compare commits

...

10 Commits
0.3 ... main

Author SHA1 Message Date
meerkat d8a8ff25a9 Adding certificate backup 2021-10-07 23:53:13 +11:00
Tom Peltonen 55beb4ecab Detect better the hash error when open 2021-09-05 20:55:59 +10:00
Tom Peltonen 46a09520b6 Adding uninstall instructions 2021-09-01 21:02:03 +10:00
Tom Peltonen 3906144a1c Corrected recipient key handling 2021-08-18 23:36:07 +10:00
Tom Peltonen cb2dc3cbf8 Adding sample folder 2021-08-16 11:21:11 +10:00
Tom Peltonen f05d8b72be Doument spelling corrections 2021-08-15 16:55:48 +10:00
Tom Peltonen e068d4da39 Minor corrections and additions to documentation 2021-08-14 12:41:46 +10:00
Tom Peltonen ae5def5aa7 Enabled processing and compare of hidden files 2021-08-14 12:32:31 +10:00
Tom Peltonen 832dde3e2a Updated documentation including offline
install instructions
2021-08-13 23:42:38 +10:00
Tom Peltonen c24b5d369b Added Hidden files as default to reconcile
Fixed recipient key handling
Added more 7Zip options
Updated documentation
2021-08-13 19:47:51 +10:00
18 changed files with 688 additions and 155 deletions

View File

@ -4,26 +4,26 @@ There are various options for using PeterDocs. The following sections will cove
## File Filter ## File Filter
The ``-FileFilter`` parameter allows selection of files tha are to be included into the archive file. The ``-FileFilter`` parameter allows selection of files that are to be included into the archive file.
The parameter only applies to the compress function or buidling the reconciliation file. The parameter only applies to the compress function or building the reconciliation file.
For example: For example:
```powershell ```powershell
Compress-Peter -SourceFolder "~/Pictures/" -Secret "c0mpleX%S3cret" -FileFiletr "*.jpg" Compress-Peter -SourceFolder "~/Pictures/" -Secret "c0mpleX%S3cret" -FileFilter "*.jpg"
``` ```
will only include files with the extension ".jpg" will only include files with the extension ".jpg"
```powershell ```powershell
Compress-Peter -SourceFolder "~/Pictures/" -Secret "c0mpleX%S3cret" -FileFiletr "IMG9*.jpg" Compress-Peter -SourceFolder "~/Pictures/" -Secret "c0mpleX%S3cret" -FileFilter "IMG9*.jpg"
``` ```
will only include files with the extension ".jpg" and starting with the characters "IMG90" will only include files with the extension ".jpg" and starting with the characters "IMG90"
## ReconcileFile ## ReconcileFile
The ``-ReconcileFile`` parameter allows specification of the reocnciliation file if you The ``-ReconcileFile`` parameter allows specification of the reconciliation file if you
wish to select your own name. wish to select your own name.
For example: For example:
@ -61,4 +61,21 @@ Expand-Peter -RestoreFolder "c:\backup\pictures" -RecipientKey "meerkat@merebox
## LogPath ## LogPath
The ``-LogPath`` parameter allows definition of the folder that will contain the The ``-LogPath`` parameter allows definition of the folder that will contain the
execution log. execution log. The name of the log file is automatically generated for you and
includes the date.
## Compression Level
By setting the Compression level to a value recognized by the 7Zip4Powershell module you can gain more control
of the compression. The main use case here is for documents that are already compressed and would
not benefit from future compression. To use this feature you need to set the environment variable.
If all documents are JPEG pictures then setting this value can speed up the compress process
and potentially save a few kilobytes of 7Zip archive size.
An example for archiving a source folder with already compressed files is:
```powershell
$env:PETERDOCS_7ZIPLEVEL="None"
Compress-Peter -SourceFolder "./zip_files" -RecipientKey "meerkat@merebox.com" -ArchiveFile .\myarchive.7z -ExcludeHash
```

View File

@ -0,0 +1,63 @@
# Alternative Use Cases
While ``PeterDocs`` has been built with the objective to transfer documents from
one computer to another where the computers are on isolated networks, there are
alternatives uses.
## Documents on the same network
You can use ``PeterDocs`` to reconcile files transferred using the Windows
``Robocopy`` command. Robocopy is installed by default on your Windows
system.
Robocopy does require your source and target folders to be accessible from
the computer that is executing the command.
To use ``PeterDocs`` and ``Robocopy`` install PeterDocs from the PowerShell Gallery
and execute the below commands in a PowerShell terminal, changing the values to suit.
```powershell
New-PeterReconcile -ReconcileFile .\myrobocopy.csv -SourceFolder <Source> -ExcludeHash
robocopy <Source> <Destination> /mt /e /z /j /copy:DAT /dcopy:DAT /r:100 /eta /log+:robocopy_run.log /tee
Compare-Peter -ReconcileFile .\myrobocopy.csv -RestoreFolder <Destination> -ExcludeHash
```
The source and destination folders can be network paths i.e. start with \\\\
The above robocopy command retries 100 times failed copies. The default is a million with a 30 second
wait time between retries. Probably not a realistic time before failing.
If you want to verify the HASH for each file copied, then remove the ``-ExcludeHash`` directive. Be
warned that generating a hash on both source and destination will take some time if you
have many files.
Further information on Robocopy can be found on the internet such as:
* [https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy)
* [https://pureinfotech.com/robocopy-transfer-files-fast-network-windows-10/](https://pureinfotech.com/robocopy-transfer-files-fast-network-windows-10/)
* [https://www.techrepublic.com/article/how-to-quickly-back-up-just-your-data-in-windows-10-with-robocopys-multi-threaded-feature/](https://www.techrepublic.com/article/how-to-quickly-back-up-just-your-data-in-windows-10-with-robocopys-multi-threaded-feature/)
* [https://www.youtube.com/watch?v=gTzTeHmKMKw](https://www.youtube.com/watch?v=gTzTeHmKMKw)
You need to consider the **security** of the network path when using robocopy. The copy will use the underlying
network transport layer and protocol. If you are not using SMBv3 protocol then the file contents may not
be secure in transit.
## Picture EXIF data
You can use ``PeterDocs`` to extract EXIF data from your picture files. To do
this just install PeterDocs from the PowerShell Gallery and execute the
below command in a PowerShell terminal, changing the values to suit.
```powershell
New-PeterReconcile -ReconcileFile .\mypictures_metadata.csv -SourceFolder <Source> -ExcludeHash -IncludeExif
```
At the conclusion of the execution, you will have a file named ``##peter_exif##.csv`` that
contains your pictures metadata. You will also have a CSV file with picture file
general metadata named ``mypictures_metadata.csv`` such as creation time and size.
Further information on EXIF can be found on the internet such as:
* [https://en.wikipedia.org/wiki/Exif](https://en.wikipedia.org/wiki/Exif)
* [https://photographylife.com/what-is-exif-data](https://photographylife.com/what-is-exif-data)
* [https://exiftool.org/](https://exiftool.org/)

View File

@ -7,7 +7,7 @@ or cloned to a remote location securely. The documents can be binary or text do
including personal photographs or sensitive Microsoft Word documents. including personal photographs or sensitive Microsoft Word documents.
At the remote location a reconciliation can be performed to verify that the documents At the remote location a reconciliation can be performed to verify that the documents
have been recieved and no alteration occurred. have been received and no alteration occurred.
## Why ## Why
@ -49,7 +49,7 @@ You can ignore the remaining parameters if you are happy with the defaults.
## What ## What
The ```Compress-Peter``` compressess the contet of the ```SourceFolder``` and saves the result The ```Compress-Peter``` compresses the content of the ```SourceFolder``` and saves the result
as the encrypted ```ArchiveFile```. The archive file also contains the reconciliation file as the encrypted ```ArchiveFile```. The archive file also contains the reconciliation file
so that the recipient of the archive is able to reconcile the restore at the remote location. so that the recipient of the archive is able to reconcile the restore at the remote location.
@ -67,7 +67,7 @@ generate delta archive files.
## Send Usage ## Send Usage
Once the archive file is created you will commonly send or transfer it to anohter Once the archive file is created you will commonly send or transfer it to another
location where it wll be unpacked. location where it wll be unpacked.
Please read next the documentation on [sending the archive](SendArchive.md) Please read next the documentation on [sending the archive](SendArchive.md)

View File

@ -3,7 +3,7 @@
## Objective ## Objective
The objective is to provide a simple capability to create a secure The objective is to provide a simple capability to create a secure
archive file containiung documents to be restored and reconciled at the destination. archive file containing documents to be restored and reconciled at the destination.
The design artefacts must provide all the tools required to pack, The design artefacts must provide all the tools required to pack,
secure, unpack and reconcile the restored/cloned documents. secure, unpack and reconcile the restored/cloned documents.

View File

@ -22,7 +22,7 @@ encryption. The secret (or password) needs to be complex and at least
The complex secret needs to contain lower case letter, upper case letter, The complex secret needs to contain lower case letter, upper case letter,
numeric digit and special symbol. numeric digit and special symbol.
The secret is used directly on the 7ZIP compresison and you can use the The secret is used directly on the 7ZIP compression and you can use the
secret to decrypt the archive file and check its contents. secret to decrypt the archive file and check its contents.
Make a note of the secret as you will need it to decrypt the contents. Make a note of the secret as you will need it to decrypt the contents.
@ -34,10 +34,10 @@ file itself.
## RecipientKey ## RecipientKey
The PeterDocs parameter labelled ```RecipientKey``` is used for asymmetric The PeterDocs parameter labelled ```RecipientKey``` is used for asymmetric
keys provided by the Microsft Certificate Manager for encrypting content. keys provided by the Microsoft Certificate Manager for encrypting content.
This is the most secure method for transfer as it is secured with the This is the most secure method for transfer as it is secured with the
certficates. Using certificates requires: certificates. Using certificates requires:
1. your recipient to send you their public certificate 1. your recipient to send you their public certificate
2. you to load the public certificate into your Windows Certificate Manager 2. you to load the public certificate into your Windows Certificate Manager
@ -49,7 +49,7 @@ contents.
The recipient will need to receive the archive file plus the ```.key``` The recipient will need to receive the archive file plus the ```.key```
file generated by PeterDocs. Both files must be accessible to the file generated by PeterDocs. Both files must be accessible to the
recipient for decryptingg the contents. recipient for decrypting the contents.
### Internal process ### Internal process
@ -58,7 +58,7 @@ is saved into the ```.key``` file which is encrypted using the recipients
public key. public key.
The reason for doing this process is because there are technical limitations The reason for doing this process is because there are technical limitations
in encrypting large files using the certficate public keys. The maximum in encrypting large files using the certificate public keys. The maximum
size is around 60MB when using public keys. size is around 60MB when using public keys.
### Generating your Certificate ### Generating your Certificate
@ -70,13 +70,31 @@ Manager or executing the below PowerShell command.
New-SelfSignedCertificate -Subject "CN=PeterDocs" -FriendlyName "PeterDocs" -KeyDescription "Encryption key for PeterDocs data encipherment" -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsage KeyEncipherment,DataEncipherment, KeyAgreement -Type DocumentEncryptionCert New-SelfSignedCertificate -Subject "CN=PeterDocs" -FriendlyName "PeterDocs" -KeyDescription "Encryption key for PeterDocs data encipherment" -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsage KeyEncipherment,DataEncipherment, KeyAgreement -Type DocumentEncryptionCert
``` ```
To list your current certficates use the below PowerShell command. To list your current certificates use the below PowerShell command.
```powershell ```powershell
Get-Childitem -Path "Cert:\CurrentUser\My" -DocumentEncryptionCert Get-Childitem -Path "Cert:\CurrentUser\My" -DocumentEncryptionCert
``` ```
**Note**: The certficates from above is assigned to your current logged in user and not the local machine
### Exporting your Certificate ### Exporting your Certificate
You will need to export your public key and send it to the person who will generate the archive You will need to export your public key and send it to the person who will generate the archive
file for you. That person with your public key will need to import your public key. file for you. That person with your public key will need to import your public key.
On Windows, execute the command ``certmgr`` and export the certificate under "Personal\Certificates" for example.
### Certificate Backup
You should create a backup of your certificate (public and private) in case your local
device suffers a failure. Please secure the backup copy.
You can use the below as a sample code for exporting the default ``PeterDocs``
certificate is give below
```powershell
Get-ChildItem -Path "Cert:\CurrentUser\My" | where{$_.Subject -eq "CN=PeterDocs"} | Export-Certificate -Type CERT -FilePath C:\Temp\PeterDocs_cert.cer -Force
```
Change the values and file name to suit your situation. This file can be imported using the ``certmgr``

View File

@ -36,7 +36,7 @@ To expand the archive you will need write access to the ```RestoreFolder``` loca
## What ## What
The ```Expand-Peter``` decrypts the archive file and expands the contents into The ```Expand-Peter``` decrypts the archive file and expands the contents into
the specified restore folder. It does not peform a reconciliation which is the the specified restore folder. It does not perform a reconciliation which is the
next step. next step.
## Reconcile Usage ## Reconcile Usage

View File

@ -1,6 +1,6 @@
# Install # Install
PeterDocs is a module that can be donwloaded or installed from PeterDocs is a module that can be downloaded or installed from
[PowerShell Gallery](https://xx.com/) [PowerShell Gallery](https://xx.com/)
## Pre-requisites ## Pre-requisites
@ -28,3 +28,69 @@ need administrator rights.
## Compress Usage ## Compress Usage
Please read next the documentation on [creating an archive file](Compress.md) Please read next the documentation on [creating an archive file](Compress.md)
## Offline install
If the computer you wish to use PeterDocs module on does not have Internet access,
a common situation for secured servers, then you will need to install the
PeterDocs module manually by following the instructions below.
Please familiarize yourself with all the instructions before commencing so that you are aware of
all steps to be followed.
On a **computer with Internet (PowerShell Gallery) access** do the following steps
1. First check you have PowerShell version 5 or later
```powershell
$PSVersionTable.PSVersion
```
2. Download the PeterDocs module
```powershell
Save-Module -Name PeterDocs -Path C:\Temp
```
A few folders will be created in C:\Temp. The names of the folders are
7Zip4PowerShell, AWS.Tools.Common, AWS.Tools.S3, and PeterDocs
3. Compress the new 7Zip4PowerShell, AWS.Tools.Common, AWS.Tools.S3, PeterDocs folders into a single ZIP file
4. Copy the ZIP file to the offline computer
On the **computer lacking Internet (PowerShell Gallery) access** do the following steps
1. First check you have PowerShell version 5 or later
```powershell
$PSVersionTable.PSVersion
```
2. Run the following command to determine where the ZIP needs to be unpacked
```powershell
$env:PSModulePath -Split ";"
```
3. Take a note of the name of the Windows PowerShell Modules folder that is linked to your account.
We will install the module linking to you account as you may not be authorised to the global location.
4. Unpack the ZIP contents into the folder name noted above. This should restore the
7Zip4PowerShell, AWS.Tools.Common, AWS.Tools.S3, PeterDocs folders
as a child of the Windows PowerShell Module folder
5. To check the module is installed, run the following command
```powershell
Import-Module PeterDocs
```
The summary details on PeterDocs should be displayed.
## Uninstall
To uninstall ``PeterDocs``, execute the following PowerShell command
```powershell
UnInstall-Module -Name PeterDocs
```

20
Docs/Publish.md 100644
View File

@ -0,0 +1,20 @@
# Publishing PeterDocs
To publish the PeterDocs module to the PowerShell Gallery, follow these instructions.
**Note** Only the author or delegate for PeterDocs will be authorized to perform this action.
1. Ensure you have incremented the version number in ``PeterDocs.psm1`` and ``PeterDocs.psd1``
2. Open PowerShell terminal
3. Retrieve the PowerShell Gallery API key and set it
4. Do a Whatif check on the module before publishing
5. Publish the module
```powershell
$apiKey = ""
Publish-Module -Path .\PeterDocs\ -NuGetApiKey $apiKey -WhatIf -Verbose
Publish-Module -Name .\PeterDocs\PeterDocs.psd1 -NuGetApiKey $apiKey
```

View File

@ -3,13 +3,13 @@
## Why ## Why
Once the archive file is sent, you need to download it from its intermediate Once the archive file is sent, you need to download it from its intermediate
location if the source and destination locations are not directky connected. location if the source and destination locations are not directly connected.
You can user other tools you have available to download the archive file. You can user other tools you have available to download the archive file.
## When ## When
The archive is received after it sent. The assunmption is that cloud storage The archive is received after it sent. The assumption is that cloud storage
is being used as an intermediary. is being used as an intermediary.
## How ## How
@ -36,8 +36,8 @@ The ```SourcePath``` is specified as follows:
The "s3" prefix is to download from AWS S3. The "b2" prefix The "s3" prefix is to download from AWS S3. The "b2" prefix
is to download from Backblaze. is to download from Backblaze.
If you are dowloading from AWS you can specify the AWS profile name If you are downloading from AWS you can specify the AWS profile name
in parameter ```SourceProfile```. In this situtation the profile in parameter ```SourceProfile```. In this situation the profile
needs to exist in the AWS credentials on your local device and user profile. needs to exist in the AWS credentials on your local device and user profile.
If you are downloading from Backblaze you specify the ```AccountId``` and the If you are downloading from Backblaze you specify the ```AccountId``` and the
@ -57,7 +57,7 @@ use other tools to download the archive file and the key file.
The function will not expand or reconcile the restore at the destination. The function will not expand or reconcile the restore at the destination.
Please ensure you have sufficent storage to accomodate the local copy of the Please ensure you have sufficient storage to accommodate the local copy of the
archive and space to unpack it. archive and space to unpack it.
## Expand Usage ## Expand Usage

View File

@ -4,7 +4,7 @@ A reconcile file is generated as part of the Compress process and packed with th
## Why ## Why
When transferring or cloning documenmts to another location, you will want to When transferring or cloning documents to another location, you will want to
verify that the same documents have been restored unaltered at the destination. verify that the same documents have been restored unaltered at the destination.
## When ## When
@ -44,7 +44,7 @@ The document last update and time is not checked because the value
will reflect the date and time of restore. will reflect the date and time of restore.
The reconciliation summary is displayed in the terminal and the log The reconciliation summary is displayed in the terminal and the log
wil lhave more information. will have more information.
If any errors are listed, please investigate the discrepancy. If any errors are listed, please investigate the discrepancy.

View File

@ -43,7 +43,7 @@ The "s3" prefix is to upload to AWS S3. The "b2" prefix
is to upload to Backblaze. is to upload to Backblaze.
If you are uploading to AWS you can specify the AWS profile name If you are uploading to AWS you can specify the AWS profile name
in parameter ```TargetProfile```. In this situtation the profile in parameter ```TargetProfile```. In this situation the profile
needs to exist in the AWS credentials on your local device and user profile. needs to exist in the AWS credentials on your local device and user profile.
If you are uploading to Backblaze you specify the ```AccountId``` and the If you are uploading to Backblaze you specify the ```AccountId``` and the

78
Docs/Usage.md 100644
View File

@ -0,0 +1,78 @@
# Usage
## Usage PeterDocs
The syntax is for each function is below:
```powershell
Compress-Peter
-SourceFolder <string>
[-RecipientKey <string>] | [-SecretKey <string>]
[-ArchiveFile <string>]
[-ReconcileFile <string>]
[-FileFilter <string>]
[-SecretFile <string>]
[-ExcludeHash]
[-IncludeExif]
[-RootFolder <string>]
[-VolumeSize <string>]
[-LogPath <string>]
[<CommonParameters>]
Expand-Peter
-ArchiveFile <string>
-RestoreFolder <string>
[-RecipientKey <string>] | [-SecretKey <string>]
[-SecretFile <string>]
[-LogPath <string>]
[<CommonParameters>]
New-PeterReconcile
-SourceFolder <string>
-ReconcileFile <string>
[-RootFolder <string>]
[-FileFilter <string>]
[-ProcessFileCount <long>]
[-ExcludeHash]
[-IncludeExif]
[-Feedback]
[-LogPath <string>]
[<CommonParameters>]
Compare-Peter
-RestoreFolder <string>
[-ReconcileFile <string>]
[-RootFolder <string>}
[-ExcludeHash]
[-ExtendedCheck]
[-LogPath <string>]
[<CommonParameters>]
```
## Usage PeterTask
For using the PeterTask, syntax is as simple as shown below. Behind the scenes the
PeterTask calls the functions above:
```powershell
PeterTask
-Task {Compress, Expand, Compare, NewReconcile, Put, Get, ArchiveInformation}
-Path <string>
[-RecipientKey <string>] | [-SecretKey <string>]
[-ArchiveFile <string>]
[-RootFolder <string>]
[-FileFilter <string>]
[-ReconcileFile <string>]
[-SecretFile <string>]
[-CloudProfile <string>]
[-ExcludeHash]
[-IncludeExif]
[-LogPath <string>]
[<CommonParameters>]
```
**NOTE** Please do not use the VolumeSize parameter as it will fail due to a current
issue with the underlying 7zip4 Powershell module.

Binary file not shown.

View File

@ -29,11 +29,12 @@
#> #>
$global:default_reconcileFile = "##peter_files##.csv" $global:default_reconcileFile = "##peter_files##.csv"
$global:default_exifFile = "##peter_exif##.csv"
$global:default_metaFile = "##peter##.json" $global:default_metaFile = "##peter##.json"
$global:default_errorListFile = Join-Path -Path ".\" -ChildPath "##peter_error_list##.txt"
$global:LogPathName = "" $global:LogPathName = ""
$global:MetadataPathName = Join-Path -Path ".\" -ChildPath ".peter-metadata" $global:MetadataPathName = Join-Path -Path ".\" -ChildPath ".peter-metadata"
$global:Version = "0.3" $global:Version = "0.32"
function Open-Log { function Open-Log {
@ -44,10 +45,7 @@ function Open-Log {
} }
function Write-Log { function Get-LogName {
param(
[String] $LogEntry
)
$date = Get-Date -f "yyyy-MM-dd" $date = Get-Date -f "yyyy-MM-dd"
@ -55,13 +53,23 @@ function Write-Log {
{ {
$global:LogPathName = Join-Path -Path ".\" -ChildPath "Logs" $global:LogPathName = Join-Path -Path ".\" -ChildPath "Logs"
} }
$logName = $(Get-SoftwareName) + "_$date.log"
$sFullPath = Join-Path -Path $global:LogPathName -ChildPath $logName
if (!(Test-Path -Path $global:LogPathName)) { if (!(Test-Path -Path $global:LogPathName)) {
$null = New-Item -Path $global:LogPathName -ItemType Directory $null = New-Item -Path $global:LogPathName -ItemType Directory
} }
$logName = $(Get-SoftwareName) + "_$date.log"
return Join-Path -Path $global:LogPathName -ChildPath $logName
}
function Write-Log {
param(
[String] $LogEntry
)
$sFullPath = Get-LogName
if (!(Test-Path -Path $sFullPath)) { if (!(Test-Path -Path $sFullPath)) {
Write-Host "Log path: $sFullPath" Write-Host "Log path: $sFullPath"
$null = New-Item -Path $sFullPath -ItemType File $null = New-Item -Path $sFullPath -ItemType File
@ -130,7 +138,7 @@ function Test-FilesExist
[String] $FileFilter [String] $FileFilter
) )
Get-ChildItem $folderName -Recurse | Where-Object {!$_.PSIsContainer} | ForEach-Object { Get-ChildItem $folderName -Recurse -Force | Where-Object {!$_.PSIsContainer} | ForEach-Object {
return $true return $true
} }
@ -144,15 +152,15 @@ function Get-ConvenientFileSize
) )
if ($totalFileSize -ge 1TB) { if ($Size -ge 1TB) {
$totalRightLabel = "TB" $totalRightLabel = "TB"
$totalFileXbytes = [math]::Round(($size / 1TB), 2) $totalFileXbytes = [math]::Round(($size / 1TB), 2)
} else { } else {
if ($totalFileSize -ge 1GB) { if ($Size -ge 1GB) {
$totalRightLabel = "GB" $totalRightLabel = "GB"
$totalFileXbytes = [math]::Round(($size / 1GB), 2) $totalFileXbytes = [math]::Round(($size / 1GB), 2)
} else { } else {
if ($totalFileSize -ge 1MB) { if ($Size -ge 1MB) {
$totalRightLabel = "MB" $totalRightLabel = "MB"
$totalFileXbytes = [math]::Round(($size / 1MB), 2) $totalFileXbytes = [math]::Round(($size / 1MB), 2)
} else { } else {
@ -166,6 +174,60 @@ function Get-ConvenientFileSize
} }
function Get-ReverseConvenientFileSize
{
Param(
[Parameter(Mandatory)]
[String] $Size
)
if ($null -eq $Size -or $size -eq "") {
return ""
}
$found = $false
if ($size -like "*TB") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-2))) * 1TB
}
if ($size -like "*T") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-1))) * 1TB
}
if ($size -like "*GB") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-2))) * 1GB
}
if ($size -like "*G") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-1))) * 1GB
}
if ($size -like "*MB") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-2))) * 1MB
}
if ($size -like "*M") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-1))) * 1MB
}
if ($size -like "*KB") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-2))) * 1KB
}
if ($size -like "*K") {
$found = $true
$totalSize = [int]::Parse($size.Substring(0, ($size.Length-1))) * 1KB
}
if (!$found)
{
$found = $true
$totalSize = [int]::Parse($size)
}
return $totalSize
}
<# <#
.Synopsis .Synopsis
Creates a CSV file for reconciliation at the destination. Creates a CSV file for reconciliation at the destination.
@ -284,16 +346,14 @@ Param(
if ($SourceFolder.StartsWith("@")) { if ($SourceFolder.StartsWith("@")) {
If (!(Test-Path -Path $SourceFolder.Substring(1) )) { If (!(Test-Path -Path $SourceFolder.Substring(1) )) {
Write-Log "File '$($SourceFolder.Substring(1))' does not exist" Write-Log "File '$($SourceFolder.Substring(1))' does not exist"
Write-Host "File '$($SourceFolder.Substring(1))' does not exist" -ForegroundColor Red
Close-Log Close-Log
Exit Throw "File '$($SourceFolder.Substring(1))' does not exist"
} }
} else { } else {
If (!(Test-Path -Path $SourceFolder )) { If (!(Test-Path -Path $SourceFolder )) {
Write-Log "Folder '$SourceFolder' does not exist" Write-Log "Folder '$SourceFolder' does not exist"
Write-Host "Folder '$SourceFolder' does not exist" -ForegroundColor Red
Close-Log Close-Log
Exit Throw "Folder '$SourceFolder' does not exist"
} }
} }
@ -305,7 +365,8 @@ Param(
if (!(Test-Path -Path $dirpath )) { if (!(Test-Path -Path $dirpath )) {
$null = New-Item -Path $dirpath -ItemType Directory $null = New-Item -Path $dirpath -ItemType Directory
} }
$ExifFile = Join-Path -Path $dirpath -ChildPath $global:default_exifFile $fname = [System.IO.Path]::GetFileNameWithoutExtension($ReconcileFile)
$ExifFile = Join-Path -Path $dirpath -ChildPath $($fname+"_exif.csv")
Write-Log "Generating Exif file '$ExifFile'" Write-Log "Generating Exif file '$ExifFile'"
Set-Content -Encoding utf8 -Path $ExifFile -Value $(Set-ExifCsvHeader) Set-Content -Encoding utf8 -Path $ExifFile -Value $(Set-ExifCsvHeader)
} }
@ -337,7 +398,7 @@ Param(
Write-Host "Folder/file '$($_)' does not exist" -ForegroundColor Red Write-Host "Folder/file '$($_)' does not exist" -ForegroundColor Red
} }
else { else {
Get-ChildItem $_ -Filter $FileFilter -Recurse | Where-Object {!$_.PSIsContainer} | ForEach-Object { Get-ChildItem $_ -Filter $FileFilter -Recurse -Force | Where-Object {!$_.PSIsContainer} | ForEach-Object {
$totalFilecount = $totalFileCount + 1 $totalFilecount = $totalFileCount + 1
$totalFileSize = $totalFileSize + $_.Length $totalFileSize = $totalFileSize + $_.Length
@ -354,7 +415,21 @@ Param(
if ($ExcludeHash) { if ($ExcludeHash) {
$sourceHash = "" $sourceHash = ""
} else { } else {
$sourceHash = (Get-FileHash -Path $_.FullName).Hash $fullN = $_.FullName
try
{
$sourceHash = (Get-FileHash -Path $fullN -ErrorAction Stop).Hash
}
catch
{
$sourceHash = ""
$errorDetail = $_.Exception.Message
Write-Log "Error in hash with message: $errorDetail"
Write-Log "Hash creation failed for '$fullN'. Maybe the file is open in another process"
Close-Log
Throw "Hash creation failed for '$fullN'. Maybe the file is open in another process"
}
} }
$record = '"'+$_.FullName.Replace($RootFolder, "")+'","'+$_.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"' $record = '"'+$_.FullName.Replace($RootFolder, "")+'","'+$_.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"'
$record = $record + ',"'+$_.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")+'","'+$_.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"' $record = $record + ',"'+$_.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")+'","'+$_.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"'
@ -376,7 +451,7 @@ Param(
} }
} else { } else {
Get-ChildItem $SourceFolder -Filter $FileFilter -Recurse | Where-Object {!$_.PSIsContainer} | ForEach-Object { Get-ChildItem $SourceFolder -Filter $FileFilter -Recurse -Force| Where-Object {!$_.PSIsContainer} | ForEach-Object {
$totalFilecount = $totalFileCount + 1 $totalFilecount = $totalFileCount + 1
$totalFileSize = $totalFileSize + $_.Length $totalFileSize = $totalFileSize + $_.Length
@ -393,7 +468,21 @@ Param(
if ($ExcludeHash) { if ($ExcludeHash) {
$sourceHash = "" $sourceHash = ""
} else { } else {
$sourceHash = (Get-FileHash -Path $_.FullName).Hash $fullN = $_.FullName
try
{
$sourceHash = (Get-FileHash -Path $fullN -ErrorAction Stop).Hash
}
catch
{
$sourceHash = ""
$errorDetail = $_.Exception.Message
Write-Log "Error in hash with message: $errorDetail"
Write-Log "Hash creation failed for '$fullN'. Maybe the file is open in another process"
Close-Log
Throw "Hash creation failed for '$fullN'. Maybe the file is open in another process"
}
} }
$record = '"'+$_.FullName.Replace($RootFolder, "")+'","'+$_.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"' $record = '"'+$_.FullName.Replace($RootFolder, "")+'","'+$_.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"'
$record = $record + ',"'+$_.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")+'","'+$_.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"' $record = $record + ',"'+$_.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")+'","'+$_.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss")+'"'
@ -428,6 +517,9 @@ function Invoke-SinglePack
[Parameter(Mandatory)][String] $ArchiveFolder, [Parameter(Mandatory)][String] $ArchiveFolder,
[Parameter(Mandatory)][String] $ArchiveFileName, [Parameter(Mandatory)][String] $ArchiveFileName,
[String] $FileFilter, [String] $FileFilter,
[String] $ZipFormat = "SevenZip",
[String] $CompressionLevel = "Normal",
[String] $VolumeSize = "-1",
[Boolean] $FirstCompress [Boolean] $FirstCompress
) )
@ -438,14 +530,14 @@ function Invoke-SinglePack
if (Test-FilesExist -FolderName $ArchiveFolder -FileFilter $FileFilter) { if (Test-FilesExist -FolderName $ArchiveFolder -FileFilter $FileFilter) {
try { try {
if ($FirstCompress) { if ($FirstCompress) {
Compress-7Zip -Path $ArchiveFolder -ArchiveFileName $ArchiveFileName -Format SevenZip -PreserveDirectoryRoot -Filter $FileFilter Compress-7Zip -Path $ArchiveFolder -ArchiveFileName $ArchiveFileName -Format $ZipFormat -CompressionLevel $7zipLevel -PreserveDirectoryRoot -Filter $FileFilter -Volume (Get-ReverseConvenientFileSize $VolumeSize)
} else { } else {
Compress-7Zip -Path $ArchiveFolder -ArchiveFileName $ArchiveFileName -Format SevenZip -PreserveDirectoryRoot -Filter $FileFilter -Append Compress-7Zip -Path $ArchiveFolder -ArchiveFileName $ArchiveFileName -Format $ZipFormat -CompressionLevel $7zipLevel -PreserveDirectoryRoot -Filter $FileFilter -Volume (Get-ReverseConvenientFileSize $VolumeSize) -Append
} }
$FirstCompress = $false $FirstCompress = $false
} catch { } catch {
Write-Log "Compress error with folder/file '$ArchiveFolder'. See any previous errors. $Error" Write-Log "Compress error with folder/file '$ArchiveFolder'. See any previous errors. $Error"
Write-Host "Compress error with folder/file '$ArchiveFolder'. See any previous errors. $Error" -ForegroundColor Red Throw "Compress error with folder/file '$ArchiveFolder'. Please refer to log '$(Get-LogName)' for details"
} }
} else { } else {
Write-Log "Empty folder/file '$ArchiveFolder'" Write-Log "Empty folder/file '$ArchiveFolder'"
@ -575,8 +667,22 @@ function Invoke-SinglePack
The following environment variables are supported: The following environment variables are supported:
- PETERDOCS_RECIPIENTKEY - PETERDOCS_RECIPIENTKEY
- PETERDOCS_SECRETKEY - PETERDOCS_SECRETKEY
- PETERDOCS_7ZIPLEVEL
- PETERDOCS_ZIPFORMAT
- PETERDOCS_LOGPATH - PETERDOCS_LOGPATH
The environment variable _PETERDOCS_7ZIPLEVEL_ is used to override the default
7ZIP compression level setting. This is useful if you already for example when
you know that the binary files are compressed or have no benefit in compression
saving time. For example
```PETERDOCS_7ZIPLEVEL=None```
The environment variable PETERDOCS_ZIPFORMAT is used to override the default
7ZIP format value. Using this option may invalidate other settings. For example
```PETERDOCS_ZIPFORMAT=SevenZip```
.Example .Example
# Pack and encrypt all files in folder ".\transferpack\" using a private-public key # Pack and encrypt all files in folder ".\transferpack\" using a private-public key
# A default archive named file is created which includes a date and time in the name. # A default archive named file is created which includes a date and time in the name.
@ -598,6 +704,7 @@ Param(
[switch] $ExcludeHash, [switch] $ExcludeHash,
[switch] $IncludeExif, [switch] $IncludeExif,
[String] $RootFolder, [String] $RootFolder,
[String] $VolumeSize = "-1",
[String] $LogPath [String] $LogPath
) )
@ -617,31 +724,46 @@ Param(
} else { } else {
Write-Log "Parameter: SecretKey Value: ************** " Write-Log "Parameter: SecretKey Value: ************** "
} }
Write-Log "Parameter: ArchiveFile Value: $ArchiveFile " Write-Log "Parameter: ArchiveFile Value: $ArchiveFile "
Write-Log "Parameter: ReconcileFile Value: $ReconcileFile " Write-Log "Parameter: ReconcileFile Value: $ReconcileFile "
Write-Log "Parameter: FileFilter Value: $FileFilter " Write-Log "Parameter: FileFilter Value: $FileFilter "
Write-Log "Parameter: SecretFile Value: $SecretFile " Write-Log "Parameter: SecretFile Value: $SecretFile "
Write-Log "Parameter: ExcludeHash Value: $ExcludeHash " Write-Log "Parameter: ExcludeHash Value: $ExcludeHash "
Write-Log "Parameter: IncludeExif Value: $IncludeExif " Write-Log "Parameter: IncludeExif Value: $IncludeExif "
Write-Log "Parameter: VolumeSize Value: $VolumeSize "
Write-Log "Parameter: LogPath Value: $LogPath " Write-Log "Parameter: LogPath Value: $LogPath "
Write-Log "" Write-Log ""
if ($SourceFolder.StartsWith("@")) { if ($SourceFolder.StartsWith("@")) {
If (!(Test-Path -Path $SourceFolder.Substring(1) )) { If (!(Test-Path -Path $SourceFolder.Substring(1) )) {
Write-Log "File '$($SourceFolder.Substring(1))' does not exist" Write-Log "File '$($SourceFolder.Substring(1))' does not exist"
Write-Host "File '$($SourceFolder.Substring(1))' does not exist" -ForegroundColor Red
Close-Log Close-Log
Exit Throw "File '$($SourceFolder.Substring(1))' does not exist"
} }
} else { } else {
If (!(Test-Path -Path $SourceFolder )) { If (!(Test-Path -Path $SourceFolder )) {
Write-Log "Folder '$SourceFolder' does not exist" Write-Log "Folder '$SourceFolder' does not exist"
Write-Host "Folder '$SourceFolder' does not exist" -ForegroundColor Red
Close-Log Close-Log
Exit Throw "Folder '$SourceFolder' does not exist"
} }
} }
$getEnvName = $(Get-SoftwareName) + "_7ZIPLEVEL"
if ([System.Environment]::GetEnvironmentVariable($getEnvName) -ne "" -and $null -ne [System.Environment]::GetEnvironmentVariable($getEnvName)) {
$7zipLevel = [System.Environment]::GetEnvironmentVariable($getEnvName)
Write-Log "Compression level set to '$7zipLevel'"
} else {
$7zipLevel = "Normal"
}
$getEnvName = $(Get-SoftwareName) + "_ZIPFORMAT"
if ([System.Environment]::GetEnvironmentVariable($getEnvName) -ne "" -and $null -ne [System.Environment]::GetEnvironmentVariable($getEnvName)) {
$7zipFormat = [System.Environment]::GetEnvironmentVariable($getEnvName)
Write-Log "Compression format set to '$7zipFormat'"
} else {
$7zipFormat= "SevenZip"
}
if ($RecipientKey -eq "") { if ($RecipientKey -eq "") {
$getEnvName = $(Get-SoftwareName) + "_RECIPIENTKEY" $getEnvName = $(Get-SoftwareName) + "_RECIPIENTKEY"
@ -659,17 +781,15 @@ Param(
if (($RecipientKey -eq "") -and ($SecretKey -eq "")) { if (($RecipientKey -eq "") -and ($SecretKey -eq "")) {
Write-Log "Recipient Key or Secret Key required for packing" Write-Log "Recipient Key or Secret Key required for packing"
Write-Host "Recipient Key or Secret Key required for packing" -ForegroundColor Red
Close-Log Close-Log
return Throw "Recipient Key or Secret Key required for packing"
} }
if ($RootFolder -eq "") { if ($RootFolder -eq "") {
if ($SourceFolder.EndsWith("*")) { if ($SourceFolder.EndsWith("*")) {
Write-Log "Root folder required for packing when using wild card for Source Folder" Write-Log "Root folder required for packing when using wild card for Source Folder"
Write-Host "Root folder required for packing when using wild card for Source Folder" -ForegroundColor Red
Close-Log Close-Log
return Throw "Root folder required for packing when using wild card for Source Folder"
} else { } else {
$RootFolder = $SourceFolder $RootFolder = $SourceFolder
} }
@ -679,19 +799,22 @@ Param(
$ArchiveFile = $(Get-SoftwareName) + $(Get-Date -Format "yyyyMMdd_HHmm") + ".7z" $ArchiveFile = $(Get-SoftwareName) + $(Get-Date -Format "yyyyMMdd_HHmm") + ".7z"
} }
if ($SecretKey -eq "") { if ($SecrettKey -eq "") {
if ($SecretFile -eq "") if ($SecretFile -eq "") {
{
$SecretFile = $ArchiveFile + ".key" $SecretFile = $ArchiveFile + ".key"
} }
$secret = New-RandomPassword -Length 80 $secret = New-RandomPassword -Length 80
Protect-CmsMessage -To $recipientKey -OutFile $SecretFile -Content $secret Protect-CmsMessage -To $recipientKey -OutFile $SecretFile -Content $secret
if (!(Test-Path -Path $SecretFile)) {
Write-Log "Secret file '$SecretFile' not found"
Close-Log
Throw "Secret file '$SecretFile' not found"
}
} else { } else {
if (!(Test-PasswordQuality -TestPassword $SecretKey)) { if (!(Test-PasswordQuality -TestPassword $SecretKey)) {
Write-Log "Secret Key does not meet complexity rules" Write-Log "Secret Key does not meet complexity rules"
Write-Host "Secret Key does not meet complexity rules" -ForegroundColor Red
Close-Log Close-Log
return Throw "Secret Key does not meet complexity rules"
} }
$secret = $SecretKey $secret = $SecretKey
} }
@ -700,25 +823,22 @@ Param(
Write-Log "Saving folders/files to archive file '$ArchiveFile'" Write-Log "Saving folders/files to archive file '$ArchiveFile'"
Write-Host "Saving folders/files to archive file '$ArchiveFile'" Write-Host "Saving folders/files to archive file '$ArchiveFile'"
if ($ReconcileFile -eq "") if ($ReconcileFile -eq "") {
{
if (!(Test-Path -Path $global:MetadataPathName)) { if (!(Test-Path -Path $global:MetadataPathName)) {
$null = New-Item -Path $global:MetadataPathName -ItemType Directory $null = New-Item -Path $global:MetadataPathName -ItemType Directory
} }
$ReconcileFile = Join-Path -Path $global:MetadataPathName -ChildPath $default_reconcileFile $ReconcileFile = Join-Path -Path $global:MetadataPathName -ChildPath $default_reconcileFile
} }
if ($FileFilter -eq "") if ($FileFilter -eq "") {
{
$FileFilter = "*" $FileFilter = "*"
} }
if ($SourceFolder.EndsWith("*")) if ($SourceFolder.EndsWith("*")) {
{
Write-Log "Archive primary folder is '$SourceFolder'" Write-Log "Archive primary folder is '$SourceFolder'"
$firstCompress = $true $firstCompress = $true
Get-ChildItem $SourceFolder| ForEach-Object { Get-ChildItem $SourceFolder -Force | ForEach-Object {
$firstCompress = Invoke-SinglePack -ArchiveFolder $_.FullName -ArchiveFile $ArchiveFile -FileFilter $FileFilter -FirstCompress $firstCompress $firstCompress = Invoke-SinglePack -ArchiveFolder $_.FullName -ArchiveFile $ArchiveFile -FileFilter $FileFilter -ZipFormat $7zipFormat -FirstCompress $firstCompress -CompressionLevel $7zipLevel
} }
} else { } else {
if ($SourceFolder.StartsWith("@")) { if ($SourceFolder.StartsWith("@")) {
@ -730,8 +850,8 @@ Param(
if ($_ -ne "") { if ($_ -ne "") {
if ($_.EndsWith("*")) { if ($_.EndsWith("*")) {
Get-ChildItem $_ | ForEach-Object { Get-ChildItem $_ -Force | ForEach-Object {
$firstCompress = Invoke-SinglePack -ArchiveFolder $_.FullName -ArchiveFile $ArchiveFile -FileFilter $FileFilter -FirstCompress $firstCompress $firstCompress = Invoke-SinglePack -ArchiveFolder $_.FullName -ArchiveFile $ArchiveFile -FileFilter $FileFilter -ZipFormat $7zipFormat -FirstCompress $firstCompress -CompressionLevel $7zipLevel
} }
} else { } else {
@ -740,7 +860,7 @@ Param(
Write-Host "Folder/file '$($_)' does not exist" -ForegroundColor Red Write-Host "Folder/file '$($_)' does not exist" -ForegroundColor Red
} }
else { else {
$firstCompress = Invoke-SinglePack -ArchiveFolder $_ -ArchiveFile $ArchiveFile -FileFilter $FileFilter -FirstCompress $firstCompress $firstCompress = Invoke-SinglePack -ArchiveFolder $_ -ArchiveFile $ArchiveFile -FileFilter $FileFilter -ZipFormat $7zipFormat -FirstCompress $firstCompress -CompressionLevel $7zipLevel
} }
} }
} }
@ -748,27 +868,38 @@ Param(
} else { } else {
Write-Log "Archive folder '$SourceFolder'" Write-Log "Archive folder '$SourceFolder'"
Write-Host "Archive folder '$SourceFolder'" Write-Host "Archive folder '$SourceFolder'"
Compress-7Zip -Path $SourceFolder -ArchiveFileName $ArchiveFile -Format SevenZip -Filter $FileFilter Compress-7Zip -Path $SourceFolder -ArchiveFileName $ArchiveFile -Format $7zipFormat -CompressionLevel $7zipLevel -Filter $FileFilter -Volume (Get-ReverseConvenientFileSize $VolumeSize)
} }
} }
$multiVolume = $false
If (!(Test-Path -Path $ArchiveFile )) { If (!(Test-Path -Path $ArchiveFile )) {
Write-Log "Archive file '$ArchiveFile' was not created. See any previous errors" # Check for volume
Write-Host "Archive file '$ArchiveFile' was not created. See any previous errors" -ForegroundColor Red If (!(Test-Path -Path $($ArchiveFile+".001") )) {
Close-Log Write-Log "Archive file '$ArchiveFile' was not created. See any previous errors"
Exit Close-Log
Throw "Archive file '$ArchiveFile' was not created. Please refer to log '$(Get-LogName)' for details"
} else {
$multiVolume = $true
Write-Log "Multi volume archive file '$ArchiveFile' created."
Write-Host "Multi volume archive file '$ArchiveFile' created."
}
} }
if ($multiVolume) {
$archiveInfo = Get-7ZipInformation -ArchiveFileName $ArchiveFile $fullZipName = (Get-Item $($ArchiveFile+".001")).FullName
[int] $archiveFileCount = $archiveInfo.FilesCount $archiveInfo = Get-7ZipInformation -ArchiveFileName $fullZipName
[long] $archiveFileCount = $archiveInfo.FilesCount
} else {
$archiveInfo = Get-7ZipInformation -ArchiveFileName $ArchiveFile
[long] $archiveFileCount = $archiveInfo.FilesCount
}
New-PeterReconcile -ReconcileFile $ReconcileFile -SourceFolder $SourceFolder -FileFilter $FileFilter -RootFolder $rootFolder -ExcludeHash:$ExcludeHash -ProcessFileCount $archiveFileCount -IncludeExif:$IncludeExif New-PeterReconcile -ReconcileFile $ReconcileFile -SourceFolder $SourceFolder -FileFilter $FileFilter -RootFolder $rootFolder -ExcludeHash:$ExcludeHash -ProcessFileCount $archiveFileCount -IncludeExif:$IncludeExif
If (!(Test-Path -Path $ReconcileFile )) { If (!(Test-Path -Path $ReconcileFile )) {
Write-Log "Reconcile file '$ReconcileFile' was not created. See any previous errors" Write-Log "Reconcile file '$ReconcileFile' was not created. See any previous errors"
Write-Host "Reconcile file '$ReconcileFile' was not created. See any previous errors" -ForegroundColor Red
Close-Log Close-Log
return Throw "Reconcile file '$ReconcileFile' was not created. Please refer to log '$(Get-LogName)' for details"
} }
# Write Json file as links # Write Json file as links
@ -782,19 +913,20 @@ Param(
$jsonData.Add("Software",$dataItem) $jsonData.Add("Software",$dataItem)
$items = New-Object System.Collections.ArrayList $items = New-Object System.Collections.ArrayList
$dataItem = @{"Reconcile"="$ReconcileFile";"Caption"="File listing of archive for reconciliation";} $dataItem = @{"Topic"="Reconcile";"Reconcile"="$ReconcileFile";"Caption"="File listing of archive for reconciliation";}
$null = $items.Add($dataItem) $null = $items.Add($dataItem)
if ($IncludeExif) { if ($IncludeExif) {
$ExifFile = Join-Path -Path $global:MetadataPathName -ChildPath $global:default_exifFile $fname = [System.IO.Path]::GetFileNameWithoutExtension($ReconcileFile)
$dataItem = @{"Exif"="$ExifFile";"Caption"="Exif information";} $ExifFile = Join-Path -Path $global:MetadataPathName -ChildPath $($fname+"_exif.csv")
$dataItem = @{"Topic"="Exif";"Exif"="$ExifFile";"Caption"="Exif information";}
$null = $items.Add($dataItem) $null = $items.Add($dataItem)
} }
$dataItem = @{"SecretFile"="$SecretFile";"Caption"="File used for complex password storage with asymmetric key";} $dataItem = @{"Topic"="SecretFile";"SecretFile"="$SecretFile";"Caption"="File used for complex password storage with asymmetric key";}
$null = $items.Add($dataItem) $null = $items.Add($dataItem)
$dataItem = @{"FileFilter"="$FileFilter";"Caption"="File filter used with Compress";} $dataItem = @{"Topic"="FileFilter";"FileFilter"="$FileFilter";"Caption"="File filter used with Compress";}
$null = $items.Add($dataItem) $null = $items.Add($dataItem)
$jsonData.Add("Links",$items) $jsonData.Add("Links",$items)
@ -802,9 +934,18 @@ Param(
Write-Log "Add folder '$global:MetadataPathName' to file '$ArchiveFile'" Write-Log "Add folder '$global:MetadataPathName' to file '$ArchiveFile'"
$fullMetadatName = (Get-Item $global:MetadataPathName).FullName $fullMetadatName = (Get-Item $global:MetadataPathName).FullName
$fullZipName = (Get-Item $ArchiveFile).FullName if ($multiVolume) {
Compress-7Zip -Path $fullMetadatName -ArchiveFileName $fullZipName -PreserveDirectoryRoot -Format SevenZip -Append -Password $secret -EncryptFilenames $fext = (Get-ChildItem $ArchiveFile).Extension
Remove-Item $fullMetadatName -Recurse $fname = [System.IO.Path]::GetFileNameWithoutExtension($ArchiveFile)
$fullZipName = (Get-Item $($fname+"_meta"+$fext)).FullName
Compress-7Zip -Path $fullMetadatName -ArchiveFileName $fullZipName -PreserveDirectoryRoot -Format SevenZip -Append -Password $secret -EncryptFilenames
# TODO: Change for volumes
# -Volume (Get-ReverseConvenientFileSize $VolumeSize)
} else {
$fullZipName = (Get-Item $ArchiveFile).FullName
Compress-7Zip -Path $fullMetadatName -ArchiveFileName $fullZipName -PreserveDirectoryRoot -Format SevenZip -Append -Password $secret -EncryptFilenames -Volume (Get-ReverseConvenientFileSize $VolumeSize)
Remove-Item $fullMetadatName -Recurse
}
Write-Log "Archive file '$ArchiveFile' created from folder '$SourceFolder'" Write-Log "Archive file '$ArchiveFile' created from folder '$SourceFolder'"
Write-Host "Archive file '$ArchiveFile' created from folder '$SourceFolder'" -ForegroundColor Green Write-Host "Archive file '$ArchiveFile' created from folder '$SourceFolder'" -ForegroundColor Green
@ -943,9 +1084,8 @@ Param(
if (!(Test-Path -Path $ArchiveFile )) { if (!(Test-Path -Path $ArchiveFile )) {
Write-Log "Archive file '$ArchiveFile' not found" Write-Log "Archive file '$ArchiveFile' not found"
Write-Host "Archive file '$ArchiveFile' not found" -ForegroundColor Red
Close-Log Close-Log
return Throw "Archive file '$ArchiveFile' not found"
} }
if ($SecretFile -eq "") { if ($SecretFile -eq "") {
$SecretFile = $ArchiveFile + ".key" $SecretFile = $ArchiveFile + ".key"
@ -963,9 +1103,8 @@ Param(
if ($bucketHost -eq "") { if ($bucketHost -eq "") {
Write-Log "Bucket name required" Write-Log "Bucket name required"
Write-Host "Bucket name required" -ForegroundColor Red
Close-Log Close-Log
return Throw "Bucket name required"
} }
Try { Try {
@ -986,7 +1125,8 @@ Param(
Write-Host "Archive file '$ArchiveFile' stored on AWS S3 bucket '$bucketHost' at '$targetObject'" -ForegroundColor Green Write-Host "Archive file '$ArchiveFile' stored on AWS S3 bucket '$bucketHost' at '$targetObject'" -ForegroundColor Green
} Catch { } Catch {
Write-Log "Error in sending archive file '$ArchiveFile' to AWS S3 with error: $($_)" Write-Log "Error in sending archive file '$ArchiveFile' to AWS S3 with error: $($_)"
Write-Host "Error in sending archive file '$ArchiveFile' to AWS S3 with error: $($_)" -ForegroundColor Red Close-Log
Throw "Error in sending archive file '$ArchiveFile' to AWS S3 with error: $($_)"
} }
} }
@ -1011,9 +1151,8 @@ Param(
} }
if ($null -eq $accountKey -or $accountKey -eq "") { if ($null -eq $accountKey -or $accountKey -eq "") {
Write-Log "Account key required" Write-Log "Account key required"
Write-Host "Account key required" -ForegroundColor Red
Close-Log Close-Log
return Throw "Account key required"
} }
} }
@ -1023,25 +1162,22 @@ Param(
$b2ApiToken = Get-B2ApiToken -AccountId $AccountId -AccountKey $AccountKey $b2ApiToken = Get-B2ApiToken -AccountId $AccountId -AccountKey $AccountKey
} Catch { } Catch {
Write-Log "Authentication error with account '$AccountID'" Write-Log "Authentication error with account '$AccountID'"
Write-Host "Authentication error with account '$AccountID'" -ForegroundColor Red
Close-Log Close-Log
return Throw "Authentication error with account '$AccountID'"
} }
if ($null -eq $b2ApiToken.Token -or $b2ApiToken.Token -eq "") if ($null -eq $b2ApiToken.Token -or $b2ApiToken.Token -eq "")
{ {
Write-Log "Authentication error with account '$AccountID' as no API Token" Write-Log "Authentication error with account '$AccountID' as no API Token"
Write-Host "Authentication error with account '$AccountID' as no API Token" -ForegroundColor Red
Close-Log Close-Log
return Throw "Authentication error with account '$AccountID' as no API Token"
} }
$b2Bucket = Get-B2Bucket -ApiToken $b2ApiToken.Token -AccountId $b2ApiToken.accountId -ApiUri $b2ApiToken.ApiUri -BucketHost $bucketHost $b2Bucket = Get-B2Bucket -ApiToken $b2ApiToken.Token -AccountId $b2ApiToken.accountId -ApiUri $b2ApiToken.ApiUri -BucketHost $bucketHost
if ($null -eq $b2Bucket -or $b2Bucket.BucketID -eq "") { if ($null -eq $b2Bucket -or $b2Bucket.BucketID -eq "") {
Write-Log "Bucket '$bucketHost' not found" Write-Log "Bucket '$bucketHost' not found"
Write-Host "Bucket '$bucketHost' not found" -ForegroundColor Red
Close-Log Close-Log
return Throw "Bucket '$bucketHost' not found"
} }
$b2UploadUri = Get-B2UploadUri -BucketHost $b2Bucket.bucketId -FileName $ArchiveFile -ApiUri $b2ApiToken.ApiUri -ApiToken $b2ApiToken.Token $b2UploadUri = Get-B2UploadUri -BucketHost $b2Bucket.bucketId -FileName $ArchiveFile -ApiUri $b2ApiToken.ApiUri -ApiToken $b2ApiToken.Token
@ -1065,7 +1201,6 @@ Param(
if (!($remoteType)) { if (!($remoteType)) {
Write-Log "Unknown remote path '$TargetPath.'. No transfer performed" Write-Log "Unknown remote path '$TargetPath.'. No transfer performed"
Write-Host "Unknown remote path '$TargetPath.'. No transfer performed" -ForegroundColor Red
Write-Host "Recognised transfer prefixes: " Write-Host "Recognised transfer prefixes: "
Write-Host " s3:// : Send to AWS S3 location" Write-Host " s3:// : Send to AWS S3 location"
Write-Host " b3:// : Send to Backblaze location" Write-Host " b3:// : Send to Backblaze location"
@ -1073,6 +1208,8 @@ Param(
Write-Host "If you are saving to local drives or network shared folders," Write-Host "If you are saving to local drives or network shared folders,"
Write-Host "please use your OS tools to move the file" Write-Host "please use your OS tools to move the file"
Write-Host " " Write-Host " "
Close-Log
Throw "Unknown remote path '$TargetPath.'. No transfer performed"
} }
Close-Log Close-Log
@ -1226,7 +1363,8 @@ Param(
$null = Read-S3Object -BucketName $bucketHost -File $ArchiveFile -Key $sourceObject $null = Read-S3Object -BucketName $bucketHost -File $ArchiveFile -Key $sourceObject
if (!(Test-Path -Path $ArchiveFile)) { if (!(Test-Path -Path $ArchiveFile)) {
Write-Log "Archive file '$sourceObject' not found." Write-Log "Archive file '$sourceObject' not found."
Write-Host "Archive file '$sourceObject' not found." -ForegroundColor Red Close-Log
Throw "Archive file '$sourceObject' not found."
} else { } else {
$sourceObject = $SourcePath.Substring($offset) + ".key" $sourceObject = $SourcePath.Substring($offset) + ".key"
$secretFile = $ArchiveFile + ".key" $secretFile = $ArchiveFile + ".key"
@ -1265,9 +1403,8 @@ Param(
} }
if ($null -eq $AccountKey -or $AccountKey -eq "") { if ($null -eq $AccountKey -or $AccountKey -eq "") {
Write-Log "Account key required" Write-Log "Account key required"
Write-Host "Account key required" -ForegroundColor Red
Close-Log Close-Log
return Throw "Account key required"
} }
} }
@ -1275,25 +1412,22 @@ Param(
$b2ApiToken = Get-B2ApiToken -AccountId $AccountId -AccountKey $AccountKey $b2ApiToken = Get-B2ApiToken -AccountId $AccountId -AccountKey $AccountKey
} Catch { } Catch {
Write-Log "Authentication error with account '$AccountID'" Write-Log "Authentication error with account '$AccountID'"
Write-Host "Authentication error with account '$AccountID'" -ForegroundColor Red
Close-Log Close-Log
return Throw "Authentication error with account '$AccountID'"
} }
if ($null -eq $b2ApiToken.Token -or $b2ApiToken.Token -eq "") if ($null -eq $b2ApiToken.Token -or $b2ApiToken.Token -eq "")
{ {
Write-Log "Authentication error with account '$AccountID' as no API Token" Write-Log "Authentication error with account '$AccountID' as no API Token"
Write-Host "Authentication error with account '$AccountID' as no API Token" -ForegroundColor Red
Close-Log Close-Log
return Throw "Authentication error with account '$AccountID' as no API Token"
} }
$b2Bucket = Get-B2Bucket -ApiToken $b2ApiToken.Token -AccountId $b2ApiToken.accountId -ApiUri $b2ApiToken.ApiUri -BucketHost $bucketHost $b2Bucket = Get-B2Bucket -ApiToken $b2ApiToken.Token -AccountId $b2ApiToken.accountId -ApiUri $b2ApiToken.ApiUri -BucketHost $bucketHost
if ($null -eq $b2Bucket -or $b2Bucket.BucketID -eq "") { if ($null -eq $b2Bucket -or $b2Bucket.BucketID -eq "") {
Write-Log "Bucket '$bucketHost' not found" Write-Log "Bucket '$bucketHost' not found"
Write-Host "Bucket '$bucketHost' not found" -ForegroundColor Red
Close-Log Close-Log
return Throw "Bucket '$bucketHost' not found"
} }
$sourceObject = $SourcePath.Substring($offset) $sourceObject = $SourcePath.Substring($offset)
@ -1302,7 +1436,8 @@ Param(
Receive-B2Download -BucketHost $bucketHost -SourcePath $sourceObject -FileName $ArchiveFile -ApiDownloadUri $b2ApiToken.DownloadUri -ApiToken $b2ApiToken.Token Receive-B2Download -BucketHost $bucketHost -SourcePath $sourceObject -FileName $ArchiveFile -ApiDownloadUri $b2ApiToken.DownloadUri -ApiToken $b2ApiToken.Token
if (!(Test-Path -Path $ArchiveFile)) { if (!(Test-Path -Path $ArchiveFile)) {
Write-Log "Archive file '$sourceObject' not found." Write-Log "Archive file '$sourceObject' not found."
Write-Host "Archive file '$sourceObject' not found." -ForegroundColor Red Close-Log
Throw "Archive file '$sourceObject' not found."
} else { } else {
$sourceObject = $SourcePath.Substring($offset) + ".key" $sourceObject = $SourcePath.Substring($offset) + ".key"
$secretFile = $ArchiveFile + ".key" $secretFile = $ArchiveFile + ".key"
@ -1323,7 +1458,6 @@ Param(
if (!($remoteType)) { if (!($remoteType)) {
Write-Log "Unknown remote path '$SourcePath'. No get performed" Write-Log "Unknown remote path '$SourcePath'. No get performed"
Write-Host "Unknown remote path '$SourcePath'. No get performed" -ForegroundColor Red
Write-Host "Recognised transfer prefixes: " Write-Host "Recognised transfer prefixes: "
Write-Host " s3://bucket/path/path : Fetch from AWS S3 location" Write-Host " s3://bucket/path/path : Fetch from AWS S3 location"
Write-Host " b2://bucket/path/path : Fetch from Backblaze location" Write-Host " b2://bucket/path/path : Fetch from Backblaze location"
@ -1331,6 +1465,8 @@ Param(
Write-Host "If you are fetching from local drives or network shared folders," Write-Host "If you are fetching from local drives or network shared folders,"
Write-Host "please use your OS tools to move the file" Write-Host "please use your OS tools to move the file"
Write-Host " " Write-Host " "
Close-Log
Throw "Unknown remote path '$SourcePath'. No get performed"
} }
Close-Log Close-Log
@ -1459,9 +1595,8 @@ Param(
If (!(Test-Path -Path $ArchiveFile )) { If (!(Test-Path -Path $ArchiveFile )) {
Write-Log "Archive file '$ArchiveFile' does not exist" Write-Log "Archive file '$ArchiveFile' does not exist"
Write-Host "Archive file '$ArchiveFile' does not exist" -ForegroundColor Red
Close-Log Close-Log
return Throw "Archive file '$ArchiveFile' does not exist"
} }
if ($RecipientKey -eq "") { if ($RecipientKey -eq "") {
@ -1480,9 +1615,8 @@ Param(
if (($RecipientKey -eq "") -and ($SecretKey -eq "")) { if (($RecipientKey -eq "") -and ($SecretKey -eq "")) {
Write-Log "Recipient Key name or Secret Key required for unpacking" Write-Log "Recipient Key name or Secret Key required for unpacking"
Write-Host "Recipient Key name or Secret Key required for unpacking" -ForegroundColor Red
Close-Log Close-Log
return Throw "Recipient Key name or Secret Key required for unpacking"
} }
if ($SecretKey -eq "") { if ($SecretKey -eq "") {
@ -1490,14 +1624,11 @@ Param(
{ {
$SecretFile = $ArchiveFile + ".key" $SecretFile = $ArchiveFile + ".key"
} }
If (!(Test-Path -Path $SecretFile )) { If (!(Test-Path -Path $SecretFile )) {
Write-Log "Secret file '$SecretFile' does not exist" Write-Log "Secret file '$SecretFile' does not exist"
Write-Host "Secret file '$SecretFile' does not exist" -ForegroundColor Red
Close-Log Close-Log
return Throw "Secret file '$SecretFile' does not exist"
} }
$secret = Unprotect-CmsMessage -To $RecipientKey -Path $SecretFile $secret = Unprotect-CmsMessage -To $RecipientKey -Path $SecretFile
} else { } else {
$secret = $SecretKey $secret = $SecretKey
@ -1599,9 +1730,8 @@ Param(
If (!(Test-Path -Path $RestoreFolder )) { If (!(Test-Path -Path $RestoreFolder )) {
Write-Log "Folder '$RestoreFolder' does not exist" Write-Log "Folder '$RestoreFolder' does not exist"
Write-Host "Folder '$RestoreFolder' does not exist" -ForegroundColor Red
Close-Log Close-Log
return Throw "Folder '$RestoreFolder' does not exist"
} }
@ -1641,9 +1771,8 @@ Param(
If (!(Test-Path -Path $ReconcileFile )) { If (!(Test-Path -Path $ReconcileFile )) {
Write-Log "Reconciliation file '$ReconcileFile' does not exist" Write-Log "Reconciliation file '$ReconcileFile' does not exist"
Write-Host "Reconciliation file '$ReconcileFile' does not exist" -ForegroundColor Red
Close-Log Close-Log
return Throw "Reconciliation file '$ReconcileFile' does not exist"
} }
Write-Log "Reconciling documents transferred" Write-Log "Reconciling documents transferred"
@ -1667,6 +1796,7 @@ Param(
Import-Csv $ReconcileFile | ForEach-Object { Import-Csv $ReconcileFile | ForEach-Object {
$totalFileCount = $totalFileCount +1 $totalFileCount = $totalFileCount +1
$errorFileLogged = $false
if ($RootFolder -ne "") { if ($RootFolder -ne "") {
$adjustedName = $_.FullName.Replace($RootFolder, "\") $adjustedName = $_.FullName.Replace($RootFolder, "\")
$restoreFileName = $(Join-Path -Path $RestoreFolder -ChildPath $adjustedName) $restoreFileName = $(Join-Path -Path $RestoreFolder -ChildPath $adjustedName)
@ -1674,66 +1804,131 @@ Param(
$restoreFileName = $(Join-Path -Path $RestoreFolder -ChildPath $_.FullName) $restoreFileName = $(Join-Path -Path $RestoreFolder -ChildPath $_.FullName)
} }
If (Test-Path -Path $restoreFileName ) { If (Test-Path -Path $restoreFileName ) {
$fileItem = Get-Item -Path $restoreFileName -Force
if (!($ExcludeHash)) { if (!($ExcludeHash)) {
if ($_.Hash -ne "") { if ($_.Hash -ne "") {
$targetHash= (Get-FileHash -Path $restoreFileName).Hash $targetHash= (Get-FileHash -Path $restoreFileName).Hash
if ($_.Hash -ne $targetHash) { if ($_.Hash -ne $targetHash) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
Write-Log "Hash mismatch for file '$restoreFileName' with target value $targetHash" Write-Log "Hash mismatch for file '$restoreFileName' with target value $targetHash"
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
} else { } else {
$missingHash = $true $missingHash = $true
} }
} }
if ((Get-Item -Path $restoreFileName).LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastWriteTime) { if ($fileItem.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastWriteTime) {
Write-Log "LastWrite mismatch for file '$restoreFileName' with target value $((Get-Item -Path $restoreFileName).LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")) expected $($_.LastWriteTime)" Write-Log "LastWrite mismatch for file '$restoreFileName' with target value $($fileItem.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss")) expected $($_.LastWriteTime)"
$errorCreateCount = $errorCreateCount + 1 $errorCreateCount = $errorCreateCount + 1
$dateTimeValue = [Datetime]::ParseExact($_.LastWriteTime, 'yyyy-MM-ddTHH:mm:ss', $null) $dateTimeValue = [Datetime]::ParseExact($_.LastWriteTime, 'yyyy-MM-ddTHH:mm:ss', $null)
$fileValue = (Get-Item -Path $restoreFileName).LastWriteTime $fileValue = $fileItem.LastWriteTime
$diff = ($dateTimeValue - $fileValue).Seconds $diff = ($dateTimeValue - $fileValue).Seconds
# Allow +/- 2 second discrepancy # Allow +/- 2 second discrepancy
if (($diff.Seconds -lt -2) -or ($diff.Seconds -gt 2)) { if (($diff.Seconds -lt -2) -or ($diff.Seconds -gt 2)) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
} }
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
if ((Get-Item -Path $restoreFileName).Length -ne $_.Length) { if ($fileItem.Length -ne $_.Length) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
Write-Log "Length mismatch for file '$restoreFileName' with target value $(Get-Item -Path $restoreFileName).Length) expected $($_.Length)" Write-Log "Length mismatch for file '$restoreFileName' with target value $($fileItem.Length) expected $($_.Length)"
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
# Note that last / write access time is not checked by default as it will commonly be changed after restore # Note that last / write access time is not checked by default as it will commonly be changed after restore
if ($extendedCheck) { if ($extendedCheck) {
if ((Get-Item -Path $restoreFileName).CreationTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.CreationTime) { if ($fileItem.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.CreationTime) {
Write-Log "Creation mismatch for file '$restoreFileName' with target value $((Get-Item -Path $restoreFileName).CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")) expected $($_.CreationTime)" Write-Log "Creation mismatch for file '$restoreFileName' with target value $($fileItem.CreationTime.ToString("yyyy-MM-ddTHH:mm:ss")) expected $($_.CreationTime)"
$errorCreateCount = $errorCreateCount + 1 $errorCreateCount = $errorCreateCount + 1
$dateTimeValue = [Datetime]::ParseExact($_.CreationTime, 'yyyy-MM-ddTHH:mm:ss', $null) $dateTimeValue = [Datetime]::ParseExact($_.CreationTime, 'yyyy-MM-ddTHH:mm:ss', $null)
$fileValue = (Get-Item -Path $restoreFileName).CreationTime $fileValue = $fileItem.CreationTime
$diff = ($dateTimeValue - $fileValue).Seconds $diff = ($dateTimeValue - $fileValue).Seconds
# Allow +/- 2 second discrepancy # Allow +/- 2 second discrepancy
if (($diff.Seconds -lt -2) -or ($diff.Seconds -gt 2)) { if (($diff.Seconds -lt -2) -or ($diff.Seconds -gt 2)) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
} }
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
if ((Get-Item -Path $restoreFileName).LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastAccessTime) { if ($fileItem.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastAccessTime) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
Write-Log "Last access mismatch for file '$restoreFileName' with target value $((Get-Item -Path $restoreFileName).LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss"))" Write-Log "Last access mismatch for file '$restoreFileName' with target value $($fileItem.LastAccessTime.ToString("yyyy-MM-ddTHH:mm:ss"))"
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
if ((Get-Item -Path $restoreFileName).LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastWriteTime) { if ($fileItem.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss") -ne $_.LastWriteTime) {
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
Write-Log "Last write mismatch for file '$restoreFileName' with target value $((Get-Item -Path $restoreFileName).LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss"))" Write-Log "Last write mismatch for file '$restoreFileName' with target value $($fileItem.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss"))"
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
} }
$totalFileSize = $totalFileSize + (Get-Item -Path $restoreFileName).Length $totalFileSize = $totalFileSize + $fileItem.Length
} else { } else {
$missingFileCount = $missingFileCount + 1 $missingFileCount = $missingFileCount + 1
$errorCount = $errorCount + 1 $errorCount = $errorCount + 1
Write-Log "Non existant target file '$restoreFileName'" Write-Log "Non existant target file '$restoreFileName'"
if (!$errorFileLogged) {
if (!(Test-Path -Path $global:default_errorListFile)) {
$null = New-Item -Path $global:default_errorListFile -ItemType File
}
Add-Content -Path $global:default_errorListFile -Value "$restoreFileName"
$errorFileLogged = $true
}
} }
if ( $ProcessFileCount -gt 0) { if ( $ProcessFileCount -gt 0) {
@ -1757,21 +1952,28 @@ Param(
Write-Log "Total file count is $totalFileCount with $errorCount errors" Write-Log "Total file count is $totalFileCount with $errorCount errors"
Write-Log "There are $missingFileCount missing files" Write-Log "There are $missingFileCount missing files"
$errorDetected = $false
if ($errorCreateCount -gt 0) { if ($errorCreateCount -gt 0) {
$errorDetected = $true
Write-Log "File create mismatch count is $errorCreateCount" Write-Log "File create mismatch count is $errorCreateCount"
Write-Host "File create mismatch count is $errorCreateCount" -ForegroundColor Red Write-Host "File create mismatch count is $errorCreateCount" -ForegroundColor Red
} }
if ($errorCount -gt 0) { if ($errorCount -gt 0) {
$errorDetected = $true
Write-Host "Total file count is $totalFileCount with $errorCount errors" -ForegroundColor Red Write-Host "Total file count is $totalFileCount with $errorCount errors" -ForegroundColor Red
} else { } else {
Write-Host "Total file count is $totalFileCount with $errorCount errors" -ForegroundColor Green Write-Host "Total file count is $totalFileCount with $errorCount errors" -ForegroundColor Green
} }
if ($missingFileCount -gt 0) { if ($missingFileCount -gt 0) {
$errorDetected = $true
Write-Host "There are $missingFileCount missing files" -ForegroundColor Red Write-Host "There are $missingFileCount missing files" -ForegroundColor Red
} }
Close-Log Close-Log
if ($errorDetected) {
Throw "Compare mismatch error detected. Please refer to log '$(Get-LogName)' for details"
}
} }
$getEnvName = $(Get-SoftwareName) + "_LOGPATH" $getEnvName = $(Get-SoftwareName) + "_LOGPATH"

View File

@ -239,6 +239,8 @@ param (
[Alias("Exif")] [Alias("Exif")]
[switch] $IncludeExif, [switch] $IncludeExif,
[String] $VolumeSize = "-1",
[String] $LogPath = "" [String] $LogPath = ""
@ -250,7 +252,7 @@ Import-Module .\PeterDocs
if ($task -eq "Compress") { if ($task -eq "Compress") {
$actioned = $true $actioned = $true
Compress-Peter -SourceFolder $path -SecretKey $SecretKey -SecretFile $SecretFile -ArchiveFile $archiveFile -ReconcileFile $reconcileFile -RootFolder $rootFolder -FileFilter $fileFilter -LogPath $LogPath -ExcludeHash:$ExcludeHash -IncludeExif:$IncludeExif Compress-Peter -SourceFolder $path -SecretKey $SecretKey -SecretFile $SecretFile -RecipientKey $RecipientKey -ArchiveFile $archiveFile -ReconcileFile $reconcileFile -RootFolder $rootFolder -FileFilter $fileFilter -VolumeSize $VolumeSize -LogPath $LogPath -ExcludeHash:$ExcludeHash -IncludeExif:$IncludeExif
} }
@ -268,7 +270,7 @@ Import-Module .\PeterDocs
if ($task -eq "Expand") { if ($task -eq "Expand") {
$actioned = $true $actioned = $true
Expand-Peter -RestoreFolder $path -SecretKey $secretKey -SecretFile $secretFile -ArchiveFile $ArchiveFile -LogPath $LogPath Expand-Peter -RestoreFolder $path -SecretKey $secretKey -SecretFile $secretFile -RecipientKey $RecipientKey -ArchiveFile $ArchiveFile -LogPath $LogPath
} }
@ -299,6 +301,12 @@ Import-Module .\PeterDocs
{ {
$SecretFile = $ArchiveFileName + ".key" $SecretFile = $ArchiveFileName + ".key"
} }
if (!(Test-Path -Path $SecretFile)) {
Write-Log "Secret file '$SecretFile' not found"
Write-Host "Secret file '$SecretFile' not found" -ForegroundColor Red
Close-Log
return
}
$secret = Unprotect-CmsMessage -To $RecipientKey -Path $SecretFile $secret = Unprotect-CmsMessage -To $RecipientKey -Path $SecretFile
} else { } else {
$secret = $SecretKey $secret = $SecretKey

View File

@ -1,9 +1,10 @@
# PeterDocs - Protect, Transfer, Reconcile Dcouments # PeterDocs - Protect, Transfer, Reconcile Documents
## Summary ## Summary
PeterDocs is for [Protecting](Encryption.md), [Transferring](SendArchive.md) and [Reconciling](Reconcile.md) documents PeterDocs is for [Protecting](Encryption.md), [Transferring](SendArchive.md) and [Reconciling](Reconcile.md) documents
on a remote computer where the computers are isolated or on different networks. on a remote computer where the computers are isolated or on different networks and not accessible via
file network shares.
The PowerShell module is available on [PowerShell Gallery](https://www.powershellgallery.com/packages/PeterDocs) The PowerShell module is available on [PowerShell Gallery](https://www.powershellgallery.com/packages/PeterDocs)
@ -13,14 +14,14 @@ that execute the code are required to have Windows PowerShell installed.
Use the script to create an encrypted archive of the source folder and its contents, then Use the script to create an encrypted archive of the source folder and its contents, then
transfer the archive file to your target, where the content are unpacked using the decryption transfer the archive file to your target, where the content are unpacked using the decryption
key. After archive contents are restored you can execute the reconcile function key. After archive contents are restored you can execute the reconcile function
to veriy that the contents are transferred, unaltered. to verify that the contents are transferred, unaltered.
See [Quick Start](QuickStart.md) if you are ready to start and don't need the details. See [Quick Start](QuickStart.md) if you are ready to start and don't need the details.
If you have access to both source and target folders, then you should consider If you have access to both source and target folders as shared folders or even
using tools such as: on the same computer, then you should consider using tools such as:
* Microsoft ROBOCOPY * Microsoft ROBOCOPY - See [Alternate Uses](./Docs/AlternateUses.md)
* rsync * rsync
Alternatively, you can use backup and restore utilities on the folder, and rely that Alternatively, you can use backup and restore utilities on the folder, and rely that
@ -89,11 +90,11 @@ will need write access on the target storage. A log file is written at execution
to record activity. to record activity.
Your bulk file transfer is encrypted in transit. Note that if you use the Your bulk file transfer is encrypted in transit. Note that if you use the
SecretKey method the ecnrypted contents will only be as secure as the strength SecretKey method the encrypted contents will only be as secure as the strength
of your secret. of your secret.
You can use storage providers such as Dropbox, AWS S3, Google Drive, OneDrive or BackBlaze You can use storage providers such as Dropbox, AWS S3, Google Drive, OneDrive or BackBlaze
and your documents have additonal protection. and your documents have additional protection.
A log file is produced on execution. Repeated executions on the same day A log file is produced on execution. Repeated executions on the same day
will add text content to the same log file. The default log name takes the form: will add text content to the same log file. The default log name takes the form:
@ -104,6 +105,26 @@ via local file NuGet package file if Internet access is limited.
See the [Advanced Usage](Docs/Advanced.md) for more advanced options. See the [Advanced Usage](Docs/Advanced.md) for more advanced options.
## Limitations
### Secure string
The current version does not use secure strings for password protection
within the code. You data is stil protected with encryption.
## Path length
There is a limitation with the PowerShell functions used within PeterDocs
of file paths having to be 260 characters in length or less.
If you have long file paths, the processing will fail. A possible
work around is to use mapped net work drive even on your local sourced
file. The command in PowerShell would be something like:
```powershell
New-PSDrive "X" -PSProvider FileSysytem -Root "$Source"
```
## Further Reading ## Further Reading
[Design](Docs/Design.md) [Design](Docs/Design.md)

View File

@ -0,0 +1,34 @@
param (
[Parameter(Mandatory)]
[String] $Source,
[Parameter(Mandatory)]
[String] $Destination
)
# Note that there is a path limitation for files of 260 characters
# beyond which PeterDocs will fail
# You could also use drive mapping to overcome this
# New-PSDrive "X" -PSProvider FileSysytem -Root "$Source"
$step ="Starting"
Try {
$step ="Creating initial reconcile"
New-PeterReconcile -ReconcileFile .\myrobocopy.csv -SourceFolder $Source
$step ="Running robocopy"
Write-Host "Running robocopy for source '$Source' and destination '$Destination'"
# Change the command line switches to suit
robocopy `"$Source`" "$Destination" /e /copy:DAT /dcopy:DAT /log+:./robocopy.log /r:1000 /w:10
if ($LastExitCode -lt 8) {
Write-Host "Robocopy succeeded"
} else {
Write-Host "Robocopy failed with exit code:" $LastExitCode
throw "Robocopy error"
}
$step ="Running copy reconcile"
Compare-Peter -ReconcileFile .\myrobocopy.csv -RestoreFolder $Destination
# You can modify the code here to add a success email notification
} Catch {
Write-Host "Error: $_"
Write-Error "Processing encountered error at step '$step'"
# You can modify the catch to add a simlpe email notification on errors
}

View File

@ -0,0 +1,6 @@
# Samples
This folder contains sample scripts using ``PeterDocs``
The samples are intended to be self documenting. Please adjust
the code to suit your circumstances.