Windows – How to optionally copy and rename a file in Windows

cmd.exerobocopywindowsxcopy

Basically, in an automated batch file, I want to copy and rename a file if the destination file is missing or older. There are several variants, which do not quite work:

copy /Y c:\source\a.file c:\dest\b.file 

– always copies, no /D option or something

xcopy /Y /D c:\source\a.file c:\dest\b.file

– if destination does not exist, tries to ask if the destination is a file or directory, creating havoc in the automated build.

robocopy /XO ... 

– does not support renaming the file.

It seems Windows has not managed to replicate Unix "cp -u" in 20+ years, or am I overlooking something?

Best Answer

This may be more than you need but with the proper tweaking it should do the job. I did some experimenting and it seems that if you use xcopy with the /D option and specify a destination filename, you will get a copy whether the files are the same or not. (Seems like it's basing the compare off the destination filename instead of the source.) So I only used xcopy to do the comparison but not actually to do the copy. (Thanks selbie for suggesting the /L option.) It seems rather large but if you remove the comments it's pretty small. This example copies and renames files that exist in the destination but are newer using the current date and time. It copies but does not rename files that don't exist in the destination (can be easily changed). And does not copy files that are the same. I can help you modify it to fit your specific needs.

@ECHO OFF
REM ### Note that this assumes all filenames end with ".txt".  This can be changed to suit your needs ###

REM ### Needed so the variables will expand properly within the FOR DO loops ###
Setlocal EnableDelayedExpansion

REM ### Set the source file(s) and call the "CopyIt" subroutine ###
SET Sourcefile=Test 1.txt
CALL :CopyIt

SET Sourcefile=Test2.txt
CALL :CopyIt

REM ### End of the program.  Otherwise the subroutine will run again ###
REM ### You can also just put an "EXIT" command here ###
GOTO END

REM ### The subroutine ###
:CopyIt
    REM ### Set the date and time to a variable called "DaTime" and remove offending ###
    REM ### characters (/ and :) that can't be used in a filename ###
    FOR /f "tokens=1-4 delims=/ " %%a in ("!date!") do SET DaTime=%%b-%%c-%%d
    FOR /f "tokens=1-3 delims=':'" %%e in ("!time!") do SET DaTime=!DaTime!_%%e-%%f-%%g

    REM ### Set a variable called "DestFile" to source filename minus ".txt" ###
    REM ### to be used to set the new destination file name + "DaTime" ###
    SET Destfile=!Sourcefile:.txt=!

    REM ### Check to see if the source filename exists in the destination directory ###
    DIR "C:\_Dest\!Sourcefile!" > NUL
    IF !ErrorLevel!==0 (
        REM ### If it does exist, set the loop count to 0 ###
        SET /a Count=0
        REM ### Have xcopy check to see if the source file is newer, but only report and not copy ###
        REM ### Thanks to selbie for suggesting the /L option ###
        FOR /f "tokens=1 delims=0" %%s in ('xcopy /Y /D /L "C:\_Source\!Sourcefile!" "C:\_Dest\"') DO (
            REM ### Increment the loop count ###
            SET /a Count=!Count!+1
            REM ### Only pick up the first iteration.  If the files are different, it will be the full path and the filename ###
            If !Count!==1 SET Source=%%s
        )
        REM ### If the source is the same as the destination, then !Source! will = " File(s)".  Change it to NONE ###
        SET Source=!Source: File^(s^)=NONE!
        REM ### If it's not equal to NONE, copy the file and rename it to source file + date & time + .txt ###
        IF !Source! NEQ NONE COPY /Y "!Source!" "C:\_Dest\!Destfile!-!DaTime!.txt"
    ) ELSE (
        REM ### If it does not exist, copy the file without renaming it ###
        COPY /Y "C:\_Source\!Sourcefile!" "C:\_Dest\"
    )
REM ### Exit the subroutine ###
EXIT /b

:END
pause
Related Question