8

I would like to compare two folders, strictly for filenames. (Not the file contents). I am on windows 10 and would like to use command line tools.

Here is scenario that may explain the issue.

Folders
-------
source                  source2 
   - file_1                 - file_1
   - file_2                 - file_2
   - file_3                 - file_4
   - file_5                 - file_5
   - file_6                 - file_7


Output should be:
-----------------
Source  -> Missing files file_4  
Source2 -> Missing files file_3  

I did some research, and there are many diff tools, but I am not interested in comparing the file contents. All I want is compare two folders and output which files are missing in both folders (compared to the other one).

I prefer not to use Powershell.

Thank you.

2
  • Related:  How to compare folders in Windows 7. Commented Feb 3, 2020 at 20:43
  • I totally agree with your desire to use the command line (I am a unix user ^^) but for this case, I recommend instead: use : Total Commander "compare directories" function, or even better the "synchronise directory" one (and you can adjust several things: for ex: you can tell it to ignore this or that type of files, etc. many tweaks possible and a very very handy command. Right-clicking also allow you to unmark (or mark "copy to right" or "copy to left") a selection, etc. highly recommended tool. And once you fall in love with it, you can buy it (it's 100% useable without buying, to test it) Commented Dec 18, 2020 at 21:15

5 Answers 5

12

robocopy (included in recent Windows versions) can do this in one pass:

given \source\ and \source2\ with some files which are common and files which exist only in either folder, running

robocopy source source2 /L /NJH /NJS /NP /NS

yields

D:\Users\me\test\source\
         *EXTRA Datei                  only_in_source2.txt
           Neue Datei                  only_in_source.txt

where lines starting with a * denote files only in source2 (independent of the OS language), and other lines denote files only in source.

The options suppress various output items, and /L takes care that differences are listed only, not copied.

2
  • Thanks. This is exactly what I was looking for. Commented Feb 19, 2019 at 19:22
  • @user1016274 Find fast which files are missing after Windows Update. C:\Windows\Microsoft.NET\Framework\v3.5\ *EXTRA File Microsoft.Build.Tasks.v3.5.xml *EXTRA File Microsoft.CompactFramework.Build.Tasks.dll *EXTRA File Microsoft.CompactFramework.Common.targets *EXTRA File Microsoft.CompactFramework.CSharp.targets *EXTRA File Microsoft.CompactFramework.VisualBasic.targets Thanks
    – antonio
    Commented Nov 12, 2020 at 1:09
4

You can save the next code as ccomp.cmd, and then call it with the /? flag to find out how to use it (tested on Windows 7, Windows 10):

Syntax:

ccomp <dir_tree1> <dir_tree2>

Code:

@echo off

if "!ERRORLEVEL!" == "%ERRORLEVEL%" (
    REM if delayed expansion is enabled:
    (
        echo.
        echo ERROR: This script must be called in a Disabled Delayed Expansion block ^(default^)^^^!
        echo.
        call :PressAKey "Press a key to exit..."
        echo.
    )>con
    exit /b 1
)

setlocal disabledelayedexpansion

    if defined _first_time goto :label1MAIN

    REM Test CHCP:
    chcp /?>nul 2>nul||(
        (
            echo. & echo ERROR: Could not start chcp ^(necessary^)!
        )>con
        exit /b 1
    )

    REM Get the initial code page:
    call :GetCurrentCodePage _initial_CP

    REM Change the code page (character encoding) for "CON" (console) to 65001 (UTF-8):
    set _con_error=false
    mode con cp select=65001>nul 2>nul||(
        (
            echo. & echo WARNING: Could not change the code page for CON ^(Console^)!
        )>con
        set _con_error=true
    )

    set _help_flag=0
    set /a _count=1

    set "_script_path=%~Dpnx0"

    :repeat1MAIN
    if _%1_ == _""_ (
        REM "%1" is """"
        set _param%_count%=""
        shift
        set /a _count+=1
        goto :repeat1MAIN
    ) else (

        if /i "%~1" == "/?" (
            set _help_flag=1
            shift
            goto :repeat1MAIN
        ) else (
            if /i "%~1" == "/help" (
                set _help_flag=1
                shift
                goto :repeat1MAIN
            ) else (
                if /i "%~1" == "/h" (
                    set _help_flag=1
                    shift
                    goto :repeat1MAIN
                ) else (
                    if not "%~1" == "" (
                        REM if "%1" is null, it means that no more parameters are provided
                        set "_param%_count%=%~1"
                        shift
                        set /a _count+=1
                        goto :repeat1MAIN
                    )
                )
            )
        )
    )
    set /a _count-=1
    set /a _param_count=_count

    REM start Checking parameters \/
    if "%_help_flag%" == "1" (
        call :DisplayHelp
        exit /b 0
    ) else (
        if %_param_count% gtr 2 (
            (
                echo. & echo ERROR: Too many parameters!
            )>con
            exit /b 1
        ) else (
            if %_param_count% lss 2 (
                (
                    echo. & echo ERROR: Too few parameters!
                )>con
                exit /b 1
            )
        )
    )

    set "_error=false"
    if "%_param1%" == """" (
        (
            echo. & echo ERROR: First provided directory parameter must not be empty!
        )>con
        set "_error=true"
    ) else (
        call :TestIfDirAcccessible _param1 _is_param1_dir
        setlocal enabledelayedexpansion
        if not "!_is_param1_dir!" == "0" (
            endlocal
            (
                echo. & echo ERROR: First provided directory parameter: "%_param1%" is not a directory or is not accessible!
            )>con
            set "_error=true"
        ) else (
            endlocal
        )
    )
    if "%_param2%" == """" (
        (
            echo. & echo ERROR: Second provided directory parameter must not be empty!
        )>con
        set "_error=true"
    ) else (
        call :TestIfDirAcccessible _param2 _is_param2_dir
        setlocal enabledelayedexpansion
        if not "!_is_param2_dir!" == "0" (
            endlocal
            (
                echo. & echo ERROR: Second provided directory parameter "%_param2%" is not a directory or is not accessible!
            )>con
            set "_error=true"
        ) else (
            endlocal
        )
    )

    call :TestIfPathIsUNC _param1 _result1
    call :TestIfPathIsUNC _param2 _result2
    if "%_result1%" == "true" (
        set "_error=true"
        (
            echo. & echo ERROR: Path1: "%_param1%" seems to be a UNC path ^(contains \\^), and UNC paths are not supported by this program ^(but a UNC path can be mounted ^(for example by using pushd^), in order to make it accessible^)!
        )>con
    )
    if "%_result2%" == "true" (
        set "_error=true"
        (
            echo. & echo ERROR: Path2: "%_param2%" seems to be a UNC path ^(contains \\^), and UNC paths are not supported by this program ^(but a UNC path can be mounted ^(for example by using pushd^), in order to make it accessible^)!
        )>con
    )

    if "%_error%" == "true" (
        (
            echo. & call :PressAKey "Press a key to exit!"
            echo.
        )>con
        exit /b 1
    )

    cmd /u /c ^(echo.^&echo Path1: "%_param1%"^)
    cmd /u /c ^(echo.^&echo Path2: "%_param2%"^)

    REM end Checking parameters /\

    REM If everything seems ok, proceed to PROCESSING:

    pushd "%_param1%\">nul
    set "_param1=%CD%"
    if "%_param1:~-1%" == "\" set "_param1=%_param1:~0,-1%"
    popd>nul

    pushd "%_param2%\">nul
    set "_param2=%CD%"
    if "%_param2:~-1%" == "\" set "_param2=%_param2:~0,-1%"
    popd>nul

    call :ConvertDriveLetterToUpperCase _param1 _param1
    call :ConvertDriveLetterToUpperCase _param2 _param2

    call :GetStrLen _param1 _param1_len
    call :GetStrLen _param2 _param2_len

    pushd "%_script_path%\..">nul
    if not defined _first_time (
        call :EscapePathString _script_path _script_path_escaped
        call :EscapePathString _param1 _param1_escaped
        call :EscapePathString _param2 _param2_escaped
    )
    popd>nul

    REM Change the code page (character encoding) for "CON" (console) to 437 (ANSI):
    if "%_con_error%" == "false" (
        mode con cp select=437>nul 2>nul
    )

:label1MAIN
    REM CHCP 437 = ANSI CODE PAGE
    REM CHCP 65001 = UTF-8 CODE PAGE
    if not defined _first_time (
        set _first_time=defined

        chcp 65001>nul

        cmd /u /c ^(echo.^&echo Start time: %date% %time%^&echo.^)

        cmd /u /c ^(for /f ^"tokens=^*^" %%l in ^(^'^^^( setlocal^^^&^^^"%%_script_path_escaped%%^^^" ^^^"%%_param1_escaped%%^^^" ^^^"%%_param2_escaped%%^^^"^^^&title Sorting Results ^^^^^^^^^^^^^^^(finally^^^^^^^^^^^^^^^)^^^&endlocal ^^^)^^^|sort^'^) do @^(if not defined _once ^( set "_once=defined"^&echo %%l^) else echo %%l ^)^)^&if defined _once echo.
        set "_first_time="
        set "_second_time="
        set "_third_time="

        cmd /u /c ^(call echo End time: %%date%% %%time%%^)

        chcp 437>nul

        REM Restore the initial code page:
        chcp %_initial_CP%>nul 2>nul
    ) else (
        if not defined _second_time (
            set _second_time=defined
            (
                call :ProcedureAnalyzeFiles
            )
        ) else (
            if not defined _third_time (
                set "_third_time=defined"
                (
                    call :ProcedureProcessFilenamesLengthAndSize1
                    chcp 65001>nul
                    call :ProcedureProcessFilenamesLengthAndSize2
                    chcp 437>nul

                    @REM After sorting: "-" is displayed before all other characters:
                    @call echo ----------------------------------------""%%_count1%%""
                    @title Sorting results. Please wait...
                )
            )
        )
    )

endlocal & (
    if defined _first_time set "_first_time=%_first_time%"
    if defined _second_time set "_second_time=%_second_time%"
    if defined _third_time set "_third_time=%_third_time%"
)

goto :eof

REM \\\/// Next subroutines use jeb's syntax for working with delayed expansion: \\\///

:GetStrLen
REM - by jeb - adaptation

(
    setlocal EnableDelayedExpansion
        set "s=!%~1!#"
        set "len=0"
        for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
            if "!s:~%%P,1!" NEQ "" ( 
                set /a "len+=%%P"
                set "s=!s:~%%P!"
            )
        )
)
(
    endlocal
    set "%~2=%len%"
    exit /b
)

:EscapePathString

(   
    setlocal EnableDelayedExpansion
        set "string=!%~1!"
        call :GetStrLen string string_len
        set /a string_len-=1

        for /l %%i in (0,1,!string_len!) do (
            rem escape "^", "(", ")", "!", "&"
            if "!string:~%%i,1!" == "^" (
                set "result=!result!^^^^"
            )  else (
                if "!string:~%%i,1!" == "(" (
                    set "result=!result!^^^("
                ) else (
                    if "!string:~%%i,1!" == ")" (
                        set "result=!result!^^^)"
                    ) else (
                        if "!string:~%%i,1!" == "^!" (
                            set "result=!result!^^^!"
                        ) else (
                            if "!string:~%%i,1!" == "&" (
                                set "result=!result!^^^&"
                            ) else (
                                if "!string:~%%i,1!" == "%%" (
                                    set "result=!result!%%"
                                ) else (
                                    set "result=!result!!string:~%%i,1!"
                                )
                            )
                        )
                    )
                )
            )
        )
)
(
    endlocal
    set "%~2=%result%"
    exit /b
)

:TestIfDirAcccessible

(
    setlocal EnableDelayedExpansion
        set _returned_code=0
        (pushd "!%~1!">nul 2>nul||set "_returned_code=1")&&popd>nul
)
(
    endlocal
    set "%~2=%_returned_code%"
    exit /b
)

:TestIfPathIsUNC

(
    setlocal EnableDelayedExpansion
        set "_current_path=!%~1!"
        set "_is_unc_path=true"
        if "!_current_path:\\=!" == "!_current_path!" (
            set "_is_unc_path=false"
        )
)
(
    endlocal
    set "%~2=%_is_unc_path%"
    exit /b
)

:GetCurrentCodePage

(
    setlocal EnableDelayedExpansion
        set "_current_cp=-1"
        for /f "tokens=1,2* delims=:" %%a in ('chcp 2^>nul') do (
            set "_current_cp=%%~b"
        )
)
(
    endlocal
    set "%~1=%_current_cp%"
    exit /b
)

:ConvertDriveLetterToUpperCase

(
    setlocal EnableDelayedExpansion
        set "_path=!%~1!"
        for /f "tokens=1 delims=\" %%f in ('echo "!_path!"') do set "_path_drive=%%~df"
        set "_upper_drive=!_path_drive!"
        for %%D in (A: B: C: D: E: F: G: H: I: J: K: L: M: N: O: P: Q: R: S: T: U: V: W: X: Y: Z:) do (
            if /i "!_path_drive!" == "%%D" (
                set "_upper_drive=%%D"
                goto :endConvertDriveLetterToUpperCase
            )
        )
)
:endConvertDriveLetterToUpperCase
(
    endlocal
    set "%~1=%_upper_drive%%_path:~2%"
    exit /b
)

REM ///\\\ The subroutines above use jeb's syntax for working with delayed expansion: ///\\\


:GenerateShortName
    @set "%~1=%~s2"
@goto :eof

:GenerateShortNameSize
    @set "%~1=%~z2"
@goto :eof

:ProcedureAnalyzeFiles
    setlocal

        for /f "tokens=1,2,3,4,5* delims=/" %%p in ('chcp 65001^>nul^&^(setlocal^&^"%_script_path_escaped%^" ^"%_param1_escaped%^" ^"%_param2_escaped%^"^|sort^&endlocal^)^&chcp 437^>nul') do (
            if not defined _second_time_for1 (
                set /a _total_count=%%~p>nul 2>nul
                set /a _currrent_count=1
                set _second_time_for1=defined
            ) else (
                set /a _current_count+=1

                REM START PROCESSING:
                setlocal enabledelayedexpansion

                    title Analyzing file !_current_count! of !_total_count!...

                    set "_previous_file=!_current_file!"
                    set "_previous_file_type=!_current_file_type!"
                    set "_previous_file_visibility=!_current_file_visibility!"
                    set "_previous_file_dir=!_current_file_dir!"
                    REM Due to a bug in sort, an extra " is added at the end of the line, so we need to remove it:
                    set "_previous_file_size=!_current_file_size:"=!"
                    set "_previous_file_base_dir=!_current_file_base_dir!"

                    setlocal disabledelayedexpansion

                        set "_current_file=%%~p"
                        set "_current_file_type=%%~q"
                        set "_current_file_visibility=%%~r"
                        set "_current_file_dir=%%~s"
                        set "_current_file_size=%%~t"

                        setlocal enabledelayedexpansion

                        if "!_current_file_dir!" == "1" (
                            endlocal
                            set "_current_file_base_dir=%_param1%"
                        ) else (
                            if "!_current_file_dir!" == "2" (
                                endlocal
                                set "_current_file_base_dir=%_param2%"
                            ) else (
                                endlocal
                            )
                        )

                        setlocal enabledelayedexpansion
                            REM Due to a bug in sort, an extra " is added at the end of the line, so we need to remove it:
                            set "_current_file_size=!_current_file_size:"=!"

                            REM if not first time:
                            if NOT "!_previous_file_type!" == "" (
                                if "!_next!" == "1" (
                                    if "!_current_file_type!" == "!_previous_file_type!" (
                                        if "!_current_file_type!" == "file" (

                                            if NOT "!_current_file!" == "!_previous_file!" (
                                                echo Only in "!_previous_file_dir!" - !_previous_file_type!: "!_previous_file_base_dir!!_previous_file!"
                                            ) else (
                                                set "_for_one_cannot_get_size=0"
                                                if "!_previous_file_size!" == "-1" set "_for_one_cannot_get_size=1"
                                                if "!_current_file_size!" == "-1" set "_for_one_cannot_get_size=1"
                                                if "!_for_one_cannot_get_size!" == "0" (
                                                    if "!_previous_file_size!" GTR "!_current_file_size!" (
                                                        echo "!_previous_file_base_dir!!_previous_file!" ^(!_previous_file_dir!^) size ^(!_previous_file_size!B^) is bigger than "!_current_file_base_dir!!_current_file!" ^(!_current_file_dir!^) size ^(!_current_file_size!B^)
                                                    ) else (
                                                        if "!_previous_file_size!" LSS "!_current_file_size!" (
                                                            echo "!_previous_file_base_dir!!_previous_file!" ^(!_previous_file_dir!^) size ^(!_previous_file_size!B^) is smaller than "!_current_file_base_dir!!_current_file!" ^(!_current_file_dir!^) size ^(!_current_file_size!B^)
                                                        )
                                                    )
                                                )
                                            )
                                        ) else (
                                            if NOT "!_current_file!" == "!_previous_file!" (
                                                echo Only in "!_previous_file_dir!" - !_previous_file_type!: "!_previous_file_base_dir!!_previous_file!"
                                            )
                                        )
                                    ) else (
                                        echo Only in "!_previous_file_dir!" - !_previous_file_type!: "!_previous_file_base_dir!!_previous_file!"
                                    )
                                )
                            )
                        endlocal
                    endlocal
                endlocal                


                setlocal enabledelayedexpansion

                if "%%~s" == "1" (
                    endlocal
                    set "_current_file_base_dir=%_param1%"
                ) else (
                    if "%%~s" == "2" (
                        endlocal
                        set "_current_file_base_dir=%_param2%"
                    ) else (
                        endlocal
                    )
                )

                set "_temp=%%~p"
                setlocal enabledelayedexpansion
                REM if not first time:
                if NOT "!_current_file_type!" == "" (
                    if "!_next!" == "1" (
                        if "%%~q" == "!_current_file_type!" (
                            if "!_temp!" == "!_current_file!" (
                                endlocal
                                set _next=2
                            ) else (
                                endlocal
                                set _next=1
                            )
                        ) else (
                            endlocal
                            set _next=1
                        )
                    ) else (
                        endlocal
                        set _next=1
                    )
                ) else (
                    endlocal
                    set _next=1
                )

                set "_current_file=%%~p"
                set "_current_file_type=%%~q"
                set "_current_file_visibility=%%~r"
                set "_current_file_dir=%%~s"
                set "_current_file_size=%%~t"
            )
        )
        REM Treat the last file separately:
        chcp 65001>nul
        setlocal enabledelayedexpansion
            if "!_next!" == "1" (
                echo Only in "!_current_file_dir!" - !_current_file_type!: "!_current_file_base_dir!!_current_file!"
            )
        endlocal
        chcp 437>nul

    endlocal
goto :eof

:ProcedureProcessFilenamesLengthAndSize1
    @setlocal
        @set /a _count1=0

        @REM Process directories:

        @chcp 65001>nul

        @title Loading directory paths for directory 1. Please wait...
        @pushd "%_param1%">nul
        @for /r /d %%f in (*) do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param1_len%!///dir///1///1///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param1_len%!///dir///1///1///!_size!"
                    ) else (
                        @echo "!_current_path:~%_param1_len%!///dir///1///1///-1"
                    )
                )
            @endlocal
        )
        @popd>nul
        @title Please wait...

        @REM Process directories:
        @title Loading directory paths for directory 2. Please wait...
        @pushd "%_param2%">nul
        @for /r /d %%f in (*) do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param2_len%!///dir///1///2///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param2_len%!///dir///1///2///!_size!"
                    ) else (
                        @echo "!_current_path:~%_param2_len%!///dir///1///2///-1"
                    )
                )
            @endlocal
        )
        @popd>nul
        @title Please wait...

        @REM Process files:
        @title Loading file paths for directory 1. Please wait...
        @pushd "%_param1%">nul
        @for /r %%f in (*) do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param1_len%!///file///1///1///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param1_len%!///file///1///1///!_size!"
                    ) else (
                        @echo "!_current_path:~%_param1_len%!///file///1///1///-1"
                    )
                )
            @endlocal
        )
        @popd>nul
        @title Please wait...

        @REM Process files:
        @title Loading file paths for directory 2. Please wait...
        @pushd "%_param2%">nul
        @for /r %%f in (*) do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param2_len%!///file///1///2///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param2_len%!///file///1///2///!_size!"
                    ) else (
                        @echo "!_current_path:~%_param2_len%!///file///1///2///-1"
                    )
                )
            @endlocal
        )
        @popd>nul
        @title Please wait...

        @chcp 437>nul

    @endlocal & (
        @set _count1=%_count1%
    )
@goto :eof

:ProcedureProcessFilenamesLengthAndSize2
    @setlocal
        @REM Process hidden directories:
        @for /f "tokens=*" %%f in ('title Loading hidden directory paths for directory 1. Please wait...^&@pushd "%_param1%"^>nul^&^&dir /a:dh /s /b 2^>nul^&^&popd^>nul^&title Please wait...') do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param1_len%!///dir///0///1///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param1_len%!///dir///0///1///!_size!"
                    ) else (
                        @REM This file is hidden and probably has a Unicode path:
                        @echo "!_current_path:~%_param1_len%!///dir///0///1///-1"
                    )
                )
            @endlocal
        )
        @title Please wait...

        @REM Process hidden directories:
        @for /f "tokens=*" %%f in ('title Loading hidden directory paths for directory 2. Please wait...^&@pushd "%_param2%"^>nul^&^&dir /a:dh /s /b 2^>nul^&^&popd^>nul^&title Please wait...') do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @set /a _count1+=1 >nul
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param2_len%!///dir///0///2///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param2_len%!///dir///0///2///!_size!"
                    ) else (
                        @REM This file is hidden and probably has a Unicode path:
                        @echo "!_current_path:~%_param2_len%!///dir///0///2///-1"
                    )
                )
            @endlocal
        )
        @title Please wait...

        @REM Process hidden files:
        @for /f "tokens=*" %%f in ('title Loading hidden file paths for directory 1. Please wait...^&@pushd "%_param1%"^>nul^&^&dir /a:-dh /s /b 2^>nul^&^&popd^>nul^&title Please wait...') do @(
            @set "_current_path=%%~f"
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @set /a _count1+=1 >nul
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param1_len%!///file///0///1///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param1_len%!///file///0///1///!_size!"
                    ) else (
                        @REM This file is hidden and probably has a Unicode path:
                        @echo "!_current_path:~%_param1_len%!///file///0///1///-1"
                    )
                )
            @endlocal
        )
        @title Please wait...

        @REM Process hidden files:
        @for /f "tokens=*" %%f in ('title Loading hidden file paths for directory 2. Please wait...^&@pushd "%_param2%"^>nul^&^&dir /a:-dh /s /b 2^>nul^&^&popd^>nul^&title Please wait...') do @(
            @set "_current_path=%%~f"
            @set /a _count1+=1 >nul
            @if "%%~zf" == "" (
                @call :GenerateShortName _short_name "%%~f"
                @call :GenerateShortNameSize _size "%%_short_name%%"
            )
            @setlocal enabledelayedexpansion
                @title Processing file !_count1!...
                @if not "%%~zf" == "" (
                    @echo "!_current_path:~%_param2_len%!///file///0///2///%%~zf"
                ) else (
                    @if not "!_size!" == "" (
                        @echo "!_current_path:~%_param2_len%!///file///0///2///!_size!"
                    ) else (
                        @REM This file is hidden and probably has a Unicode path:
                        @echo "!_current_path:~%_param2_len%!///file///0///2///-1"
                    )
                )
            @endlocal
        )
        @title Please wait...

    @endlocal & (
        @set _count1=%_count1%
    )
@goto :eof

:DisplayHelp
    echo. 
    echo     %~n0 ^(Cmd COMPare^) - Compare two directory trees by file and folder paths and by size
    echo.
    echo     Syntax: %~n0 ^<dir_tree1^> ^<dir_tree2^>
    echo.
    echo         - where ^<dir_tree1^> and ^<dir_tree2^> are two directory trees to be compared, provided by the user
    echo.
    echo     * Note 1:
    echo         - Files that are hidden and also have a Unicode path - are compared only by path, not also by size
    echo         - In some rare cases ^(for some Unicode file paths^) some files may be misreported
    echo.
    echo     * Note 2:
    echo         - This program uses the ^"sort^" utility for sorting results, so the waiting time for the comparison of the two directories depends on (is a multiple of) n * log n ^(the time complexity for the quick sort algorithm ^(used in ^"sort^"^)^) - where - n is the total number of files in the two directories that are to be compared.
    echo         - The output of the program is Unicode
goto :eof

:PressAKey
    set /p=%~1<nul
    pause>nul
goto :eof
2
  • 1
    Thank you, Rube Goldberg! Commented Jan 7, 2020 at 0:27
  • I hope this is not some "underhanded powershell code context" ... The amount of lines and the potential for harmful "but seemingly innocuous" code in this is staggering ^^ . And well, it doesn't really showcase the power of the command line (in unix (and probably in windows with the new "linux on windows" features) we can do what OP asks with : diff -q dir1/ dir2/| grep "missing" Commented Dec 21, 2020 at 17:46
3

This Powershell script does what you want.

$fso = Get-ChildItem -Recurse -path C:\Temp\Source
$fsoBU = Get-ChildItem -Recurse -path C:\Temp\Source2
Compare-Object -ReferenceObject $fso -DifferenceObject $fsoBU

That, and several other options, are discussed here: https://stackoverflow.com/questions/3804109/what-is-the-best-way-to-compare-2-folder-trees-on-windows

2
  • thanks, I cannot use Powershell. Really appreciate your answer though. Commented Feb 19, 2019 at 15:38
  • 1
    Why can you not use Powershell?
    – Doug Deden
    Commented Feb 19, 2019 at 16:36
0

Do a dir /b /A-dof both folders to a file and compare these with findstr and the /VG: options

Given a sample tree:

> tree /f
X:.
│
└───Folders
    ├───source
    │       file_1
    │       file_2
    │       file_3
    │       file_5
    │       file_6
    │
    └───source2
            file_1
            file_2
            file_4
            file_5
            file_7

The following batch file:

:: Q:\Test\2019\02\19\SU_1407481.cmd
@echo off

Set "DirA=X:\Folders\source\"
Set "DirB=X:\Folders\source2\"

Dir /B /A-D "%DirA%*" >DirA.txt
Dir /B /A-D "%DirB%*" >DirB.txt

echo file(s) missing in %DirA%
findstr /VG:DirA.txt DirB.txt

echo fil(e) missing in %DirB%
findstr /VG:DirB.txt DirA.txt

yields this output:

> SU_1407481.cmd
file(s) missing in X:\Folders\source\
file_4
file_7
file(s) missing in X:\Folders\source2\
file_3
file_6
0

Another solution is to use the free open-source software WinMerge.

After install, go to File > Open and enter the folder paths to compare. You can check the "Read-only" box to ensure no changes are made.

WinMerge Open Dialog

You may also want to choose a specific comparison method depending on your needs. "Modified Date and Size" is a good option for speed if you don't need actual file contents checked. Below is a table of comparison methods compared:

WinMerge Comparison Options Compared Table content from Github

WinMerge Comparison Menu Options

Finally, click compare to run the analysis. Below is example output with default config:

WinMerge Directory Comparison Results

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .