Jump to content

RoboCopy function


storme
 Share

Recommended Posts

BACKGROUND (for those that haven’t used Robocopy)

I ran into Robocopy some time ago and it’s an amazing command line with a heap of options to do almost anything you want.

Technet Robocopy command line reference

It originally came with the Windows Server 2003 Resource Kit Tools as version XP010.

A newer version XP026 was released with the Robocopy GUI

.

The latest version XP027 is part of Vista and Windows 7 (not compatible with Windows XP).

The Vista/7 version is even Multithreaded.

I found a nice tool Easy RoboCopy that helps with the command line and could be interesting if you want to use robocopy in batch file.

But I’d like to use Robocopy in my programs without having to re-create the command line every time. So I’ve created this function to make it simple to use robocopy and to set the switches how I want them to be.

Have a look at the script and I’d appreciate feedback. It’s still a work in progress adn has a lot of consolewrite commands but it does what I want now and I know that is when I tend to go cold on a project and never finish it...sigh... so if I get some feedback on this it will give me some impetus to polish it.

Read the header for how to use it and the test program for examples. If you have any questions please let me know.

<_RoboCopy.au3>

#include <Constants.au3> ; STDIN & STDOUT
;Global Const $STDIN_CHILD              = 1
;Global Const $STDOUT_CHILD             = 2
;Global Const $STDERR_CHILD             = 4
;Global Const $STDERR_MERGED            = 8
;Global Const $STDIO_INHERIT_PARENT     = 0x10
;Global Const $RUN_CREATE_NEW_CONSOLE   = 0x00010000
;===============================================================================
;
; Function Name:    _robocopy($source, $target, $stitches = "mirror", $excludeFiles = "", $excludeFolders = "", $logFile = "", $statusBox = "", $progressBar = "")
; Description::     Runs the robocopy command
; Parameter(s):     $source     -   Source directory
;                   $target     -   Target Directory
;                   $stitches   -   Standard Robocopy switches, predefined meta commands or combination of both
;                               -   "Meta" or "Std Switches" or [meta][Space][Std switches] (eg mirror /r:0 /w:0)
;                               -   Mirror - Clone source to target (includeing subdirectories)
;                               -   Add - Add new/updates files but don't delete anything
;                               -   Copy - Same as Add
;                               -   CopySub - Same as add but also copies all subdirectories
;                               -   Move - Move files to destination
;                               -   MoveSub - Same as move but also copies all subdirectories
;                   $includeFiles   -   A list of files to include (Comma delimited)
;                                   -   OR An array of files
;                   $excludeFiles   -   A list of files to exclude (Comma delimited)
;                                   -   OR An array of files
;                   $excludeFolders -   A list of folders to exclude (Comma delimited)
;                                   -   OR An array of files
;                   $logFile        -   Filename for log file
;                                       If File starts with + then append to log
;                   $outputFunction -   Function name to pass text to from robocopy
;                                   -   Only full lines will be passed to this function
;                                   -   This will be run by execute command in the form {Function name}($text)
; Requirement(s):  Robocopy is installed on the computer or in the same directoy as script
; Return Value(s): Success: ???
;                   Error: 0 and @error =
;                   = 1 - Robocopy.exe can't be found
;                   = 2 - Source doesn't Exist
;                   = 3 - Destination can't be created (eg maybe read only media)
;                   = 4 - Log File can't be created
;                   = 5 - Robocopy Cant' be run
; Author(s):       John Morrison
;
; Basis:
; Thanks:   PsaltyDS http://www.autoitscript.com/forum/index.php?showtopic=98602&view=findpost&p=720326 _ProcessGetExitCode()
;===============================================================================
Func _robocopy($source, $target, $switches = "mirror", $includeFiles = "", $excludeFiles = "", $excludeFolders = "", $logFile = "", $outputFunction = "")
    #cs
    ConsoleWrite("All Switches" & @CR & _
            "$source = <" & $source & ">" & @CR & _
            "$target = <" & $target & ">" & @CR & _
            "$switches = <" & $switches & ">" & @CR & _
            "$includeFiles = <" & $includeFiles & ">" & @CR & _
            "$excludeFolders = <" & $excludeFolders & ">" & @CR & _
            "$logFile = <" & $logFile & ">" & @CR & _
            "$outputFunction = <" & $outputFunction & ">" & @CR)
    #ce

    ; Check if Robocopy can be executed
    Local $roboCopyCMD = ""
    While $roboCopyCMD = ""
        If FileExists(@SystemDir & "\robocopy.exe") Then
            $roboCopyCMD = @SystemDir & "\robocopy.exe "
        ElseIf FileExists(@ScriptDir & "\robocopy.exe") Then
            $roboCopyCMD = @ScriptDir & "\robocopy.exe "
        ElseIf FileExists(@ScriptDir & "\getrobocopy.exe") Then
            ;Download Robocopy
            If RunWait(@ScriptDir & "\getrobocopy.exe", @TempDir) <> 0 Then
                Return SetError(1, "", 0) ; Can't find Robocopy
            EndIf
        Else
            Return SetError(1, "", 0) ; Can't find Robocopy
        EndIf
    WEnd

    $workingdir = $source
    ; Check source exists
    If StringRight($source, 1) = "\" Then
        ; remove trailing slash \
        $source = StringLeft($source, StringLen($source) - 1)
    EndIf
    If Not FileExists($source) Then
        ; source doesn't exist
        Return SetError(2, "", 0) ; Source doesn't Exist
    EndIf
    $source = _RCProcessFilelist($source)

    ; Check Target reachable
    If StringRight($target, 1) = "\" Then
        ; remove trailing slash \
        $target = StringLeft($target, StringLen($target) - 1)
    EndIf
    If Not FileExists($target) Then
        If Not DirCreate($target) Then
            Return SetError(3, "", 0) ; Destination can't be created (eg maybe read only media)
        EndIf
    EndIf
    $target = _RCProcessFilelist($target)

    ;Workout robocopy switches from Meta
    While 1
        ;Loop until all metas are removed
        Select
            ;Case _RCcheckMeta("sub", "/E", $switches)
            Case _RCcheckMeta("mirror", "/MIR ", $switches)
            Case _RCcheckMeta("Add", "", $switches)
            Case _RCcheckMeta("Copy", "", $switches)
            Case _RCcheckMeta("CopySub", "/E", $switches)
            Case _RCcheckMeta("Move", "/MOVE", $switches)
            Case _RCcheckMeta("MoveSub", "/MOVE /E", $switches)
            Case Else
                ;MsgBox(0, "pause", "META EKSE")
                ExitLoop
        EndSelect
        ;MsgBox(0, "pause", "META")
    WEnd

    ;Include files
    If $includeFiles = "" Then $includeFiles = "*.*"
    $switches = _RCProcessFilelist($includeFiles) & " " & StringStripWS($switches, 1)

    ;Exclude files/folders
    If $excludeFiles <> "" Then
        $switches &= " /XF " & _RCProcessFilelist($excludeFiles)
    EndIf
    If $excludeFolders <> "" Then
        ; Add target to 'excluded files' in case tragete is inside source (eg source=c: target=c:\backup)
        $switches &= " /XD " & _RCProcessFilelist($excludeFolders) & " " & _RCProcessFilelist($target)
    EndIf

    ; LOG
    If $logFile <> "" Then
        If StringLeft($logFile, 1) = "+" Then
            $logFile = StringMid($logFile, 2, 255)
            $switches &= ' /log+:"' & $logFile & '"'
        Else
            $switches &= ' /log:"' & $logFile & '"'
        EndIf

        $hfile = FileOpen($logFile, 1)
        If $hfile = -1 Then
            Return SetError(4, "", 0) ;Can't append/create Log file error
        EndIf
        FileClose($hfile)
    EndIf

    ;Add extra switches if nessacary
    If Not StringInStr($switches, "/r:") Then
        ; retry count
        $switches &= " /r:0"
    EndIf
    If Not StringInStr($switches, "/w:") Then
        ; Wait time
        $switches &= " /w:0"
    EndIf
    If Not StringInStr($switches, "/ZB") Then
        ; Tries to copy files in restartable mode, but if that fails with an “Access Denied” error, switches automatically to Backup mode.
        ;TODO - MAY drop this out as slows copy ???
        $switches &= " /ZB"
    EndIf
    If Not StringInStr($switches, "/FFT") Then
        ; Assume FAT File Times (2-second granularity). Useful for copying to third-party systems that declare a volume to be NTFS but only implement file times with a 2-second granularity..
        $switches &= " /FFT"
    EndIf
    If Not StringInStr($switches, "/COPY:") Then
        ; Copies Data, Attributes, Timestamps only (SEE
        $switches &= " /COPY:DAT"
    EndIf
    ; Wasn't as useful as it looked-  removed
    ;If Not StringInStr($switches, "/ETA") Then
    ;   ; Shows estimated time of completion for copied files.
    ;   $switches &= " /ETA"
    ;EndIf
    If StringInStr($switches, "/log") Then
        ; Displays output in the console window, in addition to directing it to the log file specified by /LOG or /LOG+.
        ; Needed for status and progress displays
        $switches &= " /TEE"
    EndIf
    ; DEGUB TODO - remove commenting after testing
    ;If $progressBar = "" Then
    ;   ; no progress bar so "suppress the display of progress information"
    ;   $switches &= " /NP "
    ;EndIf

#cs
    MsgBox(0, "ROBOCOPY", _
            "$source = <" & $source & ">" & @CR & _
            "$target = <" & $target & ">" & @CR & _
            "$switches = <" & $switches & ">" & @CR & _
            "$roboCopyCMD = <" & $roboCopyCMD & " " & $source & " " & $target & " " & $switches & ">" & @CR & _
            "")
            #ce
    ConsoleWrite($roboCopyCMD & " " & $source & " " & $target & " " & $switches & @CR)

    ;Exit

    ; run robocopy
    Local $robocopyPID = Run('"' & $roboCopyCMD & '" ' & $source & " " & $target & " " & $switches, $workingdir, @SW_HIDE, $STDIN_CHILD + $STDOUT_CHILD)
    If $robocopyPID = 0 Then
        Return SetError(5, "", 0) ; Robocopy failed to run
    Else
        Local $hrobocopy = _ProcessGetHandle($robocopyPID)
        ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $hrobocopy = ' & $hrobocopy & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
        If @error Then MsgBox(0,"Error _ProcessGetHandle", "@error = " & @error)

        Local $cmdOut, $cmdoutlast, $buffer, $loopCount, $buffer2, $sPosit
        ;Local $cmdOutErr = 1 ; so loop is run at least once
        While 1 ; ProcessExists($robocopyPID); Or ($cmdOutErr = 0) ; $cmdOurErr - used to wait untill ALL data is extracted from command line

            $cmdOut = StdoutRead($robocopyPID)
            ;$cmdOutErr = @error
            If @error Then ExitLoop

            If $cmdOut <> $cmdoutlast Then
                $cmdoutlast = $cmdOut
                $fnewdata = True
                $buffer &= $cmdOut ; Stores all data for processing

                ;interpret $BUFFER data
                ;extract and send to $statusFunc() the buffer string up to and including the last @CRLF
                $posit = StringInStr($buffer, @CRLF, 0, -1) + 2
                If Not @error Then
                    $buffer2 = StringStripWS(StringLeft($buffer, $posit), 2)
                    If $buffer2 <> "" Then
                        ;ConsoleWrite($posit & "|" & StringLen($buffer) & "<<" & $buffer2 & ">>" & Asc(StringMid($buffer, $posit, 1)) & "|" & $buffer & "|" & @CR)
                        Execute($outputFunction & '("' & $buffer2 & '")')
                    EndIf
                    $buffer = StringMid($buffer, $posit, 1000)
                    ;ConsoleWrite("<<<" & StringMid($buffer, $posit, 1000) & ">>>" & @CR)

                EndIf
            EndIf ; $cmdOut
            ;endif ; $outputFunction

            Sleep(20)
        WEnd

        Local $iReturnCode = _ProcessGetExitCode($hrobocopy)
        ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $iReturnCode = ' & $iReturnCode & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
        If @error Then MsgBox(0,"Error _ProcessGetExitCode", "@error = " & @error)
        #cs
            BITMAP
            Value   Meaning If Set
            16  Serious error. Robocopy did not copy any files. This is either a usage error or an error due to insufficient access privileges on the source or destination directories.
            8   Some files or directories could not be copied (copy errors occurred and the retry limit was exceeded). Check these errors further.
            4   Some Mismatched files or directories were detected. Examine the output log. Housekeeping is probably necessary.
            2   Some Extra files or directories were detected. Examine the output log. Some housekeeping may be needed.
            1   One or more files were copied successfully (that is, new files have arrived).
            0   No errors occurred, and no copying was done. The source and destination directory trees are completely synchronized.
        #ce
        _ProcessCloseHandle($hrobocopy)
        If @error Then MsgBox(0,"Error _ProcessCloseHandle", "@error = " & @error)

        Return $iReturnCode
    EndIf

EndFunc   ;==>_robocopy

Func _RCcheckMeta($meta, $metaSwitch, ByRef $switches)
    ;check if meta is the first part of $switches or whole of switches
    ; if so replace with Robocopy switches for that meta
    $meta = StringLower($meta)
    If StringInStr($switches, $meta & " ") = 1 Or StringLower($switches) = $meta Then
        $switches = StringStripWS(StringStripWS($metaSwitch & " " & StringStripWS(StringMid($switches, StringLen($meta) + 1, 100), 1), 1), 2)
        Return True
    Else
        Return False
    EndIf
EndFunc   ;==>_RCcheckMeta

Func _RCProcessFilelist($fileList)
    ; $includeFiles = either a comma delimited string or Array of strings
    Local $sReturn = ""

    If Not IsArray($fileList) Then
        ;Standard string so convert to array
        $fileList = StringSplit($fileList, ",", 2)
    EndIf

    For $item = 0 To UBound($fileList) - 1
        $fileList[$item] = StringStripWS(StringStripWS($fileList[$item], 1), 2)
        If StringLeft($fileList[$item], 1) <> '"' Then ;Search for inverted commas as first character
            $fileList[$item] = '"' & $fileList[$item]
        EndIf
        If StringRight($fileList[$item], 1) <> '"' Then ;Search for inverted commas as first character
            $fileList[$item] &= '"'
        EndIf
        $sReturn &= " " & $fileList[$item]
    Next
    Return StringStripWS($sReturn, 1) ; Strip leading space
EndFunc   ;==>_RCProcessFilelist

; Return handle of given PID
Func _ProcessGetHandle($iPID)
    Local Const $PROCESS_QUERY_INFORMATION = 0x0400
    Local $avRET = DllCall("kernel32.dll", "ptr", "OpenProcess", "int", $PROCESS_QUERY_INFORMATION, "int", 0, "int", $iPID)
    If @error Then
        Return SetError(1, 0, 0)
    Else
        Return $avRET[0]
    EndIf
EndFunc   ;==>_ProcessGetHandle

; Close process handle
Func _ProcessCloseHandle($hProc)
    Local $avRET = DllCall("kernel32.dll", "int", "CloseHandle", "ptr", $hProc)
    If @error Then
        Return SetError(1, 0, 0)
    Else
        Return 1
    EndIf
EndFunc   ;==>_ProcessCloseHandle

; Get process exit code from handle
Func _ProcessGetExitCode($hProc)
    Local $t_ExitCode = DllStructCreate("int")
    Local $avRET = DllCall("kernel32.dll", "int", "GetExitCodeProcess", "ptr", $hProc, "ptr", DllStructGetPtr($t_ExitCode))
    If @error Then
        Return SetError(1, 0, 0)
    Else
        Return DllStructGetData($t_ExitCode, 1)
    EndIf
EndFunc   ;==>_ProcessGetExitCode

Any feedback would be appreciated!

Thank you

John Morrison

aka

Storm-E

EDIT: 23/02/2011 - Fix so source can be set to "c:\"

Edited by storme
Link to comment
Share on other sites

Thanks for posting this

I run robocopy a lot from commandline for doing cutomer backups and was thinking about making an autoit version this will give me a push in the right direction.

many thanks

Chimaera

Link to comment
Share on other sites

  • 3 months later...

Just found a little bug.

Robocopy.exe rejects a source of "C:\" but "C:" is ok.

Don't ask me why that is just what it does.

The big problem is if you specify "C:" as the source it will start copying from the "current" directory (IE where ever you are).

The fix (OP updated) is to set the "working directory" to teh specified source and remove any trailing back slashes.

Thanks Chimaera for bringing that robocopy "feature" to light so I could fix it. :)

John Morrison

AKA

Storm-E

Link to comment
Share on other sites

  • 2 months later...
  • 6 years later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...