I wrote Windows PowerShell Cmdlet (Advanced Function) to work with Robocopy.
- It is possible to use ComputerName (invoke all operations) or Session (use established session) to copy items on a remote computer.
- Results are not displayed as text but outputted as object. Some of the properties:
- Directories / Files / Bytes Total
- Directories / Files / Bytes Copied
- Directories / Files / Bytes Skipped
- Directories / Files / Bytes Mismatch
- Directories / Files / Bytes Failed
- Directories / Files / Bytes Extras
- Similar behavior as Copy-Item. It is possible to specify Container parameter to copy whole directory or its content.
- It is possible to copy files and not only whole directories.
- It is possible to copy multiple files and directories.
- It is possible to pipe System.IO.DirectoryInfo (directory object) or System.IO.FileInfo (file object). That is mean that is possible to pipe for example output from Get-Item or Get-ChildItem.
- Error handling that ensure that any errors (filed copies) are displayed as error.
Examples
Copy whole directory to destination – Similar to Copy-Item
Copy-RvItemRobocopy `
-Path 'C:\Temp\Source' `
-Destination 'C:\Temp\Target' `
-LogDirectoryPath 'C:\Temp'
Same as previous with usage of pipeline
Get-ChildItem -Path 'C:\Temp' -Filter S* -Directory |
Copy-RvItemRobocopy `
-Destination 'C:\Temp\Target' `
-LogDirectoryPath 'C:\Temp'
Copy content of a directory – Same behaviour as Robocopy
Copy-RvItemRobocopy `
-Path 'C:\Temp\Source' `
-Destination 'C:\Temp\Target' `
-Container:$false `
-LogDirectoryPath 'C:\Temp'
Same as previous with usage of pipeline
Get-ChildItem -Path 'C:\Temp' -Filter S* -Directory |
Copy-RvItemRobocopy `
-Destination 'C:\Temp\Target' `
-Container:$true `
-LogDirectoryPath 'C:\Temp'
Copy multiple files
Copy-RvItemRobocopy `
-Path 'C:\Temp\Source\1.txt',
'C:\Temp\Source\2.txt',
'C:\Temp\Source\3.txt' `
-Destination 'C:\Temp\Target' `
-LogDirectoryPath 'C:\Temp'
Copy multiple files using pipeline
Get-ChildItem -Path 'C:\Temp\Source' -Filter *.txt |
Copy-RvItemRobocopy `
-Destination 'C:\Temp\Target' `
-LogDirectoryPath 'C:\Temp'
Code
Function Copy-RvItemRobocopy
{
<#
.SYNOPSIS
Copy files and directories using Robocopy.
.DESCRIPTION
Developer
Developer: Rudolf Vesely, http://rudolfvesely.com/
Copyright (c) Rudolf Vesely. All rights reserved
License: Free for private use only
"RV" are initials of the developer's name Rudolf Vesely and distingue names of Rudolf Vesely's cmdlets from the other cmdlets.
Description
Copy files and directories using Robocopy.
Similar behaviour as Copy-Item -Recurse:$true (always recursive copy of directories) -Container:$true (by default True but could be defined)
# Copy whole directory (not just content) - Create D:\Temp\Target\Source with all child files and directories from D:\Temp\Source
# Copy-Item -Path D:\Temp\Source -Destination D:\Temp\Target -Recurse:$true
# Same as previous
# Copy-Item -Path D:\Temp\Source -Destination D:\Temp\Target -Recurse:$true -Container:$true
# Copy content of the directory - Create D:\Temp\Target with all child files and directories from D:\Temp\Source
# Copy-Item -Path D:\Temp\Source -Destination D:\Temp\Target -Recurse:$true -Container:$false
Requirements
Developed and tested using PowerShell 4.0.
.PARAMETER Path
Source path.
.PARAMETER Destination
Path of the destination directory.
.PARAMETER Container
Preserves container objects during the copy operation.
Possibilities
$true
D:\Temp\Source -> D:\Temp\Target = D:\Temp\Target\Source
$false
D:\Temp\Source -> D:\Temp\Target = D:\Temp\Target
.PARAMETER ExcludedFile
List of excluded files.
It is possible to pass full path (C:\Temp\myfile.bin), file name (myfile.bin) or wildcard (*.bin).
.PARAMETER ExcludedFile
List of excluded directories. It is possible to pass full path (C:\Temp\MyDir) or directory name (MyDir).
.PARAMETER Process
Copy or Mirror
.PARAMETER Information
Alternate data streams that should be copied (only date and times of creation and modifications or everything including attibutes and ACLs).
.PARAMETER Configuration
List (string array) of specific configuration options.
Possibilities
VolumeRoot
Exclude directories that should not be copied: $RECYCLE.BIN and System Volume Information
.PARAMETER LogPath
Full path of robocopy log that will be created.
.PARAMETER LogDirectoryPath
Path of a directory where Robocpy log will be created.
.PARAMETER LogFileName
File name for Robocopy. LogDirectoryPath is specified and file name is not specified then file name will be generated.
.PARAMETER ComputerName
Invoke all operations on a remote computer.
.PARAMETER ComputerName
Do all operations on a remote computer and use already established session.
.EXAMPLE
'Copy whole directory to destination - Similar to Copy-Item'
Copy-RvItemRobocopy `
-Path 'D:\Temp\Source' `
-Destination 'D:\Temp\Target' `
-LogDirectoryPath 'D:\Temp' -Debug
.EXAMPLE
'Same as previous with usage of pipeline'
Get-ChildItem -Path 'D:\Temp' -Filter S* -Directory |
Copy-RvItemRobocopy `
-Destination 'D:\Temp\Target' `
-LogDirectoryPath 'D:\Temp' -Debug
.EXAMPLE
'Copy content of a directory - Same behaviour as Robocopy'
Copy-RvItemRobocopy `
-Path 'D:\Temp\Source' `
-Destination 'D:\Temp\Target' `
-Container:$false `
-LogDirectoryPath 'D:\Temp' -Debug
.EXAMPLE
'Same as previous with usage of pipeline'
Get-ChildItem -Path 'D:\Temp' -Filter S* -Directory |
Copy-RvItemRobocopy `
-Destination 'D:\Temp\Target' `
-Container:$true `
-LogDirectoryPath 'D:\Temp' -Debug
.EXAMPLE
'Copy multiple files'
Copy-RvItemRobocopy `
-Path 'D:\Temp\Source\1.txt',
'D:\Temp\Source\2.txt',
'D:\Temp\Source\3.txt' `
-Destination 'D:\Temp\Target' `
-LogDirectoryPath 'D:\Temp' -Debug
.EXAMPLE
'Copy multiple files using pipeline'
Get-ChildItem -Path 'D:\Temp\Source' -Filter *.txt |
Copy-RvItemRobocopy `
-Destination 'D:\Temp\Target' `
-LogDirectoryPath 'D:\Temp'
.INPUTS
System.IO.DirectoryInfo (directory object) or System.IO.FileInfo (file object)
.OUTPUTS
System.Management.Automation.PSCustomObject
.LINK
https://techstronghold.com/
#>
[CmdletBinding(
DefaultParametersetName = 'ComputerName',
SupportsShouldProcess = $true,
PositionalBinding = $false,
HelpURI = 'https://techstronghold.com/',
ConfirmImpact = 'Medium'
)]
Param
(
[Parameter(
Mandatory = $true,
Position = 0,
# ParameterSetName = '',
ValueFromPipelineByPropertyName = $true
)]
[ValidateLength(1, 1024)]
[Alias('FullName')]
[string[]]$Path,
[Parameter(
Mandatory = $true,
Position = 1
# ParameterSetName = ''
)]
[ValidateLength(1, 1024)]
[Alias('DestinationPath')]
[string]$Destination,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[bool]$Container = $true,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[ValidateLength(1, 1024)]
[string[]]$ExcludedFile,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[ValidateLength(1, 1024)]
[string[]]$ExcludedDirectory,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[ValidateSet(
'Copy',
'Mirror'
)]
[string]$Process = 'Copy',
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[ValidateSet(
'All',
'DateAndTime'
)]
[string]$Information = 'DateAndTime',
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[ValidateSet(
'VolumeRoot'
)]
[AllowNull()]
[string[]]$Configuration,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[AllowEmptyString()]
[ValidateLength(0, 255)]
[string]$LogPath,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[AllowEmptyString()]
[ValidateLength(0, 255)]
[string]$LogDirectoryPath,
[Parameter(
Mandatory = $false
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[AllowEmptyString()]
[ValidateLength(0, 255)]
[string]$LogFileName,
[Parameter(
Mandatory = $false,
# Position = ,
ParameterSetName = 'ComputerName'
)]
[AllowNull()]
[ValidateLength(1, 255)]
[string[]]$ComputerName = '.',
[Parameter(
Mandatory = $true,
# Position = ,
ParameterSetName = 'Session'
)]
[AllowNull()]
[System.Management.Automation.Runspaces.PSSession[]]$Session
)
Begin
{
$ErrorActionPreference = 'Stop'
if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
Set-PSDebug -Strict
Set-StrictMode -Version Latest
$logDateTimeUtc = Get-Date -Format yyyyMMddHHmmss
# Paths
if ($LogPath)
{
$logPathFullItem = $LogPath
}
elseif ($LogDirectoryPath)
{
if ($LogFileName)
{
$logPathFullItem = Join-Path -Path $LogDirectoryPath -ChildPath $LogFileName
}
else
{
$logPathFullItem = Join-Path -Path $LogDirectoryPath -ChildPath ('{0}.log' -f $logDateTimeUtc)
}
}
else
{
$logPathFullItem = $null
}
if ($logPathFullItem)
{
New-RvDirectory -Path (Split-Path -Path $logPathFullItem)
}
#region Functions
Function Copy-RvItemRobocopyProcess
{
[CmdletBinding(
DefaultParametersetName = 'Path',
SupportsShouldProcess = $true,
PositionalBinding = $true,
HelpURI = 'https://techstronghold.com/',
ConfirmImpact = 'Medium'
)]
Param
(
[Parameter(
Mandatory = $true,
# Position = ,
ParameterSetName = 'Path'
)]
[string[]]$Path,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[string]$Destination,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[bool]$Container,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[string[]]$ExcludedFile,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[string[]]$ExcludedDirectory,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[string]$Process,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[string]$Information,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[string[]]$Configuration,
[Parameter(
Mandatory = $true
# Position = ,
# ParameterSetName = ''
)]
[AllowNull()]
[AllowEmptyString()]
[string]$LogPath
)
Begin
{
<#
Special configuration
#>
if ($Configuration -contains 'VolumeRoot')
{
$ExcludedDirectory += '$RECYCLE.BIN', 'System Volume Information'
}
}
Process
{
foreach ($pathItem in $Path)
{
<#
Flags
#>
$resultStatus = $true
$resultStatusDescription = @()
$resultError = $false
$resultErrorDescription = @()
<#
Source and destination
#>
Write-Debug -Message ' - Items'
Write-Debug -Message (' - Source : {0}' -f $pathItem)
# Wrong path
$item = Get-Item -Path $pathItem -ErrorAction SilentlyContinue
if (!$item)
{
Write-Error -Message ('Path is wrong or you do not have permissions: {0}' -f $pathItem)
}
# Copy directory
elseif ($item.PSIsContainer)
{
Write-Debug -Message ' - Type: Directory'
if ($Container)
{
$destinationItem = Join-Path -Path $Destination -ChildPath (Split-Path -Path $pathItem -Leaf)
}
else
{
$destinationItem = $Destination
}
$parametersAndArguments = $pathItem, $destinationItem
}
# Copy file
else
{
Write-Debug -Message ' - Type: File'
$destinationItem = $Destination
$parametersAndArguments = (Split-Path -Path $pathItem -Parent), $destinationItem, $item.Name
}
Write-Debug -Message (' - Destination : {0}' -f $destinationItem)
<#
Parameters and arguments
#>
# Information
if ($Information -eq 'All')
{
$parametersAndArguments += '/COPYALL', '/DCOPY:T'
}
else # if ($Information -eq 'DateAndTime')
{
$parametersAndArguments += '/COPY:DT', '/DCOPY:T'
}
# Process
if ($Process -eq 'Copy')
{
if ($item.PSIsContainer)
{
$parametersAndArguments += '/E'
}
}
else # if ($Process -eq 'Mirror')
{
$parametersAndArguments += '/MIR'
}
# Common
$parametersAndArguments += '/R:0', '/V'
# Log
if ($logPathFullItem)
{
$parametersAndArguments += ('/UNILOG+:{0}' -f $logPathFullItem), '/NP', '/TS', '/TEE'
}
# Exclusions
if ($ExcludedFile)
{
$parametersAndArguments += '/XF', $ExcludedFile
}
if ($ExcludedDirectory)
{
$parametersAndArguments += '/XF', $ExcludedDirectory
}
<#
Process
#>
Write-Debug -Message (' - & ''robocopy'' ''{0}''' -f ($parametersAndArguments -join ''', '''))
$outputRaw = & 'robocopy' $parametersAndArguments
# Decrease amount of data that will be further processed
$outputRawFooter = $outputRaw | Select-Object -Last 35
$outputRawResultTable = $outputRaw | Select-Object -Last 14
$outputTextFooter = $outputRawFooter -join "`r`n"
Write-Debug -Message $outputTextFooter
<#
Exit code
#>
$resultExitCode = $LASTEXITCODE
if ($resultExitCode -eq 0 -or $resultExitCode -eq 1)
{
Write-Debug -Message ' - Results: True'
}
else
{
Write-Debug -Message ' - Results: False'
Write-Warning -Message ('Robocopy: Results: False; Last exit code: {0}' -f $resultExitCode)
$resultStatus = $false
$resultError = $true
$resultErrorDescription += 'Exit code: {0}' -f $resultExitCode
}
switch ($resultExitCode)
{
0 { $resultExitCodeDescription = 'No errors occurred, and no copying was done. The source and destination directory trees are completely synchronized.'; break }
1 { $resultExitCodeDescription = 'One or more files were copied successfully (that is, new files have arrived).'; break }
2 { $resultExitCodeDescription = 'Some Extra files or directories were detected. No files were copied'; break }
3 { $resultExitCodeDescription = 'Some files were copied. Additional files were present. No failure was encountered.'; break }
4 { $resultExitCodeDescription = 'Some Mismatched files or directories were detected.'; break }
5 { $resultExitCodeDescription = 'Some files were copied. Some files were mismatched. No failure was encountered.'; break }
6 { $resultExitCodeDescription = 'Additional files and mismatched files exist. No files were copied and no failures were encountered. This means that the files already exist in the destination directory'; break }
7 { $resultExitCodeDescription = 'Files were copied, a file mismatch was present, and additional files were present.'; break }
8 { $resultExitCodeDescription = 'Some files or directories could not be copied (copy errors occurred and the retry limit was exceeded).'; break }
16 { $resultExitCodeDescription = 'Serious error. Robocopy did not copy any files. Either a usage error or an error due to insufficient access privileges on the source or destination directories.'; break }
Default { $resultExitCodeDescription = 'Unknown exit code' }
}
<#
Results
#>
# 0, 2439043
$resultDirectories = ((($outputRawResultTable -match '^\s+Dirs :\s+\d') -replace '.*:\s+', '').Trim()) -split '\s+'
$resultFiles = ((($outputRawResultTable -match '^\s+Files :\s+\d') -replace '.*:\s+', '').Trim()) -split '\s+'
# 239.49 m, 82.497 g
$resultBytes = ((($outputRawResultTable -match '^\s+Bytes :\s+\d') -replace '.*:\s+', '').Trim()) -split '\s{2,}'
$resultErrorNumbersInTable = $false
try { $resultDirectoriesTotal = [Int64]$resultDirectories[0] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesTotal = 'Error' }
try { $resultDirectoriesCopied = [Int64]$resultDirectories[1] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesCopied = 'Error' }
try { $resultDirectoriesSkipped = [Int64]$resultDirectories[2] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesSkipped = 'Error' }
try { $resultDirectoriesMismatch = [Int64]$resultDirectories[3] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesMismatch = 'Error' }
try { $resultDirectoriesFailed = [Int64]$resultDirectories[4] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesFailed = 'Error' }
try { $resultDirectoriesExtras = [Int64]$resultDirectories[5] } catch { $resultErrorNumbersInTable = $true; $resultDirectoriesExtras = 'Error' }
try { $resultFilesTotal = [Int64]$resultFiles[0] } catch { $resultErrorNumbersInTable = $true; $resultFilesTotal = 'Error' }
try { $resultFilesCopied = [Int64]$resultFiles[1] } catch { $resultErrorNumbersInTable = $true; $resultFilesCopied = 'Error' }
try { $resultFilesSkipped = [Int64]$resultFiles[2] } catch { $resultErrorNumbersInTable = $true; $resultFilesSkipped = 'Error' }
try { $resultFilesMismatch = [Int64]$resultFiles[3] } catch { $resultErrorNumbersInTable = $true; $resultFilesMismatch = 'Error' }
try { $resultFilesFailed = [Int64]$resultFiles[4] } catch { $resultErrorNumbersInTable = $true; $resultFilesFailed = 'Error' }
try { $resultFilesExtras = [Int64]$resultFiles[5] } catch { $resultErrorNumbersInTable = $true; $resultFilesExtras = 'Error' }
$resultBytesTotal = $resultBytes[0]
$resultBytesCopied = $resultBytes[1]
$resultBytesSkipped = $resultBytes[2]
$resultBytesMismatch = $resultBytes[3]
$resultBytesFailed = $resultBytes[4]
$resultBytesExtras = $resultBytes[5]
if ($resultErrorNumbersInTable)
{
$resultStatus = $false
$resultError = $true
$resultErrorDescription += 'Cannot gather results'
}
if ($resultDirectoriesFailed -gt 0 -or $resultFilesFailed -gt 0)
{
$resultStatus = $false
$resultError = $true
$resultErrorDescription += 'Some of the copies failed'
}
# Return
[PsCustomObject]@{
Source = $pathItem
Destination = $destinationItem
ItemType = $(if ($item.PSIsContainer) { 'Container' } else { 'Leaf' })
Status = $resultStatus
StatusDescription = ($resultStatusDescription -join '; ')
Error = $resultError
ErrorDescription = ($resultErrorDescription -join '; ')
ExitCode = $resultExitCode
ExitCodeDescription = $resultExitCodeDescription
RobocopyLogFooter = $outputRawFooter
RobocopyLogResultTable = $outputRawResultTable
DirectoriesTotal = $resultDirectoriesTotal
DirectoriesCopied = $resultDirectoriesCopied
DirectoriesSkipped = $resultDirectoriesSkipped
DirectoriesMismatch = $resultDirectoriesMismatch
DirectoriesFailed = $resultDirectoriesFailed
DirectoriesExtras = $resultDirectoriesExtras
FilesTotal = $resultFilesTotal
FilesCopied = $resultFilesCopied
FilesSkipped = $resultFilesSkipped
FilesMismatch = $resultFilesMismatch
FilesFailed = $resultFilesFailed
FilesExtras = $resultFilesExtras
BytesTotal = $resultBytesTotal
BytesCopied = $resultBytesCopied
BytesSkipped = $resultBytesSkipped
BytesMismatch = $resultBytesMismatch
BytesFailed = $resultBytesFailed
BytesExtras = $resultBytesExtras
}
if ($resultError)
{
Write-Error -Message ($resultErrorDescription -join '; ')
}
}
}
End
{
}
}
#endregion
}
Process
{
if ($PSCmdlet.ParameterSetName -eq 'ComputerName' -or $Session -eq $null)
{
foreach ($computerNameItem in $ComputerName)
{
# Local device
if (!$computerNameItem -or $computerNameItem -eq '.' -or $computerNameItem -eq $env:COMPUTERNAME)
{
Copy-RvItemRobocopyProcess `
-Path $Path `
-Destination $Destination `
-Container $Container `
-ExcludedFile $ExcludedFile `
-ExcludedDirectory $ExcludedDirectory `
-Process $Process `
-Information $Information `
-Configuration $Configuration `
-LogPath $logPathFullItem
}
# Remote device
else
{
Invoke-Command `
-ComputerName $computerNameItem `
-ArgumentList $Path, $Destination, $Container, $ExcludedFile, $ExcludedDirectory, $Process, $Information, $Configuration, $logPathFullItem `
-ScriptBlock ${Function:Copy-RvItemRobocopyProcess}
}
}
}
else # if ($PSCmdlet.ParameterSetName -eq 'Session')
{
foreach ($sessionItem in $Session)
{
Invoke-Command `
-Session $sessionItem `
-ArgumentList $Path, $Destination, $Container, $ExcludedFile, $ExcludedDirectory, $Process, $Information, $Configuration, $logPathFullItem `
-ScriptBlock ${Function:Copy-RvItemRobocopyProcess}
}
}
}
End
{
}
}

2 responses to “PowerShell advanced function (cmdlet) to copy files and folders using Robocopy on a local or remote computer”
"The term ‘New-RvDirectory’ is not recognized as the name of a cmdlet, function, script file, or operable program."
Based on the code I am seeing the script appears to be a very nice way to setup RoboCopy to run in an environment. However, I am getting the same error when using this script on the ‘New-RVDirectory’ but assuming you are looking to create a new directory that is an easy fix. However the code appears to have an additional issue with the ‘Copy-RvItemRobocopyProcess’ command. I am getting an error that the system object does not contain a method named trim. It appears that on the function for ‘Copy-RvItemRobocopyProcess’ the parameters you are using are treating the string as a folder object and it cannot pass through your function without throwing up errors.