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:

  1. Copy files from SourceFolder to CopyFolderDestination with options for Recursive, Mirror and Mirror with Security.
  2. Move files from SourceFolder to MoveFolderDestination with an option for Recursive.
  3. Writes a Log File to a Logs folder (creates it if it doesn’t exist).
  4. 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.
  5. 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
}

Loading