functions/Start-Robocopy.ps1

Function Start-RoboCopy {

    <#
    .SYNOPSIS
    Start Robocopy with PowerShell
 
    .DESCRIPTION
    See https://technet.microsoft.com/en-us/library/cc733145(v=ws.11).aspx for an extensive documentation on Robocopy switches
    Some parameters are in use by the function: /bytes /TEE /np /njh /fp /v /ndl /ts
 
    .PARAMETER Confirm
    Prompts you for confirmation before running the function.
 
    .PARAMETER WhatIf
    Shows what would happen if the function runs. The function is not run.
 
    .EXAMPLE
    PS > Start-RoboCopy -Source C:\temp\ -Destination C:\temp2\ -Mirror
 
    Source : C:\temp\
    Destination : C:\temp2\
    Command : Robocopy.exe "C:\temp" "C:\temp2" *.* /r:3 /w:3 /mir /bytes /TEE /np /njh /fp /v /ndl /ts
    DirCount : 11
    FileCount : 34
    DirCopied : 1
    FileCopied : 6
    DirIgnored : 10
    FileIgnored : 28
    DirMismatched : 0
    FileMismatched : 0
    DirFailed : 0
    FileFailed : 0
    DirExtra : 0
    FileExtra : 1
    TotalTime : 00:00:00
    StartedTime : 2019-04-13 18:14:20
    EndedTime : 2019-04-13 18:14:20
    TotalSize : 1,4 MB
    TotalSizeCopied : 1,2 MB
    TotalSizeIgnored : 155 KB
    TotalSizeMismatched : 0 B
    TotalSizeFailed : 0 B
    TotalSizeExtra : 9 KB
    Speed : 72,5 MB/s
    ExitCode : 3
    Success : True
    LastExitCodeMessage : [SUCCESS]Some files were copied. Additional files were present. No failure was encountered.
 
    .EXAMPLE
    PS > Start-RoboCopy -Source 'C:\tmp 2\' -Destination NULL -IncludeEmptySubDirectories -list
 
    Source : C:\tmp 2\
    Destination : NULL
    Command : Robocopy.exe "C:\tmp 2" "NULL" *.* /r:3 /w:3 /e /l /bytes /TEE /np /njh /fp /v /ndl /ts
    DirCount : 2929
    FileCount : 53272
    DirCopied : 2928
    FileCopied : 36370
    DirIgnored : 1
    FileIgnored : 16902
    DirMismatched : 0
    FileMismatched : 0
    DirFailed : 0
    FileFailed : 0
    DirExtra : 0
    FileExtra : 0
    TotalTime : 00:00:13
    StartedTime : 2019-05-30 17:57:57
    EndedTime : 2019-05-30 17:58:11
    TotalSize : 21,8 MB
    TotalSizeCopied : 21,3 MB
    TotalSizeIgnored : 547 KB
    TotalSizeMismatched : 0 B
    TotalSizeFailed : 0 B
    TotalSizeExtra : 0 B
    Speed : 0 B/s
    ExitCode : 1
    Success : True
    LastExitCodeMessage : [SUCCESS]All files were copied successfully.
 
    .EXAMPLE
    PS > Start-RoboCopy -Source 'C:\tmp 2\' -Destination NULL -IncludeEmptySubDirectories -ListToDefaultStream
 
    Extension : txt
    Name : file1.txt
    FullName : C:\tmp 2\file1.txt
    Length : 24
    TimeStamp : 2019-05-30 15:35:32
 
    Extension : txt
    Name : file10.txt
    FullName : C:\tmp 2\file10.txt
    Length : 28
    TimeStamp : 2019-05-30 15:35:32
 
    .NOTES
    Original script by Keith S. Garner (KeithGa@KeithGa.com) - 6/23/2014
    Originally posted on https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress
 
    With inspiration by Trevor Sullivan @pcgeek86
    https://stackoverflow.com/a/21209726
 
    Updated by Ninjigen - 01/08/2018
    https://github.com/Ninjigen/PowerShell/tree/master/Robocopy
    #>


    [CmdletBinding(SupportsShouldProcess)]

    Param (

        # Specifies the path to the source directory. Must be a folder.
        [Parameter( Mandatory = $True,
            ValueFromPipelineByPropertyName,
            ValueFromPipeline)]
        [Alias('Path','FullPath')]
        [String]$Source,

        # Specifies the path to the destination directory. Must be a folder.
        [Parameter( Mandatory = $True,
            ValueFromPipelineByPropertyName,
            ValueFromPipeline)]
        [Alias('Target')]
        [String]$Destination,

        # Specifies the file or files to be copied. You can use wildcard characters (* or ?), if you want. If the File parameter is not specified, *.* is used as the default value.
        [Parameter(Mandatory = $False)]
        [String[]] $Files = '*.*',

        # Writes the status output to the log file (overwrites the existing log file).
        [Parameter(Mandatory = $False)]
        [String]$LogFile,

        # Copies subdirectories. Note that this option excludes empty directories.
        [Parameter(ParameterSetName = 'IncludeSubDirectories')]
        [Alias('s')]
        [switch]$IncludeSubDirectories,

        # Copies subdirectories. Note that this option includes empty directories.
        [Parameter(ParameterSetName = 'IncludeEmptySubDirectories')]
        [Alias('e', 'Recurse')]
        [switch]$IncludeEmptySubDirectories,

        # Copies only the top N levels of the source directory tree.
        [Parameter(Mandatory = $False)]
        [Alias('lev', 'Depth')]
        [Int]$Level,

        # Copies files in Backup mode.
        [Alias('b')]
        [switch]$BackupMode,

        # Copies files in restartable mode.
        [Alias('z')]
        [switch]$RestartMode,

        # Copies all encrypted files in EFS RAW mode.
        [switch]$EFSRaw,

        # Specifies the file properties to be copied. The default value for CopyFlags is DAT (data, attributes, and time stamps). D = Data. A = Attributes. T = Time stamps.S = NTFS access control list (ACL). O =Owner information. U = Auditing information
        [Parameter(Mandatory = $False)]
        [Alias('copy')]
        [ValidateSet('D', 'A', 'T', 'S', 'O', 'U')]
        [String[]]$CopyFlags,

        # Copies no file information.
        [switch]$NoCopy,

        # Fixes file security on all files, even skipped ones.
        [Alias('secfix')]
        [switch]$SecurityFix,

        # Fixes file times on all files, even skipped ones.
        [Alias('timfix')]
        [switch]$Timefix,

        # Deletes destination files and directories that no longer exist in the source.
        [switch]$Purge,

        # Mirrors a directory tree
        [Parameter(ParameterSetName = 'Mirror')]
        [Alias('mir', 'Sync')]
        [switch]$Mirror,

        # Moves files, recursively, and deletes them from the source after they are copied. Folders will still be in source directory.
        [Parameter(ParameterSetName = 'MoveFiles')]
        [Alias('mov')]
        [switch]$MoveFiles,

        # Moves files and directories, and deletes them from the source after they are copied.
        [Parameter(ParameterSetName = 'MoveFilesAndDirectories')]
        [Alias('move')]
        [switch]$MoveFilesAndDirectories,

        # Adds the specified attributes to copied files.
        [Parameter(Mandatory = $False)]
        [ValidateSet('R', 'A', 'S', 'H', 'N', 'E', 'T')]
        [String[]]$AddAttribute,

        # Removes the specified attributes from copied files.
        [Parameter(Mandatory = $False)]
        [ValidateSet('R', 'A', 'S', 'H', 'N', 'E', 'T')]
        [String[]]$RemoveAttribute,

        # Creates a directory tree and zero-length files only.
        [switch]$Create,

        # Creates destination files by using 8.3 character-length FAT file names only.
        [switch]$FAT,

        # Turns off support for very long paths.
        [Alias('256')]
        [switch]$IgnoreLongPath,

        # Monitors the source, and runs again when more than N changes are detected.
        [Parameter(Mandatory = $False)]
        [Alias('mon')]
        [Int]$MonitorChanges,

        # Monitors source, and runs again in M minutes if changes are detected.
        [Parameter(Mandatory = $False)]
        [Alias('mot')]
        [Int]$MonitorMinutes,

        # Creates multi-threaded copies with N threads. N must be an integer between 1 and 128. Cannot be used with the InterPacketGap and EFSRAW parameters. The /MT parameter applies to Windows Server 2008 R2 and Windows 7.
        [Parameter(Mandatory = $False)]
        [Alias('MT')]
        [string]$Threads,

        # Specifies run times when new copies may be started.
        [Parameter(Mandatory = $False)]
        [Alias('rh')]
        [ValidatePattern("[0-2]{1}[0-3]{1}[0-5]{1}[0-9]{1}-[0-2]{1}[0-3]{1}[0-5]{1}[0-9]{1}")]
        [String]$RunTimes,

        # Checks run times on a per-file (not per-pass) basis.
        [Alias('pf')]
        [switch]$UsePerFileRunTimes,

        # Specifies the inter-packet gap to free bandwidth on slow lines.
        [Parameter(Mandatory = $False)]
        [Alias('ipg')]
        [Int]$InterPacketGap,

        # Follows the symbolic link and copies the target.
        [Alias('sl')]
        [switch]$SymbolicLink,

        # Copies only files for which the Archive attribute is set.
        [Alias('a')]
        [switch]$Archive,

        # Copies only files for which the Archive attribute is set, and resets the Archive attribute.
        [Alias('m')]
        [switch]$ResetArchiveAttribute,

        # Includes only files for which any of the specified attributes are set.
        [Parameter(Mandatory = $False)]
        [Alias('ia')]
        [ValidateSet('R', 'A', 'S', 'H', 'N', 'E', 'T', 'O')]
        [String[]]$IncludeAttribute,

        # Excludes files for which any of the specified attributes are set.
        [Parameter(Mandatory = $False)]
        [ValidateSet('R', 'A', 'S', 'H', 'N', 'E', 'T', 'O')]
        [Alias('xa')]
        [String[]]$ExcludeAttribute,

        # Excludes files that match the specified names or paths. Note that FileName can include wildcard characters (* and ?).
        [Parameter(Mandatory = $False)]
        [Alias('xf')]
        [String[]]$ExcludeFileName,

        # Excludes directories that match the specified names and paths.
        [Parameter(Mandatory = $False)]
        [Alias('xd')]
        [String[]]$ExcludeDirectory,

        # Excludes changed files.
        [Alias('xct')]
        [switch]$ExcludeChangedFiles,

        # Excludes newer files.
        [Alias('xn')]
        [switch]$ExcludeNewerFiles,

        # Excludes older files.
        [Alias('xo')]
        [switch]$ExcludeOlderFiles,

        # Excludes extra files and directories.
        [Alias('xx')]
        [switch]$ExcludeExtraFiles,

        # Excludes "lonely" files and directories.
        [Alias('xl')]
        [switch]$ExcludeLonelyFiles,

        # Includes the same files.
        [Alias('is')]
        [switch]$IncludeSameFiles,

        # Includes "tweaked" files.
        [Alias('it')]
        [switch]$IncludeTweakedFiles,

        # Specifies the maximum file size (to exclude files bigger than N bytes).
        [Parameter(Mandatory = $False)]
        [Alias('max')]
        [String]$MaximumFileSize,

        # Specifies the minimum file size (to exclude files smaller than N bytes).
        [Parameter(Mandatory = $False)]
        [Alias('min')]
        [String]$MinimumFileSize,

        # Specifies the maximum file age (to exclude files older than N days or date).
        [Parameter(Mandatory = $False)]
        [Alias('maxage')]
        [String]$MaximumFileAge,

        # Specifies the minimum file age (exclude files newer than N days or date).
        [Parameter(Mandatory = $False)]
        [Alias('minage')]
        [String]$MinimumFileAge,

        # Specifies the maximum last access date (excludes files unused since N).
        [Parameter(Mandatory = $False)]
        [Alias('maxlad')]
        [String]$MaximumFileLastAccessDate,

        # Specifies the minimum last access date (excludes files used since N) If N is less than 1900, N specifies the number of days. Otherwise, N specifies a date in the format YYYYMMDD.
        [Parameter(Mandatory = $False)]
        [Alias('minlad')]
        [String]$MinimumFileLastAccessDate,

        # Excludes junction points, which are normally included by default.
        [Alias('xj')]
        [switch]$ExcludeJunctionPoints,

        # Excludes junction points for files.
        [Alias('xjf')]
        [switch]$ExcludeFileJunctionPoints,

        # Excludes junction points for directories.
        [Alias('xjd')]
        [switch]$ExcludeDirectoryJunctionPoints,

        # Assumes FAT file times (two-second precision).
        [Alias('fft')]
        [switch]$AssumeFATFileTime,

        # Compensates for one-hour DST time differences.
        [Alias('dst')]
        [switch]$CompensateDST,

        # Specifies the number of retries on failed copies. Default is 3.
        [Alias('r')]
        [int]$Retry = 3,

        # Specifies the wait time between retries, in seconds. The default value of N is 3.
        [Alias('w')]
        [int]$Wait = 3,

        # Saves the values specified in the /r and /w options as default settings in the registry.
        [Alias('reg')]
        [switch]$SaveRetrySettings,

        # Specifies that the system will wait for share names to be defined (retry error 67).
        [Alias('tbd')]
        [switch]$WaitForShareName,

        # What unit the sizes are shown as
        [ValidateSet('Auto', 'PB', 'TB', 'GB', 'MB', 'KB', 'Bytes')]
        [String]$Unit = 'Auto',

        # Specifies that files are to be listed only (and not copied, deleted, or time stamped).
        [Alias('l')]
        [Switch]$List,

        # Specifies that files are to be listed only (and not copied, deleted, or time stamped) and output to default stream.
        [Switch]$ListToDefaultStream
    )

    Begin { }

    Process {

        # Remove trailing backslash because Robocopy can sometimes error out when spaces are in path names
        $ModifiedSource = $Source -replace '\\$'
        $ModifiedDestination = $Destination -replace '\\$'

        # We place "" so we can use spaces in path names
        $ModifiedSource = '"' + $ModifiedSource + '"'
        $ModifiedDestination = '"' + $ModifiedDestination + '"'

        # RobocopyArguments are not the final variable that countain all robocopy parameters
        $RobocopyArguments = $ModifiedSource, $ModifiedDestination + $Files

        # We add wait and retry with the default from their parameters, else Robocopy will try a million time before time out
        $RobocopyArguments += '/r:' + $Retry
        $RobocopyArguments += '/w:' + $Wait

        if ($IncludeSubDirectories) { $RobocopyArguments += '/s'; $action = 'Copy' }
        if ($IncludeEmptySubDirectories) { $RobocopyArguments += '/e'; $action = 'Copy' }
        If ($LogFile) { $RobocopyArguments += '/log:' + $LogFile }
        if ($Level) { $RobocopyArguments += '/lev:' + $Level }
        if ($BackupMode) { $RobocopyArguments += '/b' }
        if ($RestartMode) { $RobocopyArguments += '/z' }
        if ($EFSRaw) { $RobocopyArguments += '/efsraw' }
        if ($CopyFlags) { $RobocopyArguments += '/copy:' + (($CopyFlags | Sort-Object -Unique) -join '') }
        if ($NoCopy) { $RobocopyArguments += '/nocopy' }
        if ($SecurityFix) { $RobocopyArguments += '/secfix' }
        if ($Timefix) { $RobocopyArguments += '/timfix' }
        if ($Purge) { $RobocopyArguments += '/purge' ; $action = 'Purge' }
        if ($Mirror) { $RobocopyArguments += '/mir'; $action = 'Mirror' }
        if ($MoveFiles) { $RobocopyArguments += '/mov'; $action = 'Move' }
        if ($MoveFilesAndDirectories) { $RobocopyArguments += '/move' ; $action = 'Move' }
        if ($AddAttribute) { $RobocopyArguments += '/a+:' + (($AddAttribute | Sort-Object-Unique) -join '') }
        if ($RemoveAttribute) { $RobocopyArguments += '/a-:' + (($RemoveAttribute | Sort-Object-Unique) -join '') }
        if ($Create) { $RobocopyArguments += '/create' }
        if ($fat) { $RobocopyArguments += '/fat' }
        if ($IgnoreLongPath) { $RobocopyArguments += '/256' }
        if ($MonitorChanges) { $RobocopyArguments += '/mon:' + $MonitorChanges }
        if ($MonitorMinutes) { $RobocopyArguments += '/mot:' + $MonitorMinutes }
        if ($Threads) { $RobocopyArguments += '/MT:' + $Threads }
        if ($RunTimes) { $RobocopyArguments += '/rh:' + $RunTimes }
        if ($UsePerFileRunTimes) { $RobocopyArguments += '/pf' }
        if ($InterPacketGap) { $RobocopyArguments += '/ipg:' + $InterPacketGap }
        if ($SymbolicLink) { $RobocopyArguments += '/sl' }
        if ($Archive) { $RobocopyArguments += '/a' }
        if ($ResetArchiveAttribute) { $RobocopyArguments += '/m' }
        if ($IncludeAttribute) { $RobocopyArguments += '/ia:' + ($IncludeAttribute | Sort-Object-Unique) -join '' }
        if ($ExcludeAttribute) { $RobocopyArguments += '/xa:' + ($ExcludeAttribute | Sort-Object-Unique) -join '' }
        if ($ExcludeFileName) { $RobocopyArguments += '/xf ' + $ExcludeFileName -join ' ' }
        if ($ExcludeDirectory) { $RobocopyArguments += '/xd ' + $ExcludeDirectory -join ' ' }
        if ($ExcludeChangedFiles) { $RobocopyArguments += '/xct' }
        if ($ExcludeNewerFiles) { $RobocopyArguments += '/xn' }
        if ($ExcludeOlderFiles) { $RobocopyArguments += '/xo' }
        if ($ExcludeExtraFiles) { $RobocopyArguments += '/xx' }
        if ($ExcludeLonelyFiles) { $RobocopyArguments += '/xl' }
        if ($IncludeSameFiles) { $RobocopyArguments += '/is' }
        if ($IncludeTweakedFiles) { $RobocopyArguments += '/it' }
        if ($MaximumFileSize) { $RobocopyArguments += '/max:' + $MaximumFileSize }
        if ($MinimumFileSize) { $RobocopyArguments += '/min:' + $MinimumFileSize }
        if ($MaximumFileAge) { $RobocopyArguments += '/maxage:' + $MaximumFileAge }
        if ($MinimumFileAge) { $RobocopyArguments += '/minage:' + $MinimumFileAge }
        if ($MaximumFileLastAccessDate) { $RobocopyArguments += '/maxlad:' + $MaximumFileLastAccessDate }
        if ($MinimumFileLastAccessDate) { $RobocopyArguments += '/minlad:' + $MinimumFileLastAccessDate }
        if ($ExcludeJunctionPoints) { $RobocopyArguments += '/xj' }
        if ($ExcludeFileJunctionPoints) { $RobocopyArguments += '/xjf' }
        if ($ExcludeDirectoryJunctionPoints) { $RobocopyArguments += '/xjd' }
        if ($AssumeFATFileTime) { $RobocopyArguments += '/fft' }
        if ($CompensateDST) { $RobocopyArguments += '/dst' }
        if ($SaveRetrySettings) { $RobocopyArguments += '/reg' }
        if ($WaitForShareName) { $RobocopyArguments += '/tbd' }
        If ($List) { $RobocopyArguments += '/l' ; $action = 'List' }
        If ($ListToDefaultStream) { $RobocopyArguments += '/l' ; $action = 'List' }

        # Reason why ShouldProcess is this far down is because $action is not set before this part
        If ($PSCmdlet.ShouldProcess("$Destination from $Source" , $action)) {

            if (-not (Get-Command -Name robocopy -ErrorAction SilentlyContinue)) {
                Write-Warning -Message "Action failed because robocopy.exe could not be found."
                break
            }

            # Regex filter used for finding strings we want to handle in Robocopy output. This is also used when we find specific strings in the output
            [regex] $HeaderRegex = '\s+Total\s*Copied\s+Skipped\s+Mismatch\s+FAILED\s+Extras'
            [regex] $DirLineRegex = 'Dirs\s*:\s*(?<DirCount>\d+)(?:\s+\d+){3}\s+(?<DirFailed>\d+)\s+\d+'
            [regex] $FileLineRegex = 'Files\s*:\s*(?<FileCount>\d+)(?:\s+\d+){3}\s+(?<FileFailed>\d+)\s+\d+'
            [regex] $BytesLineRegex = 'Bytes\s*:\s*(?<ByteCount>\d+)(?:\s+\d+){3}\s+(?<BytesFailed>\d+)\s+\d+'
            [regex] $TimeLineRegex = 'Times\s*:\s*(?<TimeElapsed>\d+).*'
            [regex] $EndedLineRegex = 'Ended\s*:\s*(?<EndedTime>.+)'
            [regex] $SpeedLineRegex = 'Speed\s:\s+(\d+)\sBytes\/sec'
            [regex] $JobSummaryEndLineRegex = '[-]{78}'
            [regex] $SpeedInMinutesRegex = 'Speed\s:\s+(\d+).(\d+)\sMegaBytes\/min'

            # Regex filter for catching errors
            $ErrorFilterNonTerminating = @(
                "ERROR 5 \(0x00000005\) Accessing Destination Directory",
                "Access is denied.",
                "ERROR 53 \(0x00000035\) Getting File System Type of Destination",
                "ERROR 53 \(0x00000035\) Accessing Destination Directory",
                "ERROR 53 \(0x00000035\) Creating Destination Directory",
                "The network path was not found."
            ) -join '|'
            
            

            # Regex filter for catching errors
            $ErrorFilter = @(
                "The system cannot find the file specified.",
                "The system cannot find the path specified.",
                "Access is denied.",
                "The handle is invalid.",
                "The process cannot access the file because it is being used by another process.",
                "Scanning Destination Directory: Windows cannot find the network path. Verify that the network path is correct and the destination computer is not busy or turned off. If Windows still cannot find the network path, contact your network administrator.",
                "Copying NTFS Security to Destination File: The specified server cannot perform the requested operation",
                "The specified network name is no longer available.",
                "There is not enough space on the disk.",
                "The semaphore timeout period has expired.",
                "Scanning Source Directory: An internal error occurred.",
                "\*\*\*\*\* You need these to perform Backup copies \(\/B or \/ZB\).",
                "ERROR \d \(0x\d{1,11}\)",
                "ERROR : *"
            ) -join '|'

            # Regex filter to capture text about Retry or Waiting information
            $RetryWaitFilter = @(
                "Waiting $Wait seconds... Retrying...",
                "ERROR: RETRY LIMIT EXCEEDED."
            ) -join '|'

            $StartTime = $(Get-Date)

            # Arguments of the copy command. Fills in the $RoboLog temp file
            $RoboArgs = $RobocopyArguments + "/bytes /TEE /np /njh /fp /v /ndl /ts" -split " "

            #region All Logic for the robocopy process is handled here. Including what to do with the output etc.
            Robocopy.exe $RoboArgs | Where-Object { $PSItem -ne "" } | ForEach-Object {
                # If statement is for catching error messages
                If ($PSitem -match $ErrorFilterNonTerminating) {
                    
                    try {
                        # Robocopy will in some cases send two lines of text with information about an error. We catch both before output
                        
                        If (!($IsLastMessage)) {
                            #$ErrorMessage = [regex]::Split($PSitem, 'ERROR \d \(0x\d{1,11}\)')[-1].trim()
                            #$ErrorMessage = [regex]::Split($PSitem, $ErrorFilterNonTerminating)[1].trim()
                            $errormessage =  $PSitem | Select-String -Pattern $ErrorFilterNonTerminating -AllMatches |  ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value }
                            $IsLastMessage = $true
                        }
                        else {
                            $IsLastMessage = $false
                            throw ("{0}: {1}" -f $ErrorMessage, $PSitem.trim())
                            $ErrorMessage = $null
                        }
                    }
                    catch {
                        $PSCmdlet.WriteError($psitem)
                    }
                }

                elseIf ($PSitem -match $ErrorFilter) {
                    try {
                        # Robocopy will in some cases send two lines of text with information about an error. We catch both before output
                        
                        If (!($IsLastMessage)) {
                            #$ErrorMessage = [regex]::Split($PSitem, 'ERROR \d \(0x\d{1,11}\)')[-1].trim()
                            $ErrorMessage = [regex]::Split($PSitem, $ErrorFilter)[-1].trim()
                            #$errormessage = $PSitem | Select-String -Pattern $ErrorFilter -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value }
                            $IsLastMessage = $true
                        }
                        else {
                            $IsLastMessage = $false
                            throw ("{0}: {1}" -f $ErrorMessage, $PSitem.trim())
                            $ErrorMessage = $null
                        }
                    }
                    catch {
                        $PSCmdlet.ThrowTerminatingError($psitem)
                    }
                }

                # elseif catch all lines with information about copy/move/mir action
                elseif ($PSitem -like "*$Source*" -or $PSitem -like "*$Destination*") {

                    # If no error is found we will output the file name. We are using split because when we use /bytes in the Robocopy args we also output each files size by default.
                    $Line = $PSitem.Trim().Split("`t")

                    If ($Line[0] -notmatch '[0-9]') {
                        # This should capture all output

                        If ($PSBoundParameters.ContainsKey('ListToDefaultStream')) {
                            $Size, [datetime]$TimeStamp = $line[2].Trim().Split(" ", 2) # Trimming and splitting on this line instead of in Write-Verbose for readability
                            $ExtensionSplit = ($Line[3]).Split(".")

                            [PSCustomObject]@{
                                Extension = if ($ExtensionSplit.count -gt 1) { $ExtensionSplit[-1] } else { }
                                Name      = $line[3].Split("\")[-1]
                                FullName  = $line[3]
                                Length    = $Size
                                TimeStamp = $TimeStamp
                            }
                        }
                        else {
                            $Size, [datetime]$TimeStamp = $line[2].Trim().Split(" ", 2) # Trimming and splitting on this line instead of in Write-Verbose for readability
                            Write-Verbose -Message ('"{0} File" on "Item {1}" to target "{2}" Status on Item "{3}". Size on Item "{4}". TimeStamp on Item "{5}"' -f $action, $line[3], $Destination, $line[0].Trim(), $Size, $TimeStamp)
                        }

                    }
                    else {
                        Write-Verbose -Message $PSitem
                    } # end else in ElseIf
                }

                elseif ($PSitem -match $RetryWaitFilter) {
                    Write-Warning -Message $PSitem
                }

                # elseif capture the job summary
                elseif ($PSitem -match "$HeaderRegex|$DirLineRegex|$FileLineRegex|$BytesLineRegex|$TimeLineRegex|$EndedLineRegex|$SpeedLineRegex|$JobSummaryEndLineRegex|$SpeedInMinutesRegex") {

                    # Some we will just assign to variables and dont use or dont do anything with
                    Switch -Regex ($PSitem) {
                        $JobSummaryEndLine { }
                        $HeaderRegex { }
                        $DirLineRegex { $TotalDirs, $TotalDirCopied, $TotalDirIgnored, $TotalDirMismatched, $TotalDirFailed, $TotalDirExtra = $PSitem | Select-String -Pattern '\d+' -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value } }
                        $FileLineRegex { $TotalFiles, $TotalFileCopied, $TotalFileIgnored, $TotalFileMismatched, $TotalFileFailed, $TotalFileExtra = $PSitem | Select-String -Pattern '\d+' -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value } }
                        $BytesLineRegex { $TotalBytes, $TotalBytesCopied, $TotalBytesIgnored, $TotalBytesMismatched, $TotalBytesFailed, $TotalBytesExtra = $PSitem | Select-String -Pattern '\d+' -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value } }
                        $TimeLineRegex { [TimeSpan]$TotalDuration, [TimeSpan]$CopyDuration, [TimeSpan]$FailedDuration, [TimeSpan]$ExtraDuration = $PSitem | Select-String -Pattern '\d?\d\:\d{2}\:\d{2}' -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value } }
                        $EndedLineRegex { }
                        $SpeedLineRegex { $TotalSpeedBytes, $null = $PSitem | Select-String -Pattern '\d+' -AllMatches | ForEach-Object { $PSitem.Matches } | ForEach-Object { $PSitem.Value } }
                        $SpeedInMinutesRegex { }
                    }
                }

                # catch everything we dont have any rules for
                else {
                    Write-Warning "No rule for following line was defined: $PSitem"
                }
            }
            #endregion

            $endtime = $(Get-Date)

            # Exit Code lookup "table"
            $LastExitCodeMessage = switch ($LASTEXITCODE) {
                0 { '[SUCCESS]No files were copied. No failure was encountered. No files were mismatched. The files already exist in the destination directory; therefore, the copy operation was skipped.' }
                1 { '[SUCCESS]All files were copied successfully.' }
                2 { '[SUCCESS]There are some additional files in the destination directory that are not present in the source directory. No files were copied.' }
                3 { '[SUCCESS]Some files were copied. Additional files were present. No failure was encountered.' }
                4 { '[WARNING]Some Mismatched files or directories were detected. Examine the output log. Housekeeping might be required.' }
                5 { '[WARNING]Some files were copied. Some files were mismatched. No failure was encountered.' }
                6 { '[WARNING]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.' }
                7 { '[WARNING]Files were copied, a file mismatch was present, and additional files were present.' }
                8 { '[ERROR]Several files did not copy.(copy errors occurred and the retry limit was exceeded). Check these errors further.' }
                9 { '[ERROR]Some files did copy, but copy errors occurred and the retry limit was exceeded. Check these errors further.' }
                10 { '[ERROR]Copy errors occurred and the retry limit was exceeded. Some Extra files or directories were detected.' }
                11 { '[ERROR]Some files were copied. Copy errors occurred and the retry limit was exceeded. Some Extra files or directories were detected.' }
                12 { '[ERROR]Copy errors occurred and the retry limit was exceeded. Some Mismatched files or directories were detected.' }
                13 { '[ERROR]Some files were copied. Copy errors occurred and the retry limit was exceeded. Some Mismatched files or directories were detected.' }
                14 { '[ERROR]Copy errors occurred and the retry limit was exceeded. Some Mismatched files or directories were detected. Some Extra files or directories were detected.' }
                15 { '[ERROR]Some files were copied. Copy errors occurred and the retry limit was exceeded. Some Mismatched files or directories were detected. Some Extra files or directories were detected.' }
                16 { '[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.' }
                default { '[WARNING]No message associated with this exit code. ExitCode: {0}' -f $LASTEXITCODE }
            }

            # If we got a warning or error, output warning/error message to correct stream
            If ($LASTEXITCODE -gt 3 -and $LASTEXITCODE -lt 8) {
                Write-Warning -Message $LastExitCodeMessage
            }
            If ($LASTEXITCODE -ge 8) {
                Write-Error -Message $LastExitCodeMessage
            }

            $Output = [PSCustomObject]@{
                'Source'              = [System.IO.DirectoryInfo]$Source
                'Destination'         = [System.IO.DirectoryInfo]$Destination
                'Command'             = 'Robocopy.exe ' + $RoboArgs -join " "
                'DirCount'            = [int]$TotalDirs
                'FileCount'           = [int]$TotalFiles
                #'Duration' = $TotalDuration
                'DirCopied'           = [int]$TotalDirCopied
                'FileCopied'          = [int]$TotalFileCopied
                #'CopyDuration' = $CopyDuration
                'DirIgnored'          = [int]$TotalDirIgnored
                'FileIgnored'         = [int]$TotalFileIgnored
                'DirMismatched'       = [int]$TotalDirMismatched
                'FileMismatched'      = [int]$TotalFileMismatched
                'DirFailed'           = [int]$TotalDirFailed
                'FileFailed'          = [int]$TotalFileFailed
                #'FailedDuration' = $FailedDuration
                'DirExtra'            = [int]$TotalDirExtra
                'FileExtra'           = [int]$TotalFileExtra
                #'ExtraDuration' = $ExtraDuration
                'TotalTime'           = "{0:HH:mm:ss}" -f ([datetime]$($endtime - $StartTime).Ticks)
                'StartedTime'         = [datetime]$StartTime
                'EndedTime'           = [datetime]$endTime
                'TotalSize'           = (Format-SpeedHumanReadable $Totalbytes -Unit $Unit)
                'TotalSizeCopied'     = (Format-SpeedHumanReadable $TotalBytesCopied -Unit $Unit)
                'TotalSizeIgnored'    = (Format-SpeedHumanReadable $TotalBytesIgnored -Unit $Unit)
                'TotalSizeMismatched' = (Format-SpeedHumanReadable $TotalBytesMismatched -Unit $Unit)
                'TotalSizeFailed'     = (Format-SpeedHumanReadable $TotalBytesFailed -Unit $Unit)
                'TotalSizeExtra'      = (Format-SpeedHumanReadable $TotalBytesExtra -Unit $Unit)
                'Speed'               = (Format-SpeedHumanReadable $TotalSpeedBytes -Unit $Unit) + '/s'
                'ExitCode'            = $LASTEXITCODE
                'Success'             = If ($LASTEXITCODE -lt 8) { $true } else { $false }
                'LastExitCodeMessage' = [string]$LastExitCodeMessage
            }


            If ($PSBoundParameters.ContainsKey('ListToDefaultStream')) {
                Write-Verbose $Output
            }
            else {
                $Output
            }
        }
    }

    end { }
}