POWERSHELL: COPY, MOVE, Mirror FILES and folders USING ROBOCOPY AND CSV (version 2.1)
This script is an update to the previous script with a few fixes and a lot of new features. It’s also a lot more complicated, so if you want easy to understand, use the old script.
Testing has been done to ensure it works with Windows XP/Server 2003 to Windows 10/Server 2022.
It will do the following:
- Copy files from SourceFolder to CopyFolderDestination with options for Recursive, Mirror and Mirror with Security.
- Move files from SourceFolder to MoveFolderDestination with an option for Recursive.
- Writes a Log File to a Logs folder (creates it if it doesn’t exist).
- Log file is named the same as the script filename, or the CSV file if specified, with the date and time appended to the end.
- Uses the Description from the CSV to separate Copy/Move log entries.
This script requires a CSV file to read from. This CSV file should have the fields filled out based on the header information listed below. Logging will use the Description field, so make sure that’s filled out!
If specifying the CSV file, it can be named anything you want. If you do not specify a CSV file, it will look for a CSV with the name of the script (without extension) and “_SrcDst.csv” appended.
CSV Filename:
<ScriptName>_SrcDst.csv
CSV Header Layout:
SourceFolder,CopyFolderDestination,CopyFileInclude,CopyFileExclude,CopyFolderExclude,MoveFolderDestination,MoveFileInclude,MoveFileExclude,MoveFolderExclude,Description
Default Settings
# Will use defined defaults within script
Start-RoboCopyandMove.ps1
Default with Recursive Settings
# Will use defaults and add global recursive folder copy/move.
Start-RoboCopyandMove.ps1 -Recursive
Example – Manual Settings
# Using default CSV file with copy and move.
Start-RoboCopyandMove.ps1 -RoboCOPY '/COPY:DAT /NP /NDL /FP /R:1 /W:2' -RoboMOVE '/MOVE /NP /NDL /FP /R:1 /W:2'
# Using default CSV file with folder mirroring.
Start-RoboCopyandMove.ps1 -Mirror
# Using custom CSV file with copy and move.
Start-RoboCopyandMove.ps1 -SrcDstFile 'custom-name.csv' -RoboCOPY '/COPY:DAT /NP /NDL /FP /R:1 /W:2' -RoboMOVE '/MOVE /NP /NDL /FP /R:1 /W:2'
# Using custom CSV file with folder mirroring.
Start-RoboCopyandMove.ps1 -SrcDstFile 'custom-name.csv' -Mirror
Full Script
<#
.SYNOPSIS
POSH wrapper for robocopy.
.DESCRIPTION
Will read from a CSV file for Source, [Copy/Move]Destination and Description.
CSV Filename: <ScriptName>_SrcDst.csv
CSV Header Layout: SourceFolder,CopyFolderDestination,CopyFileInclude,CopyFileExclude,MoveFolderDestination,MoveFileInclude,MoveFileExclude,Description
SourceFolder: Root folder to copy/move from
CopyFolderDestination: Destination root folder. If a single file is specified, it will copy to this folder.
CopyFileInclude: Specify file(s) you want to copy by filename. Do not use file path.
Filenames with spaces should be single quoted. Multiple files are space separated.
If specified, it will prevent copying all files/folders and only copy the specified file(s).
CopyFileExclude: Specify file(s) you want to exclude from copying.
Filenames with spaces should be single quoted. Multiple files are space separated.
If specified, it will prevent copying all files with the specified file name(s).
CopyFolderExclude: Specify folder(s) you want to exclude from copying. Invoked only when "recursive" is used.
Folders with spaces should be single quoted. Multiple folders are space separated.
If specified, it will prevent copying the folder(s).
MoveFolderDestination: Destination root folder. If a single file is specified, it will move to this folder.
MoveFileInclude: Specify file(s) you want to move by filename. Do not use file path.
Filenames with spaces should be single quoted. Multiple files are space separated.
If specified, it will prevent copying all files/folders and only move the specified file(s).
MoveFileExclude: Specify file(s) you want to exclude from moving.
Filenames with spaces should be single quoted. Multiple files are space separated.
If specified, it will prevent moving all files with the specified file name(s).
MoveFolderExclude: Specify folder(s) you want to exclude from moving. Invoked only when "recursive" is used.
Folders with spaces should be single quoted. Multiple folders are space separated.
If specified, it will prevent moving the folder(s).
Description: Description of job being run. This is useful when logging to differentiate multiple jobs.
.PARAMETER RobocopyPath
The path to the robocopy binary (exe)
.PARAMETER SrcDstFile
The CSV file containing the required file/folder information for the script.
.PARAMETER RoboCOPY
Specify the robocopy parameters you wish to use to copy files.
.PARAMETER RoboMOVE
Specify the robocopy parameters you wish to use to move files.
.PARAMETER LogNameScript
Creates a logfile name with the name of the script file and writes it to the Logs folder for the robocopy results.
If specified, will use name of script as logfile name.
.PARAMETER LogFileName
The path to the log file for the robocopy results.
.PARAMETER Recursive
Use this parameter if you wish to copy/move all sub folders.
.PARAMETER Mirror
Use this parameter if you wish to mirror the contents from the source to the COPY or MOVE destiation folder. Exclusions will be honored, all other parameters will be ignored.
.NOTES
Created by: Derek Bannard
Modified: 2021-05-07
Version: 2.1
.EXAMPLE
Default Settings
Will use defined defaults within script.
PS> Start-RoboCopyandMove.ps1
.EXAMPLE
Default with Recursive Settings
Will use defaults and add global recursive folder copy/move.
PS> Start-RoboCopyandMove.ps1 -Recursive
.EXAMPLE
Manual Settings
Using default CSV file with copy and move.
PS> Start-RoboCopyandMove.ps1 -RoboCOPY '/COPY:DAT /NP /NDL /FP /R:1 /W:2' -RoboMOVE '/MOVE /NP /NDL /FP /R:1 /W:2'
Using default CSV file with folder mirroring.
PS> Start-RoboCopyandMove.ps1 -Mirror
Using custom CSV file with copy and move.
PS> Start-RoboCopyandMove.ps1 -SrcDstFile 'custom-name.csv' -RoboCOPY '/COPY:DAT /NP /NDL /FP /R:1 /W:2' -RoboMOVE '/MOVE /NP /NDL /FP /R:1 /W:2'
Using custom CSV file with folder mirroring.
PS> Start-RoboCopyandMove.ps1 -SrcDstFile 'custom-name.csv' -Mirror
#>
[CmdletBinding()]
Param(
[string]$RoboCopyPath,
[string]$SrcDstFile,
[string]$LogFileName,
[string]$IncludeFile,
[string]$RoboCOPY,
[string]$RoboMOVE,
[switch]$LogNameScript,
[switch]$Recursive,
[switch]$Mirror,
[switch]$MirrorSec
)
Begin
{
# Set Base Variables
If (!($RoboCopyPath)) {[string]$RoboCopyPath = (Get-Command robocopy.exe -TotalCount 1).Path}
If (!($PSScriptRoot)) {[String]$PSScriptRoot = (Split-Path $MyInvocation.MyCommand.Path)}
If (!($IncludeFile)) {[string]$IncludeFile = "*.*"}
If (!($RoboCOPY)) {[string]$ParamCOPY = "/COPY:DAT /NP /NDL /FP /R:1 /W:2"}
If (!($RoboMOVE)) {[string]$ParamMOVE = "/MOVE /NP /NDL /FP /R:1 /W:2"}
[string]$ParamRecursive = "/E"
[string]$ParamMirror = "/MIR /R:1 /W:2"
[string]$ParamMirrorSec = "/MIR /SEC /SECFIX /R:1 /W:2"
# Turn on verbose
$VerbosePreference = 'Continue'
#Check if Logs folder exists
If (!(Test-Path "$PSScriptRoot\Logs")) {
Write-Verbose "Creating Logs Folder..."
New-Item -ItemType Directory -Force -Path "$PSScriptRoot\Logs" | Out-Null
}
#Check if Source file exists and setting LogFile path
If (Test-Path "$SrcDstFile") {[string]$SrcDstFile = "$SrcDstFile"}
ElseIf (Test-Path "$PSScriptRoot\$SrcDstFile") {[string]$SrcDstFile = "$PSScriptRoot\$SrcDstFile"}
ElseIf (Test-Path "$((Get-Location).Path)\$SrcDstFile") {[string]$SrcDstFile = "$((Get-Location).Path)\$SrcDstFile"}
Else {
Write-Verbose "Source file not found, exiting script..."
Exit
}
If (!($SrcDstFile)) {[string]$SrcDstFile = "$PSScriptRoot\$(([system.io.fileinfo]$MyInvocation.MyCommand.Definition).BaseName)_SrcDst.csv"}
If (($SrcDstFile) -and (!($LogNameScript.IsPresent)) -and (!($LogFileName))) {[string]$LogFileName = "$PSScriptRoot\Logs\$((Get-Item $SrcDstFile).BaseName)-log_$(Get-Date -Format 'yyyyMMdd').txt"} ElseIf ((!($LogFileName)) -and ($LogNameScript.IsPresent)) {[string]$LogFileName = "$PSScriptRoot\Logs\$(([system.io.fileinfo]$MyInvocation.MyCommand.Definition).BaseName)-log_$(Get-Date -Format 'yyyyMMdd').txt"}
$RoboLogging = "/LOG+:`"$($LogFileName)`""
# Check if Robocopy.exe exists
If (!(Test-Path $RoboCopyPath)) {
Write-Verbose "Robocopy.exe not found, exiting script."
Exit
}
# Begin Logging
Add-Content -Value "**** Job Started: $(([system.io.fileinfo]$MyInvocation.MyCommand.Definition).BaseName) Copy and Move files script at $(Get-Date -Format 'yyyyMMdd_HHmm') ****" -Path $LogFileName
}
Process
{
# Building the robocopy command line
Import-CSV "$($SrcDstFile)" | foreach {
If ((($Mirror.IsPresent) -or ($MirrorSec.IsPresent)) -and ((($_.CopyFolderDestination) -and (!($_.MoveFolderDestination))) -or ((!($_.CopyFolderDestination)) -and ($_.MoveFolderDestination)))) {
Write-Verbose "Beginning Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Beginning Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
$RoboMirrorExecute = @()
If (($MirrorSec.IsPresent) -and (!($Mirror.IsPresent))) {$ParamMirrorParameters = $ParamMirrorSec}
Else {$ParamMirrorParameters = $ParamMirror}
If ($_.CopyFolderExclude) {
$ParamMirrorParameters = $ParamMirrorParameters + " /XD `"$($_.CopyFolderExclude)`""
}
If ($_.MoveFolderExclude) {
$ParamMirrorParameters = $ParamMirrorParameters + " /XD `"$($_.MoveFolderExclude)`""
}
If (($_.CopyFolderDestination) -and (!($_.MoveFolderDestination))) {
$RoboMirrorExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.CopyFolderDestination)`" $($ParamMirrorParameters)"
}
ElseIf ((!($_.CopyFolderDestination)) -and ($_.MoveFolderDestination)) {
$RoboMirrorExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.MoveFolderDestination)`" $($ParamMirrorParameters)"
}
If ($RoboMirrorExecute) {
Invoke-Expression "& $RoboMirrorExecute $($RoboLogging)"
Write-Verbose "Completed Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Completed Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
ElseIf (!($RoboMirrorExecute)) {
Write-Verbose "Error, required parameters not present to Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Error, required parameters not present to Mirror for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
}
If ((($_.CopyFolderDestination) -and ($_.SourceFolder)) -and ((!($Mirror.IsPresent)) -or (!($MirrorSec.IsPresent)))) {
Write-Verbose "Beginning COPY for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content -Path $LogFileName -Value "Beginning COPY for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Beginning COPY for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
$RoboCopyExecute = @()
$ParamCopyParameters = $ParamCOPY
If ($Recursive.IsPresent) {
$ParamCopyParameters = "$ParamCopyParameters $ParamRecursive"
}
Else {
$ParamCopyParameters = "$ParamCopyParameters"
}
If ($_.CopyFileInclude) {
$RoboCopyExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.CopyFolderDestination)`" `"$($_.CopyFileInclude)`" $($ParamCopyParameters)"
}
Else {
$RoboCopyExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.CopyFolderDestination)`" $($IncludeFile) $($ParamCopyParameters)"
}
If ($_.CopyFileExclude) {
$RoboCopyExecute = $RoboCopyExecute + " /XF `"$($_.CopyFileExclude)`""
}
If ($_.CopyFolderExclude) {
$RoboCopyExecute = $RoboCopyExecute + " /XD `"$($_.CopyFolderExclude)`""
}
If ($RoboCopyExecute) {
Invoke-Expression "& $RoboCopyExecute $($RoboLogging)"
Write-Verbose "Completed COPY for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Completed COPY for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
Else {
Write-Verbose "Error, required parameters not present to Copy for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Error, required parameters not present to Copy for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
}
If ((($_.MoveFolderDestination) -and ($_.SourceFolder)) -and ((!($Mirror.IsPresent)) -or (!($MirrorSec.IsPresent)))) {
Write-Verbose "Beginning MOVE for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Beginning MOVE for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
$RoboMoveExecute = @()
$ParamMoveParameters = $ParamMOVE
If ($Recursive.IsPresent) {
$ParamMoveParameters = "$ParamMoveParameters $ParamRecursive"
}
Else {
$ParamMoveParameters = "$ParamMoveParameters"
}
If ($_.MoveFileInclude) {
$RoboMoveExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.MoveFolderDestination)`" `"$($_.MoveFileInclude)`" $($ParamMoveParameters)"
}
Else {
$RoboMoveExecute = "`"$($RoboCopyPath)`" `"$($_.SourceFolder)`" `"$($_.MoveFolderDestination)`" $($IncludeFile) $($ParamMoveParameters)"
}
If ($_.MoveFileExclude) {
$RoboMoveExecute = $RoboMoveExecute + " /XF `"$($_.MoveFileExclude)`""
}
If ($_.MoveFolderExclude) {
$RoboMoveExecute = $RoboMoveExecute + " /XD `"$($_.MoveFolderExclude)`""
}
If ($RoboMoveExecute) {
Invoke-Expression "& $RoboMoveExecute $($RoboLogging)"
Write-Verbose "Completed MOVE for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Completed MOVE for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
Else {
Write-Verbose "Error, required parameters not present to Move for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')"
Add-Content "Error, required parameters not present to Move for: $($_.Description) at $(Get-Date -Format 'yyyyMMdd_HHmmss')" -Path $LogFileName
}
}
}
}
End
{
Add-Content -Value "**** Job Ended: $(([system.io.fileinfo]$MyInvocation.MyCommand.Definition).BaseName) at $(Get-Date -Format 'yyyyMMdd_HHmm')" -Path $LogFileName
}