Characteristics are:
- It uses curl, included in Windows 10 version 1803 or later.
- It doesn't analyze Total Commander's website, but simply assumes the installers are at a fixed location on the server. If the position on the server changes, the tool needs to be adjusted.
- You can specify whether to download final version/beta version, 32-bit/64-bit/combined installer, in various combinations.
- Files that already exist locally, are not downloaded unless the /F parameter is used.
- Parameter /DD cleans up duplicate installers after a download (or attempt to do so).
- You can also specify a Total Commander version to download, but it is trial and error to see whether the installer for that version exists in the fixed location on the server (probably works for recent versions).
- Use parameter /? for help, parameter /P adds a pause when the batch file ends.

Filename:
Code: Select all
tcdownload.cmd
Code: Select all
@echo off
Rem
Rem tcdownload.cmd v1.2b, by user white, Total Commander forum (https://www.ghisler.ch/board/)
Rem
Rem Downloads latest version of Total Commander.
Rem When beta testing is ongoing, the latest beta version can also be downloaded.
Rem
Rem This tool doesn't check the website for updates, but uses the same method
Rem as Total Commander for checking for updates. A new version may already be
Rem available without this tool (or Total Commander) showing it. This tool may
Rem detect a new version sooner though.
Rem
Rem This tool assumes installers are at a fixed location on the server. If the
Rem position on the server changes, downloads will fail and this tool need to
Rem be adjusted (see URL masks below).
Rem
Rem One way to use this tool, is to place this batch file in the downloads
Rem folder and drag the file onto Total Commander's button bar. Edit the button
Rem and add parameter /P (pause). If Total Commander reports that a newer
Rem version is available, you can click the button to download Total Commander.
Rem
Rem Use parameter /? for help.
Rem
Rem Requirements
Rem curl Included in Windows 10 version 1803 or later.
Rem certutil -hashfile Included in Window 7 or later.
Rem
:: Disable delayed expansion to allow "!" character in passed parameters
setlocal EnableExtensions DisableDelayedExpansion
:: Settings
:: Name and version of this tool
set "tool_name_and_version=TCDOWNLOAD v1.2b"
:: URLs for downloading TC, if the tool no longer works, check if the URLs need adjusting
set "mask_url_final=https://www.totalcommander.ch/[v_major][v_minor]/tcmd[v_major][v_minor][v_patch]x[bitness].exe"
set "mask_url_beta=https://www.totalcommander.ch/beta/tc[v_major][v_minor]x[bitness]_[v_bnumber].exe"
:: Codes for beta and release candidate used in remote filenames
set "code_beta=b"
set "code_rc=rc"
:: Default download folder (default: current folder)
set "default_download_folder="
:: Default download list for 64-bit Windows (to download all 6 installers by default use: 32 64 32_64 b32 b64 b32_64)
set "default_download_list_64=64"
:: Default download list for 32-bit Windows
set "default_download_list_32=32"
:: Force downloading if file already exists locally
set "flag_force_download="
:: Delete duplicates after downloading
set "flag_delete_duplicates="
:: Pause before the script ends (no pause if undefined)
set "flag_end_pause="
:: Command used to pause before the script ends
set "cmd_end_pause=echo:&pause"
:: Command used for determining the latest TC version
set "cmd_to_get_version=nslookup -type=TXT [version_domain] [dns_server] 2>nul"
:: Domain names configured with fake IPv4 addresses to hold latest version info
set "domain_final_version=releaseversion.ghisler.com"
set "domain_beta_version=releasebeta.ghisler.com"
:: Default DNS server used for nslookup (8.8.8.8 = dns.google)
set "default_dns_server=8.8.8.8"
:: Download program and parameters
set "download_program=curl.exe"
set "download_params=--silent --show-error --fail --location --output"
:: Command to create hash
set "cmd_create_hash=certutil -hashfile"
:: Hash algorithm used to check for duplicate files (together with file size)
set "dupchk_hash_algo=SHA256"
:: Hash length of used hash algorithm
set "dupchk_hash_length=64"
:: Messages and texts
set "msg_header=%tool_name_and_version%"
set "msg_latest_version=Latest final version : [version]"
set "msg_latest_bversion=Latest beta version : [version]"
set "msg_flag_specified_version=Requested version : [version]"
set "msg_beta_not_available=> No beta for a new release is available at this time."
set "msg_download_file_exists1=Download: "
set "msg_download_file_exists2=. File already exists locally. Download skipped."
set "msg_downloading=Downloading "
set "msg_downloading_to1=Downloading "
set "msg_downloading_to2= to "
set "msg_deleting_duplicate=* Deleting duplicate file: "
set "error_invalid_param=Invalid parameter: "
set "error_no_download_folder=ERROR: Nonexistent or inaccessible folder: "
set "error_no_download_program=ERROR: 'curl' is not available. (Included in Windows 10 version 1803 or later.)"
set "error_final_unknown=ERROR: Cannot determine latest final version. Final version not downloaded."
set "error_beta_unknown=ERROR: Cannot determine latest beta version. Beta version not downloaded."
set "txt_help_header=Downloads latest Total Commander version."
set "txt_unknown_version=<unknown>"
set "txt_beta_not_available=<not available>"
set "txt_beta= beta "
set "txt_rc= release candidate "
goto :start_program
::
:: display_usage: Show usage information
::
:display_usage
echo:
echo TCDOWNLOAD [drive:][path] [/32] [/64] [/32_64] [/B32] [/B64] [/B32_64]
echo [/ALL] [/ALLF] [/ALLB] [/F] [/DD] [/V:version] [dns_server] [/P] [/?]
echo:
echo [drive:][path] Specifies the download folder.
echo [/32] Download 32-bit final version.
echo [/64] Download 64-bit final version.
echo [/32_64] Download 32+64-bit final version.
echo [/B32] Download 32-bit beta version.
echo [/B64] Download 64-bit beta version.
echo [/B32_64] Download 32+64-bit beta version.
echo [/ALL] Same as [/32] [/64] [/32_64] [/B32] [/B64] [/B32_64].
echo [/ALLF] Same as [/32] [/64] [/32_64].
echo [/ALLB] Same as [/B32] [/B64] [/B32_64].
echo [/F] Force downloading if file already exists locally.
echo [/DD] Delete duplicate "filename (n).exe" installers via size and hash check.
echo [/V:version] Specifies version (e.g. /v:10.52, /v:11.00b1, /v:11.55rc7).
echo dns_server DNS server (IPv4) used for nslookup (default: 8.8.8.8 = dns.google)
echo [/P] Pause after execution.
echo [/?] Display help. Shows more help when combined with /DD or /V:version.
echo:
echo Parameters can be used in any order.
echo Note: Downloading the latest beta version is only available during active beta testing.
echo:
echo Default download: 32-bit final version on 32-bit Windows, 64-bit version otherwise.
echo Requirements: Windows 10 version 1803 or later.
if defined flag_delete_duplicates (
echo:
echo The /DD switch deletes duplicate files after downloading a file. It scans for matching
echo files with sequence number 0 to 99. It skips read-only, hidden, and system files.
echo The file with no sequence number is kept, or the one with the lowest number.
echo:
echo Note that files that are deleted are not necessarily identical to each other. They may
echo be identical to different remaining files.
)
if defined flag_version_param (
echo:
echo The /V:version option lets you download a specific final or beta release instead of
echo the latest. This serves as a raw additional feature, since the tool's main purpose is
echo fetching the latest version. It constructs the download URL from your version string
echo using a fixed format; if no file exists at that URL, the download fails.
)
exit /b
:start_program
:: General steps done by the program:
:: * Parse parameters, set variables
:: * Show error or help text
:: * Show Total Commander version information
:: * Download the installer files
:: Parse parameters, set variables
set "script_folder=%~dp0"
set "v_major="
set "v_minor="
set "v_patch_letter="
set "bv_major="
set "bv_minor="
set "bv_beta_number="
set "exit_code=0"
set "download_folder="
set "download_list="
set "dns_server="
set "flag_help="
set "invalid_param="
set "flag_specified_version="
set "flag_version_param="
set "all_finals= 32 64 32_64 "
set "all_betas= b32 b64 b32_64 "
goto :start_parsing
:next_param
shift /1
:start_parsing
set "p=%~1"
if not defined p goto :parse_end
:: Remove quotes to prevent errors
set "p=%p:"=%"
if "%p:~0,1%"=="/" goto :check_named_params
:: Check for parameters dns server and download folder
if not defined dns_server (
:: Scan first 16 characters for IPv4 address
echo "%p:~0,15%_%p:~15,1%"|findstr /rc:"^\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*_\"$" >nul
if not errorlevel 1 call :check_ipv4 "%p%" && (set "dns_server=%p%" & goto :next_param)
)
:: Parameter is not dns server, so assume it is the download folder
if not defined download_folder set "download_folder=%p%" & goto :next_param
:check_named_params
for %%i in (%all_finals%%all_betas%) do (
if /i "%p:~0,8%"=="/%%~i" set "download_list=%download_list% %%~i" & goto :next_param
)
if /i "%p:~0,5%"=="/all" set "download_list=%download_list%%all_finals%%all_betas%" & goto :next_param
if /i "%p:~0,6%"=="/allf" set "download_list=%download_list%%all_finals%" & goto :next_param
if /i "%p:~0,6%"=="/allb" set "download_list=%download_list%%all_betas%" & goto :next_param
if "%p:~0,3%"=="/?" set "flag_help=1" & goto :next_param
if /i "%p:~0,3%"=="/f" set "flag_force_download=1" & goto :next_param
if /i "%p:~0,4%"=="/dd" set "flag_delete_duplicates=1" & goto :next_param
if /i "%p:~0,3%"=="/v:" (
set "flag_version_param=1"
if not defined flag_specified_version (
:: Check formatting and length of specified version (max 9 chars)
echo "%p:~3,9%_%p:~12,1%"|findstr /rc:"^\"[0-9][0-9]*\.[0-9][0-9][0-9a-z]*_\"$" >nul
if not errorlevel 1 (
:: Parse specified version
call :parse_version "%p:~3%" v_major v_minor v_patch_letter bv_major bv_minor bv_beta_number
if not errorlevel 1 set "flag_specified_version=1" & goto :next_param
)
)
)
if /i "%p:~0,3%"=="/p" set "flag_end_pause=1" & goto :next_param
:: A parameter not parsed yet is an invalid parameter
if not defined invalid_param set "invalid_param=%p%" & goto :next_param
:parse_end
:: Parameters are stored, enable delayed expansion
setlocal EnableDelayedExpansion
:: Show help or error
if defined flag_help (
echo !txt_help_header!
call :display_usage
goto :end_script
)
if defined invalid_param (
echo !error_invalid_param!"!invalid_param!"
call :display_usage
goto :end_script
)
:: Use defaults if necessary
if not defined download_folder set "download_folder=!default_download_folder!"
if not defined download_list (
set "download_list=!default_download_list_64!"
if "%processor_architecture%"=="x86" if not defined processor_architew6432 (
set "download_list=!default_download_list_32!"
)
)
if not defined dns_server set "dns_server=!default_dns_server!"
:: Change current folder to the download folder
if defined download_folder pushd "!download_folder!" 2>nul
If %errorlevel% NEQ 0 (
echo !error_no_download_folder!"!download_folder!"
goto :end_script
)
:: Check if download program is available
if not defined download_program goto :download_program_not_found
if exist "!download_program!" goto :download_program_found
:strip_folder
set "download_program=!download_program:*\=!"
if not defined download_program (
goto :download_program_not_found
) else (
if not "!download_program!"=="!download_program:\=!" goto :strip_folder
)
:: Check in the paths specified by the PATH environment variable
where /q "!download_program!" && goto :download_program_found
:: Search for download program within the script folder
set "found_program="
:: Disable delayed expansion to disable expansion of ^ and ! characters in %%f
setlocal DisableDelayedExpansion
for /f "eol= delims=" %%f in ('where /r "%script_folder%." "%download_program%" 2^>nul') do (
if not defined found_program set "found_program=%%~f"
)
if defined found_program set "found_program=%found_program:^=^^%"
if defined found_program set "found_program=%found_program:!=^!%"
endlocal & set "found_program=%found_program%"
if defined found_program set "download_program=!found_program!" & goto:download_program_found
:download_program_not_found
echo !error_no_download_program!
goto :end_script
:download_program_found
:: Get version info if no version specified
if defined flag_specified_version goto :end_retrieving_version_info
:: Fill in DNS server in command to get version info
set "cmd_to_get_version=!cmd_to_get_version:[dns_server]=%dns_server%!"
:: Get final version info
set "v_patch_num="
set "cmd=!cmd_to_get_version:[version_domain]=%domain_final_version%!"
:: Get tokens after the first 4 lines of the output
for /f "skip=4 tokens=2,3,4 delims=.;" %%a in ('"!cmd!"') do if not defined v_patch_num (
set "v_major=%%a"
set "v_minor=%%b"
set "v_patch_num=%%c"
)
:: Convert patch number to patch letter (0="", 1=a, 2=b, etc.)
if defined v_patch_num (
set "letters=_abcdefghijklmnopqrstuvwxyz"
set "v_patch_letter=!letters:~%v_patch_num%,1!"
set "v_patch_letter=!v_patch_letter:_=!"
)
:: Get beta version info
set "cmd=!cmd_to_get_version:[version_domain]=%domain_beta_version%!"
:: Get tokens after the first 4 lines of the output
for /f "skip=4 tokens=2,3,4 delims=.;" %%a in ('"!cmd!"') do if not defined bv_beta_number (
set "bv_major=%%a"
set "bv_minor=%%b"
set "bv_beta_number=%%c"
)
:: Test if it is a beta or release candidate, or if no beta is available
if defined bv_beta_number (
if !bv_beta_number! GEQ 229 (
set /a "bv_beta_number -= 228"
set "bv_beta_number=!code_rc!!bv_beta_number!"
) else if !bv_beta_number! GEQ 129 (
set /a "bv_beta_number -= 128"
set "bv_beta_number=!code_beta!!bv_beta_number!"
) else (
:: If there is no beta number, beta testing has finished
set "bv_beta_number="
:: Beta testing ended, but perhaps info about final wasn't updated yet, update if necessary
if defined v_major (
set "flag_update_version="
if !bv_major! GTR !v_major! set "flag_update_version=1"
if !bv_major! EQU !v_major! if !bv_minor! GTR !v_minor! set "flag_update_version=1"
if defined flag_update_version (
set "v_major=!bv_major!"
set "v_minor=!bv_minor!"
set "v_patch_letter="
)
)
)
)
:: Make sure minor version is 2 digits
if defined v_minor if "!v_minor:~1,1!"=="" set "v_minor=0!v_minor!"
if defined bv_minor if "!bv_minor:~1,1!"=="" set "bv_minor=0!bv_minor!"
:end_retrieving_version_info
:: Show header
if defined msg_header echo !msg_header!
echo:
:: Show version information
set "v_version=!txt_unknown_version!"
if defined v_major set "v_version=!v_major!.!v_minor!!v_patch_letter!"
set "bv_version=!txt_unknown_version!"
if defined bv_major (
set "bv_version=!txt_beta_not_available!"
if defined bv_beta_number (
set "bv_version=!bv_major!.!bv_minor!!bv_beta_number!"
set "bv_version=!bv_version:%code_beta%=%txt_beta%!"
set "bv_version=!bv_version:%code_rc%=%txt_rc%!"
)
)
if defined flag_specified_version (
if defined v_major (
set "msg=!msg_flag_specified_version:[version]=%v_version%!"
) else (
set "msg=!msg_flag_specified_version:[version]=%bv_version%!"
)
echo !msg!
) else (
set "msg=!msg_latest_version:[version]=%v_version%!"
echo !msg!
set "msg=!msg_latest_bversion:[version]=%bv_version%!"
echo !msg!
)
echo:
:: Pre-process download list
set "flag_final_skipped="
set "flag_beta_skipped="
set "new_list= "
:: Process download list
for %%i in (!download_list!) do (
set "download_item=%%~i"
if not "!all_finals!"=="!all_finals: %%~i =!" (
:: Final version download
if not defined v_major (
if defined flag_specified_version (
:: Convert any final download to beta download if a beta version was specified
set "download_item=b!download_item!"
) else (
:: Skip download if it cannot be downloaded
set "download_item="
set "flag_final_skipped=1"
)
)
) else if not "!all_betas!"=="!all_betas: %%~i =!" (
:: Beta version download
if not defined bv_beta_number (
if defined flag_specified_version (
:: Convert any beta download to final download if a final version was specified
set "download_item=!download_item:~1!"
) else (
:: Skip download if it cannot be downloaded
set "download_item="
set "flag_beta_skipped=1"
)
)
)
:: Filter out duplicate items on the download list
for %%j in ("!download_item!") do if "!new_list!"=="!new_list: %%~j =!" set "new_list=!new_list! !download_item! "
)
set "download_list=!new_list!"
:: Show error or message if downloads were skipped
if not defined flag_specified_version (
if defined flag_final_skipped echo !error_final_unknown!
if defined flag_beta_skipped if not defined bv_major (
echo !error_beta_unknown!
) else (
set "exit_code=1"
echo !msg_beta_not_available!
)
)
:: Fill in url masks as far as we can
set "mask_url_final=!mask_url_final:[v_major]=%v_major%!"
set "mask_url_final=!mask_url_final:[v_minor]=%v_minor%!"
set "mask_url_final=!mask_url_final:[v_patch]=%v_patch_letter%!"
set "mask_url_beta=!mask_url_beta:[v_major]=%bv_major%!"
set "mask_url_beta=!mask_url_beta:[v_minor]=%bv_minor%!"
set "mask_url_beta=!mask_url_beta:[v_bnumber]=%bv_beta_number%!"
:: Loop through download list and download each version
for %%i in (!download_list!) do (
if not "!all_finals!"=="!all_finals: %%~i =!" (
:: Final version download
set "url=!mask_url_final:[bitness]=%%~i!"
call :download_file url
) else if not "!all_betas!"=="!all_betas: %%~i =!" (
:: Beta version download, remove "b" to get bitness
set "download_item=%%~i"
for %%j in ("!download_item:b=!") do set "url=!mask_url_beta:[bitness]=%%~j!"
call :download_file url
)
)
:: Restore current folder if it was changed
if defined download_folder popd
:end_script
if defined flag_end_pause %cmd_end_pause%
exit /b !exit_code!
::
:: check_ipv4: Check if address is valid IPv4 address
::
:: Parameters:
:: %1: (Quoted) string containing 4 numbers separated by a dot. ("nnn.nnn.nnn.nnn")
::
:: Return value:
:: 0: Address is valid
:: 1: Address is invalid
::
:check_ipv4
setlocal EnableDelayedExpansion
set "address=%~1"
if not defined address exit /b 1
:: Remove leading zeros to prevent interpretation as octal numbers, code: (1%%a+1%%a-2%%a)
:: Divide by 256 to check if numbers are less than 256
:: If numbers are too big and assignment fails, invalid_ipv4 remains undefined
set "invalid_ipv4="
for /f "tokens=1-4 delims=." %%a in ("%address%") do (
set /a "invalid_ipv4=(1%%a+1%%a-2%%a)/256 + (1%%b+1%%b-2%%b)/256 + (1%%c+1%%c-2%%c)/256 + (1%%d+1%%d-2%%d)/256" 2>nul
)
if not defined invalid_ipv4 exit /b 1
if !invalid_ipv4! NEQ 0 exit /b 1
exit /b 0
::
:: parse_version: Validate and parse version string, setting version variables
::
:: Parameters:
:: %1: Version string to validate and parse (already matched with ^[0-9][0-9]*\.[0-9][0-9][0-9a-z]*)
:: %2: Variable name for major version (final release)
:: %3: Variable name for minor version (final release)
:: %4: Variable name for patch letter (final release, e.g., "a")
:: %5: Variable name for major version (beta/RC)
:: %6: Variable name for minor version (beta/RC)
:: %7: Variable name for beta/RC number (e.g., "b1", "rc99")
::
:: Return value:
:: 0: Version string is valid, variables set
:: 1: Version string is invalid
::
:: Global variables:
:: code_beta
:: code_rc
::
:parse_version
setlocal EnableDelayedExpansion
set "version=%~1"
if not defined version exit /b 1
:: Check if dot is at position 2 or 3 (major version is 1 or 2 digits)
set "idx="
if "!version:~1,1!"=="." set "idx=1"
if "!version:~2,1!"=="." set "idx=2"
if not defined idx exit /b 1
:: Extract major version (before the dot) and minor version after dot (2 digits)
set "major=!version:~0,%idx%!"
set /a "idx+=1"
set "minor=!version:~%idx%,2!"
set /a "idx+=2"
set "rest=!version:~%idx%!"
if not defined rest (
:: Final version without patch letter (e.g., 10.00)
endlocal & set "%2=%major%" & set "%3=%minor%" & set "%4="
exit /b 0
)
if "!rest:~0,1!" GEQ "a" if "!rest:~0,1!" LEQ "z" if "!rest:~1,1!"=="" (
:: Final version with patch letter (e.g., 10.00a)
endlocal & set "%2=%major%" & set "%3=%minor%" & set "%4=%rest%"
exit /b 0
)
:: Extract "b" or "rc"
set "b_type="
:: The variables code_beta and code_rc are used so minimal changes are needed if the filename scheme for the installers are changed
if "!rest:~0,1!"=="b" set "b_type=!code_beta!" & set "rest=!rest:~1!"
if "!rest:~0,2!"=="rc" set "b_type=!code_rc!" & set "rest=!rest:~2!"
if not defined b_type exit /b 1
if not defined rest exit /b 1
:: Extract beta/RC number (1 or 2 digits)
set "b_num="
if "!rest:~0,1!" GEQ "0" if "!rest:~0,1!" LEQ "9" if "!rest:~1,1!"=="" (
set "b_num=!rest!"
) else (
if "!rest:~1,1!" GEQ "0" if "!rest:~1,1!" LEQ "9" if "!rest:~2,1!"=="" set "b_num=!rest!"
)
if not defined b_num exit /b 1
:: Valid beta/RC version (e.g., 10.00b1, 10.00rc12)
endlocal & set "%5=%major%" & set "%6=%minor%" & set "%7=%b_type%%b_num%"
exit /b 0
::
:: download_file: Download file from URL
::
:: Parameters:
:: %1: Variable name holding the URL to download
::
:: Global variables (apart from constants):
:: flag_force_download: Force downloading if file already exists locally
:: flag_delete_duplicates: Delete duplicates after downloading
::
:download_file
setlocal EnableDelayedExpansion
set "url=!%~1!"
if not defined url exit /b 1
:: Get filename from url
set "filename=!url!"
:remove_folder_from_filename
set "filename=!filename:*/=!"
if not defined filename (
exit /b 1
) else (
if not "!filename!"=="!filename:*/=!" goto :remove_folder_from_filename
)
:: Determine local filename
set "local_filename=!filename!"
set "flag_skip_download="
if exist "!local_filename!" (
if defined flag_force_download (
call :get_available_filename local_filename
echo !msg_downloading_to1!"!url!"!msg_downloading_to2!"!local_filename!"
) else (
echo !msg_download_file_exists1!"!url!"!msg_download_file_exists2!
set "flag_skip_download=1"
)
) else (
echo !msg_downloading!"!url!"
)
:: Start downloading
set "flag_download_failed="
if not defined flag_skip_download (
"!download_program!" !download_params! "!local_filename!" "!url!"
if !errorlevel! NEQ 0 (
:: Download failed, clean up
if exist "!local_filename!" del "!local_filename!"
set "flag_download_failed=1"
)
)
:: Delete duplicate files, regardless whether download failed or was skipped
if defined flag_delete_duplicates call :delete_duplicates "!filename!"
:: Exitcode 1 if download failed
if defined flag_download_failed exit /b 1
exit /b 0
::
:: get_available_filename: Get a filename that is not in use
::
:: Parameters:
:: %1: Name of variable holding the filename
::
:get_available_filename
setlocal EnableDelayedExpansion
set "filename=!%~1!"
if not defined filename exit /b 1
if not exist "!filename!" exit /b 0
for %%f in ("!filename!") do (
set "basename=%%~nf"
set "ext=%%~xf"
)
set /a "seq=0"
:next_sequence_number
set /a "seq+=1"
set "filename=!basename! (!seq!)!ext!"
if exist "!filename!" goto :next_sequence_number
endlocal & set "%~1=%filename%"
exit /b 0
::
:: delete_duplicates: Delete duplicate "filename (n).exe" files
::
:: Parameters:
:: %1: Filename without sequence number
::
:delete_duplicates
setlocal EnableDelayedExpansion
set "basename=%~n1"
set "ext=%~x1"
if not defined basename exit /b
:: Clear variables with name that starts with "kept_file_"
for /f "delims==" %%v in ('set "kept_file_" 2^>nul') do set "%%v="
:: Create dir list
set "dir_list=!basename!!ext!"
for /l %%i in (0,1,99) do (
set dir_list=!dir_list! "!basename! (%%i)!ext!"
)
:: Check if there are more than 1 existing files we would check
set /a "file_count=0"
for /f "eol= delims=" %%f in ('dir /b /a-d-r-s-h %dir_list% 2^>nul') do (
set /a "file_count+=1"
if !file_count! EQU 2 goto :second_file_found
)
exit /b
:second_file_found
set /a "idx_end_char=dupchk_hash_length-1"
set /a "idx_next_char=dupchk_hash_length+0"
:: Loop through existing files, skipping read-only/hidden/system files
for /f "eol= delims=" %%f in ('dir /b /a-d-r-s-h %dir_list% 2^>nul') do (
set "fsize=%%~zf"
:: Create hash
set "hash="
set "token="
:: Get first token from second line of output
for /f "skip=1" %%h in ('!cmd_create_hash! "%%f" !dupchk_hash_algo!') do (
if not errorlevel 1 if not defined token set "token=%%h"
)
if defined token (
:: Check if token consists of hex digits
set token|findstr /irc:"^token=[0-9A-F]*$" >nul
:: Check if token length is hash length, before assuming it is the hash
if not errorlevel 1 if not "!token:~%idx_end_char%,1!"=="" if "!token:~%idx_next_char%,1!"=="" set "hash=!token!"
)
:: If hash is known, delete file if it is a duplicate file
if defined hash (
set "key=!fsize!_!hash!"
if defined kept_file_!key! (
echo !msg_deleting_duplicate!"%%f"
del "%%f"
) else (
set "kept_file_!key!=1"
)
)
)
exit /b
Rem
Rem History:
Rem
Rem 2025-06-30 Release v1.2b
Rem 2025-06-30 Support full path to download program with fallback to PATH and script folder
Rem 2025-06-30 Support curl folder containing ^ and ! characters
Rem
Rem 2025-06-29 Release v1.1b
Rem 2025-06-29 Search for curl within the script folder if it cannot be found
Rem 2025-06-29 Improved comments and help text
Rem
Rem 2025-06-28 Release v1.0b (first release)
Rem