batch-filebackup

Batch backup script that parse registry to get real user folder path and a for loop for the folders


I am writing a small script to make my customers easily backup they're data.
It is executed from an external drive to avoid letter issues, even if customer change those folders destination for another drive.

Everything goes well in the loop but for a reason that I can't explain, the loop stops after the third iteration and tell me that it is finished ...
I need it to be batch in order to be as simple as a double click for the elders.
Here it is, if a good soul can help me ?

So far : I have tried AI ... but they only add confusion on despair ;o) (AI my a** !!)
I have tried voodoo .. but I am not good enogh at it ...
I have tested all variables and they seemd fine ...

@echo off
setlocal enabledelayedexpansion

:: Define source variables (actual user folders paths from registry)
for /f "tokens=1,2*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "Desktop" 2^>nul') do set DeskDir=%%C
for /f "tokens=1,2*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "Personal" 2^>nul') do set DocsDir=%%C
for /f "tokens=1,3*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "My Pictures" 2^>nul') do set PicsDir=%%C
for /f "tokens=1,3*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "My Music" 2^>nul') do set MusicDir=%%C
for /f "tokens=1,3*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "My Video" 2^>nul') do set VidsDir=%%C
for /f "tokens=1,2*" %%A in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "{374DE290-123F-4565-9164-39C4925E467B}" 2^>nul') do set DownDir=%%C

:: Define the array of folders to backup
set "folders[0]=!DeskDir!"
set "folders[1]=!DocsDir!"
set "folders[2]=!PicsDir!"
set "folders[3]=!MusicDir!"
set "folders[4]=!VidsDir!"
set "folders[5]=!DownDir!"

:: Get the drive letter of the external drive where the script is located
for %%a in ("%~dp0") do set external_drive=%%~dpa

:: Set the destination drive for backup (same as the external drive)
set destination=%external_drive%%username%

:: Perform an incremental backup using robocopy
for /l %%i in (0,1,2,3,4,5) do (
    set folder=!folders[%%i]!
    for %%F in ("!folder!") do set "folder_name=%%~nF"
    robocopy "!folder!" "%destination%\!folder_name!" /S /E /XA:SH /Z /R:0 /W:0 /XJD /XJF /XF *.tmp *.bak /XD $RECYCLE.BIN /XD "System Volume Information" /XO /TEE /NDL /NP /ETA
)
echo Incremental Backup completed.
pause

Solution

  • The AI generated batch file code does not work because of for /l %%i in (0,1,2,3,4,5) do. The valid syntax for a for /L loop is explained by the usage help of the Windows command FOR output on running in a command prompt window for /?.

    Most of the AI generated code does not make much sense at all. There are first assigned the folder paths to named environment variables. Next the folder paths are assigned to other environment variables with a naming scheme looking like an array of folder paths. Why are not assigned the folder paths directly to the environment variables which are used later? Then a horrible loop of wrong syntax is used for processing the array looking like environment variables. There is just a lot of unnecessary code making the task more complicated than necessary and let it fail if a folder path contains one or more exclamation marks or a folder path read from registry ends with a backslash, or a registry value could not be found at all.

    There could be used the following batch file for the backup operation task:

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "DestinationFolder=%~d0\%USERNAME%"
    (for %%# in ("Desktop|Desktop" "Personal|Documents" "My Pictures,Pictures" "My Music|Music" "My Video|Videos" "{374DE290-123F-4565-9164-39C4925E467B}|Downloads") do for /F "tokens=1,2 delims=|" %%G in (%%#) do call :BackupFolder "%%G" "%%H") & goto EndBatch
    :BackupFolder
    set "SourceFolder="
    set "ValueName=%~1"
    if "%ValueName: =%" == "%ValueName%" goto SimpleQuery
    for /F "skip=2 tokens=1-3*" %%I in ('%SystemRoot%\System32\reg.exe QUERY "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" /v "%ValueName%" 2^>nul') do if /I "%%I %%J" == "%ValueName%" if not "%%~L" == "" if "%%K" == "REG_SZ" (set "SourceFolder=%%~L") else if "%%K" == "REG_EXPAND_SZ" call set "SourceFolder=%%~L"
    if not defined SourceFolder for /F "skip=2 tokens=1-3*" %%I in ('%SystemRoot%\System32\reg.exe QUERY "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "%ValueName%" 2^>nul') do if /I "%%I %%J" == "%ValueName%" if not "%%~L" == "" if "%%K" == "REG_SZ" (set "SourceFolder=%%~L") else if "%%K" == "REG_EXPAND_SZ" call set "SourceFolder=%%~L"
    goto CheckSource
    :SimpleQuery
    for /F "skip=2 tokens=1,2*" %%I in ('%SystemRoot%\System32\reg.exe QUERY "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" /v "%ValueName%" 2^>nul') do if /I "%%I" == "%ValueName%" if not "%%~K" == "" if "%%J" == "REG_SZ" (set "SourceFolder=%%~K") else if "%%J" == "REG_EXPAND_SZ" call set "SourceFolder=%%~K"
    if not defined SourceFolder for /F "skip=2 tokens=1,2*" %%I in ('%SystemRoot%\System32\reg.exe QUERY "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "%ValueName%" 2^>nul') do if /I "%%I" == "%ValueName%" if not "%%~K" == "" if "%%J" == "REG_SZ" (set "SourceFolder=%%~K") else if "%%J" == "REG_EXPAND_SZ" call set "SourceFolder=%%~K"
    :CheckSource
    if not defined SourceFolder echo Folder path for %2 not found.& goto :EOF
    echo(
    echo Backup of %2 ...
    if "%SourceFolder:~-1%" == "\" for %%I in ("%SourceFolder%.") do if "%%~nxI" == "" (set "SourceFolder=%%~fI\") else set "SourceFolder=%%~fI"
    %SystemRoot%\System32\robocopy.exe "%SourceFolder%" "%DestinationFolder%\%~2" /E /ETA /NDL /NP /XA:SH /XD $RECYCLE.BIN /XD "System Volume Information" /XF *.bak *.tmp /XO /XJ /R:0 /W:0
    goto :EOF
    :EndBatch
    echo(
    echo Incremental backup completed.
    echo(
    endlocal
    pause
    

    The first two command lines define completely the required execution environment with:

    1. Command echo mode turned off.
    2. Command extensions enabled as required by the batch file.
    3. Delayed variable expansion disabled for processing also correct folder paths with one or more !.

    The base destination folder path is defined as concatenation of the drive letter with colon of the batch file storage location and the name of the account of the current user. There could be used as third command line also:

    set "DestinationFolder=%~dp0%USERNAME%"
    

    That would create the user account related backups in a subdirectory of the batch file directory if the batch file is not in the root directory of the storage media.

    The fourth command line results in processing five pairs of registry value name and destination folder name. A vertical bar not possible in a folder name is used as separator between the two names. Each string pair is split up by the for /F loop on same command line and passing the two names as arguments to the subroutine BackupFolder starting already on next line in the batch file. The additional command goto EndBatch after the unconditional command operator & results in the continuation of the batch file processing below the label :EndBatch after the first FOR loop finished processing the five string pairs.

    The code for querying the folder path from the registry of the current user is described in full details in my answer on How to create a directory in the user's desktop directory? There must be two variants supported by this batch file because of some registry value names contain a space character.

    Note: Run the following command line on Windows 10/11 in a command prompt window:

    reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
    

    There is output among other lines the line:

    !Do not use this registry key    REG_SZ    Use the SHGetFolderPath or SHGetKnownFolderPath function instead
    

    Such a line is not output on running the command line with User Shell Folders instead of Shell Folders. There should be used preferred since more than 25 years the User Shell Folders registry key. The usage of SHGetFolderPath or SHGetKnownFolderPath is not easy possible in a batch file as being C++ library functions.

    There is an information output for the user for which folder is currently running the backup process. This information could be enough, but the options used for ROBOCOPY results in lots of additional output during the copying of the files.

    The batch file detects a missing registry value and informs the user about the missing path instead of running in an undefined behavior as the AI generated code. The batch file handles also folder paths with one or more & and I correct while the AI generated code fails on such uncommon but possible folder paths.

    ROBOCOPY has a special interpreting regarding to a backlash in an argument string. If the character \ is left to one more \ or " than the following backslash respectively double quote character is interpreted as literal character. A backslash left to any other character is interpreted literally and not as escape character for the next character.

    A folder path read from the registry can end with a backslash. That is not usual for the five processed folder paths but nevertheless possible. It is not possible to remove the backslash at the end of the source folder path if there is one because of the source folder path could be also the root directory of a drive like F:\ and just F: would reference the current directory on drive F: instead of the root directory of that drive. Please read the Microsoft documentation about Naming Files, Paths, and Namespaces for more details.

    The used solution is appending one more backlash at the end of the source folder path if the source folder path ends with one or more backslashes and is the root directory of a drive. The source folder path is redefined otherwise with all backslashes at the end removed from the folder path.

    The usage of "%SourceFolder%" on the ROBOCOPY command line works now even if the source folder path is the root directory of a drive.

    To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

    Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded reg command line with using a separate command process started in background with %ComSpec% /c and the command line within ' appended as additional arguments.

    See also: