Compare two folders and output missing files in both folders

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.


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.


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


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

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