added the main script
This commit is contained in:
parent
4d630f52d2
commit
6bb7b7a6de
465
copyfiles.ps1
Executable file
465
copyfiles.ps1
Executable file
@ -0,0 +1,465 @@
|
||||
#===============================================================================
|
||||
#
|
||||
# DIRECTORY:
|
||||
# (variable)
|
||||
#
|
||||
# FILE:
|
||||
# copyfiles.ps1 (or copyfiles.exe)
|
||||
#
|
||||
# USAGE:
|
||||
# Step 1 (cmd.exe):
|
||||
# C:\>powershell -ExecutionPolicy Unrestricted
|
||||
# Step 2:
|
||||
# PS C:\> E:\copyfiles.ps1 <Sourcepath> <Destinationpath> [Options]
|
||||
# or
|
||||
# PS C:\> E:\copyfiles.ps1 -s[ourcepath] <Path> -d[estinationpath] <Path> [Options]
|
||||
#
|
||||
# OPTIONS:
|
||||
# -l[ogfilepath] <Path> : relative or absolute path to the logfile
|
||||
# -i[nfofilepath] <Path> : relative or absolute path to the infofile
|
||||
# -v[erify] : turn on output of success
|
||||
# -q[uiet] : turn off all output
|
||||
# -h[elp] : show a short help
|
||||
#
|
||||
# EXIT STATES:
|
||||
# True (0) = success
|
||||
# False (1) = sourcepath is empty
|
||||
# False (2) = sourcepath has to exist
|
||||
# False (3) = destinationpath is empty
|
||||
# False (4) = cannot create destinationpath there
|
||||
# False (5) = destinationpath already exist
|
||||
# False (6) = logfilepath and infofilepath are equal
|
||||
# False (7) = logfilepath already exist
|
||||
# False (8) = cannot create logfile there
|
||||
# False (9) = unsupported extension for logfile
|
||||
# False (10) = infofilepath already exist
|
||||
# False (11) = cannot create infofile there
|
||||
# False (12) = unsupported extension for infofile
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# Copy a file or directory (incl. files) from a given sourcepath
|
||||
# to a given destinationpath and verify that the content of the
|
||||
# file(s) has not changed.
|
||||
# Optionally output success in addition to fails.
|
||||
# Optionally write metadata to an infofile and/or each step
|
||||
# into a logfile.
|
||||
# Optionally suppress all output.
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# Powershell 4.0 (first Version with Get-FileHash commandlet)
|
||||
#
|
||||
# BUGS:
|
||||
# ---
|
||||
#
|
||||
# NOTES:
|
||||
# Syntax checked with PSScriptAnalyzer:
|
||||
# PS C:\> Invoke-ScriptAnalyzer E:\copyfiles.ps1
|
||||
#
|
||||
# Tested on
|
||||
# - Microsoft Windows 10 (1703) Pro (x64)
|
||||
# with
|
||||
# - PowerShell Version 5.1.15063.608 ($PSVersionTable.PSVersion)
|
||||
#
|
||||
# AUTHOR:
|
||||
# Patrick Neumann, patrick@neumannsland.de
|
||||
#
|
||||
# COMPANY:
|
||||
# (privately)
|
||||
#
|
||||
# VERSION:
|
||||
# 0.9.0 (beta)
|
||||
#
|
||||
# LINK TO THE MOST CURRENT VERSION:
|
||||
# (Sorry, I bet, I'm not allowed to publish it over GitHub!)
|
||||
#
|
||||
# CREATED:
|
||||
# 2017-09-29
|
||||
#
|
||||
# COPYRIGHT (C):
|
||||
# 2017 - Patrick Neumann
|
||||
#
|
||||
# LICENSE:
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# WARRANTY:
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# TODO:
|
||||
# - More testing (Powershell on macOS and/or ArchLinux)
|
||||
#
|
||||
# HISTORY:
|
||||
# 0.9.0 - Patrick Neumann - Initial (for the peer reviewer eyes only) release
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
#=== CONFIGURATION (user) ======================================================
|
||||
# Has to be at the top of the code (excl. comments)!
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Position=1)] # "Mandatory=$True" vs. easy -help
|
||||
[string]$sourcepath,
|
||||
[Parameter(Position=2)] # "Mandatory=$True" vs. easy -help
|
||||
[string]$destinationpath,
|
||||
[string]$logfilepath, # like log= support of dc3dd
|
||||
[string]$infofilepath, # like hlog= support of dc3dd
|
||||
[switch]$verify, # like --check support in GNU md5sum/sha1sum
|
||||
[switch]$quiet,
|
||||
[switch]$help
|
||||
)
|
||||
|
||||
#=== CONFIGURATION (dynamic) ===================================================
|
||||
# Date of start
|
||||
Get-Date -Format d | New-Variable -Name CURDATE -Option constant
|
||||
# Time of start
|
||||
Get-Date -Format T | New-Variable -Name CURTIME -Option constant
|
||||
# Timezone of start (as difference from UTC because CEST is not supported)
|
||||
Get-Date -UFormat "UTC%Z" | New-Variable -Name CURTZ -Option constant
|
||||
|
||||
#=== CONFIGURATION (static) ====================================================
|
||||
# Filename of the script
|
||||
New-Variable -Name MYNAME -Value $MyInvocation.MyCommand.Name -Option constant
|
||||
# Version of the script
|
||||
New-Variable -Name VERSION -Value "0.9.0" -Option constant
|
||||
# Creation date of the script
|
||||
New-Variable -Name CREATED -Value "2017-09-29" -Option constant
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# First output.
|
||||
#-------------------------------------------------------------------------------
|
||||
# nmap like output:
|
||||
# Starting Nmap 7.40 ( https://nmap.org ) at 2017-09-26 21:13 CEST
|
||||
$scriptinfo = "Starting $MYNAME $VERSION ($CREATED) at $CURDATE $CURTIME $CURTZ"
|
||||
Write-Output "$scriptinfo`n"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Help.
|
||||
#-------------------------------------------------------------------------------
|
||||
$helpstring = @"
|
||||
NAME
|
||||
copyfiles.ps1 (or copyfiles.exe)
|
||||
|
||||
SYNOPSIS
|
||||
Copy a file or directory from sourcepath to destinationpath.
|
||||
|
||||
SYNTAX
|
||||
PS C:\> E:\copyfiles.ps1 <Sourcepath> <Destinationpath> [Options]
|
||||
or
|
||||
PS C:\> E:\copyfiles.ps1 -s[ourcepath] <Path> -d[estinationpath] <Path> [Options]
|
||||
|
||||
DESCRIPTION
|
||||
Copy a file or directory (incl. files) from a given sourcepath
|
||||
to a given destinationpath and verify that the content of the
|
||||
file(s) has not changed.
|
||||
|
||||
Optionally output success in addition to fails.
|
||||
|
||||
Optionally write metadata to an infofile and/or each step
|
||||
into a logfile.
|
||||
|
||||
Optionally suppress all output.
|
||||
|
||||
REQUIREMENTS
|
||||
Powershell 4.0 (first Version with Get-FileHash commandlet).
|
||||
|
||||
You have to open the Powershell with unrestricted execution policy or
|
||||
use "a" .exe version!
|
||||
|
||||
PARAMETERS
|
||||
-l[ogfilepath] <Path>
|
||||
Relative or absolute path to the logfile.
|
||||
|
||||
-i[nfofilepath] <Path>
|
||||
Relative or absolute path to the infofile.
|
||||
|
||||
-v[erify]
|
||||
Turn on output of success (in addition to fails)
|
||||
|
||||
-q[uiet]
|
||||
Turn off all output.
|
||||
|
||||
-h[elp]
|
||||
Show this help.
|
||||
|
||||
"@
|
||||
|
||||
If($help){
|
||||
Write-Output $helpstring
|
||||
Exit $True
|
||||
}
|
||||
|
||||
#=== FUNCTION ==================================================================
|
||||
# NAME: Write-Error-and-Exit-PN
|
||||
# DESCRIPTION: Write red text to the console and exit with individual exitcode.
|
||||
# PARAMETER 1: string
|
||||
# PARAMETER 2: integer
|
||||
#===============================================================================
|
||||
Function Write-Error-and-Exit-PN
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True, Position=1)]
|
||||
[string]$message,
|
||||
[Parameter(Mandatory=$True, Position=2)]
|
||||
[string]$exitcode
|
||||
)
|
||||
# If you want color you have to use Write-Host!
|
||||
# (But be aware: Write-Host will not be available on "every host"!)
|
||||
Write-Host " ERROR: $message ... EXIT!`n" -ForegroundColor Red
|
||||
Write-Output $helpstring
|
||||
Exit $exitcode
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks for sourcepath.
|
||||
#-------------------------------------------------------------------------------
|
||||
if(-Not $sourcepath){
|
||||
Write-Error-and-Exit-PN "sourcepath is empty" 1
|
||||
}
|
||||
|
||||
if(-Not [System.IO.Path]::IsPathRooted($sourcepath)){
|
||||
# convert relative into absolut path
|
||||
$sourcepath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($sourcepath)
|
||||
}
|
||||
|
||||
if(-Not (Test-Path $sourcepath)){
|
||||
Write-Error-and-Exit-PN "sourcepath has to exist" 2
|
||||
}
|
||||
|
||||
#$sourcepathitem = Get-Item $sourcepath
|
||||
## if a file, just copy it
|
||||
#$sourcepathitem -is [System.IO.FileInfo]
|
||||
## after copy a symlink is a file!
|
||||
## if a folder, create it and copy the childitems into it
|
||||
#$sourcepathitem -is [System.IO.DirectoryInfo]
|
||||
## or only copy content to the destinationpath (tailing backslash)
|
||||
#$sourcepath.EndsWith([IO.Path]::DirectorySeparatorChar)
|
||||
## other items are not supported
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks for destinationpath.
|
||||
#-------------------------------------------------------------------------------
|
||||
if(-Not $destinationpath){
|
||||
Write-Error-and-Exit-PN "destinationpath is empty" 3
|
||||
}
|
||||
|
||||
if(-Not [System.IO.Path]::IsPathRooted($destinationpath)){
|
||||
# convert relative into absolut path
|
||||
$destinationpath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($destinationpath)
|
||||
}
|
||||
|
||||
if(-Not (Test-Path (Split-Path $destinationpath))){
|
||||
Write-Error-and-Exit-PN "cannot create destinationpath there" 4
|
||||
}
|
||||
|
||||
# It is not a good idea to use an existing destination because of the
|
||||
# possiblity of mixing traces from different cases!
|
||||
If(Test-Path $destinationpath){
|
||||
Write-Error-and-Exit-PN "destinationpath already exist" 5
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Like dc3dd you have to give "log" or "hlog" to activate "log" or "info".
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks for logfilepath != infofilepath.
|
||||
#-------------------------------------------------------------------------------
|
||||
if($logfilepath -and $infofilepath){
|
||||
if($logfilepath -ceq $infofilepath){
|
||||
Write-Error-and-Exit-PN "logfilepath and infofilepath are equal" 6
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks for logfilepath.
|
||||
#-------------------------------------------------------------------------------
|
||||
# TODO:
|
||||
# Set "logfile.txt" in the destinationpath if a path is missing after "-l".
|
||||
#-------------------------------------------------------------------------------
|
||||
$logfileextensions = @(".log", ".txt")
|
||||
if($logfilepath){
|
||||
# It is not a good idea to overwrite an existing logfile!
|
||||
if(Test-Path $logfilepath){
|
||||
Write-Error-and-Exit-PN "logfilepath already exist" 7
|
||||
}
|
||||
|
||||
if(-Not [System.IO.Path]::IsPathRooted($logfilepath)){
|
||||
# convert relative into absolut path
|
||||
$logfilepath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($logfilepath)
|
||||
}
|
||||
|
||||
if(-Not (Test-Path (Split-Path $logfilepath))){
|
||||
Write-Error-and-Exit-PN "cannot create logfile there" 8
|
||||
}
|
||||
|
||||
# Check extension for logfile (.log or .txt)
|
||||
if($logfileextensions -notcontains [System.IO.Path]::GetExtension($logfilepath)){
|
||||
Write-Error-and-Exit-PN "unsupported extension for logfile" 9
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Checks for infofilepath.
|
||||
#-------------------------------------------------------------------------------
|
||||
# TODO:
|
||||
# Set "infofile.csv" in the destinationpath if a path is missing after "-i".
|
||||
#-------------------------------------------------------------------------------
|
||||
$infofileextensions = @(".csv", ".txt")
|
||||
if($infofilepath){
|
||||
# It is not a good idea to overwrite an existing infofile!
|
||||
if(Test-Path $infofilepath){
|
||||
Write-Error-and-Exit-PN "infofilepath already exist" 10
|
||||
}
|
||||
|
||||
if(-Not [System.IO.Path]::IsPathRooted($infofilepath)){
|
||||
# convert relative into absolut path
|
||||
$infofilepath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($infofilepath)
|
||||
}
|
||||
|
||||
if(-Not (Test-Path (Split-Path $infofilepath))){
|
||||
Write-Error-and-Exit-PN "cannot create infofile there" 11
|
||||
}
|
||||
|
||||
# Check extension for infofile (.csv or .txt)
|
||||
if($infofileextensions -notcontains [System.IO.Path]::GetExtension($infofilepath)){
|
||||
Write-Error-and-Exit-PN "unsupported extension for infofile" 12
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Start write to logfile if "-l" is given.
|
||||
#-------------------------------------------------------------------------------
|
||||
if($logfilepath){
|
||||
"$scriptinfo`n" | Out-File $logfilepath
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Start write to infofile if "-i" is given.
|
||||
#-------------------------------------------------------------------------------
|
||||
if($infofilepath){
|
||||
"# $scriptinfo" | Out-File $infofilepath
|
||||
$csvtablehead = '"FullName"'
|
||||
$csvtablehead += ',"Length"'
|
||||
$csvtablehead += ',"Mode"'
|
||||
$csvtablehead += ',"Attributes"'
|
||||
$csvtablehead += ',"SymbolicLinkTarget"'
|
||||
$csvtablehead += ',"LastWriteTime"'
|
||||
$csvtablehead += ',"LastAccessTime"'
|
||||
$csvtablehead += ',"CreationTime"'
|
||||
$csvtablehead += ',"Owner"'
|
||||
$csvtablehead += ',"Group"'
|
||||
$csvtablehead += ',"MD5"'
|
||||
$csvtablehead += ',"SHA1"'
|
||||
$csvtablehead | Out-File $infofilepath -Append
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# We want as much as possible (and skip only devices and don't want errors).
|
||||
#-------------------------------------------------------------------------------
|
||||
$sourceroot = Get-Item -Path $sourcepath 2>$null
|
||||
if($sourceroot -Is [System.IO.FileInfo]){
|
||||
# copy only one file
|
||||
}
|
||||
elseif($sourceroot -Is [System.IO.DirectoryInfo]){
|
||||
$sourcefiles = Get-ChildItem -Path $sourcepath `
|
||||
-Recurse `
|
||||
-Attributes ReadOnly,
|
||||
Hidden,
|
||||
System,
|
||||
Directory,
|
||||
Archive,
|
||||
Normal,
|
||||
Temporary,
|
||||
SparseFile,
|
||||
ReparsePoint,
|
||||
Compressed,
|
||||
Offline,
|
||||
NotContentIndexed,
|
||||
Encrypted,
|
||||
IntegrityStream,
|
||||
NoScrubData 2>$null
|
||||
}
|
||||
else{
|
||||
# type of source not supported
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Main.
|
||||
#-------------------------------------------------------------------------------
|
||||
New-Item $destinationpath -ItemType "Directory" | Out-Null
|
||||
if($logfilepath){
|
||||
"Destination path successfully created.`n" | Out-File $logfilepath -Append
|
||||
}
|
||||
|
||||
ForEach($sourcefile in $sourcefiles){
|
||||
# md5 and sha1 secure like sha256...
|
||||
# Tee-Object w/o process substitution...
|
||||
if($sourcefile -Is [System.IO.FileInfo]){
|
||||
$md5ofsource = (Get-FileHash -Path $sourcefile.FullName -Algorithm MD5).hash
|
||||
$sha1ofsource = (Get-FileHash -Path $sourcefile.FullName -Algorithm SHA1).hash
|
||||
}
|
||||
|
||||
if($infofilepath){
|
||||
$line = new-object PSObject
|
||||
$line | add-member -membertype NoteProperty -name "FullName" -value $sourcefile.FullName
|
||||
$line | add-member -membertype NoteProperty -name "Mode" -value $sourcefile.Mode
|
||||
$line | add-member -membertype NoteProperty -name "Attributes" -value $sourcefile.Attributes
|
||||
if($sourcefile.LinkType -eq "SymbolicLink"){
|
||||
$line | add-member -membertype NoteProperty -name "SymbolicLinkTarget" -value $sourcefile.Target
|
||||
}
|
||||
$line | add-member -membertype NoteProperty -name "LastWriteTime" -value $sourcefile.LastWriteTime
|
||||
$line | add-member -membertype NoteProperty -name "LastAccessTime" -value $sourcefile.LastAccessTime
|
||||
$line | add-member -membertype NoteProperty -name "CreationTime" -value $sourcefile.CreationTime
|
||||
$line | add-member -membertype NoteProperty -name "Owner" -value (Get-Acl $sourcefile.FullName).Owner
|
||||
$line | add-member -membertype NoteProperty -name "Group" -value (Get-Acl $sourcefile.FullName).Group
|
||||
if($sourcefile -Is [System.IO.FileInfo]){
|
||||
$line | add-member -membertype NoteProperty -name "Length" -value $sourcefile.Length
|
||||
$line | add-member -membertype NoteProperty -name "MD5" -value $md5ofsource
|
||||
$line | add-member -membertype NoteProperty -name "SHA1" -value $sha1ofsource
|
||||
}
|
||||
$line | Export-Csv $infofilepath -Delimiter "," -Append -Encoding UTF8 -NoTypeInformation -Force
|
||||
$line = $null
|
||||
}
|
||||
|
||||
# os independent path directory separator
|
||||
# and
|
||||
# -Replace = RegExp und .Replace() = Strings
|
||||
$destinationfile = [System.IO.FileInfo](($destinationpath, [IO.Path]::DirectorySeparatorChar, $sourceroot.Name, [IO.Path]::DirectorySeparatorChar, $sourcefile.FullName.Replace($sourcepath, "")) -join "")
|
||||
# Verify (= Verbose) und Quiet funktioniert so nicht wirklich einfach!?
|
||||
# Erst msg bauen und dann nix, ausgabe oder in datei mit if!?
|
||||
Write-Host "$($destinationfile.FullName): " -NoNewline
|
||||
# Am besten gleich zusammen mit:
|
||||
# Logfile support: "Out-File -Append"
|
||||
Copy-Item $sourcefile.FullName $destinationfile.FullName | Out-Null
|
||||
if($destinationfile.Exists){
|
||||
#$destinationfile.Mode = $sourcefile.Mode # is ReadOnly :-(
|
||||
$destinationfile.Attributes = $sourcefile.Attributes
|
||||
$destinationfile.LastWriteTime = $sourcefile.LastWriteTime
|
||||
$destinationfile.LastAccessTime = $sourcefile.LastAccessTime
|
||||
$destinationfile.CreationTime = $sourcefile.CreationTime
|
||||
Set-Acl $destinationfile.FullName -AclObject (Get-Acl $sourcefile.FullName)
|
||||
if($destinationfile -Is [System.IO.FileInfo]){
|
||||
$md5ofdestination = (Get-FileHash -Path $destinationfile.FullName -Algorithm MD5).hash
|
||||
$sha1ofdestination = (Get-FileHash -Path $destinationfile.FullName -Algorithm SHA1).hash
|
||||
if(($md5ofdestination -eq $md5ofsource) -and ($sha1ofdestination -eq $sha1ofsource)){
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
} else{
|
||||
Write-Host "Failed (hash)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
if($destinationfile -Is [System.IO.DirectoryInfo]){
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
} else{
|
||||
Write-Host "Failed (copy)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
exit $True
|
Loading…
x
Reference in New Issue
Block a user