@echo off setlocal EnableExtensions DisableDelayedExpansion cd /d "%~dp0" :MENU cls echo ============================================================ echo NETWORK SCANNER (PING + ARP + HOSTNAME LOOKUP) echo ============================================================ echo. set /p "CIDR=Enter CIDR (e.g. 192.168.1.0/24): " if "%CIDR%"=="" goto MENU set /p "TOUT=Timeout ms [100]: " if "%TOUT%"=="" set "TOUT=100" set /p "CNT=Pings [1]: " if "%CNT%"=="" set "CNT=1" REM ============================================================ REM PARSE AND VALIDATE CIDR REM ============================================================ for /f "tokens=1,2 delims=/" %%A in ("%CIDR%") do ( set "IP_ADDR=%%A" set "PREFIX=%%B" ) if "%PREFIX%"=="" set "PREFIX=24" REM Trim spaces from prefix for /f "delims= " %%T in ("%PREFIX%") do set "PREFIX=%%T" set /a PNUM=%PREFIX% 2>nul if errorlevel 1 ( echo [Error] Prefix must be numeric. pause goto MENU ) if %PNUM% LSS 1 goto ERR_RANGE if %PNUM% GTR 30 goto ERR_RANGE for /f "tokens=1-4 delims=." %%A in ("%IP_ADDR%") do ( set "O1=%%A" set "O2=%%B" set "O3=%%C" set "O4=%%D" ) if "%O4%"=="" ( echo [Error] Invalid IP format. pause goto MENU ) for %%V in (%O1% %O2% %O3% %O4%) do ( if %%V LSS 0 goto ERR_RANGE if %%V GTR 255 goto ERR_RANGE ) REM ============================================================ REM CALCULATE SUBNET MASK REM ============================================================ set "M1=0" set "M2=0" set "M3=0" set "M4=0" if %PNUM% LEQ 8 ( set /a "M1=256-(1<<(8-%PNUM%))" goto MASK_DONE ) set "M1=255" if %PNUM% LEQ 16 ( set /a "M2=256-(1<<(16-%PNUM%))" goto MASK_DONE ) set "M2=255" if %PNUM% LEQ 24 ( set /a "M3=256-(1<<(24-%PNUM%))" goto MASK_DONE ) set "M3=255" set /a "M4=256-(1<<(32-%PNUM%))" :MASK_DONE REM ============================================================ REM CALCULATE HOST COUNT REM ============================================================ set /a "HOSTS=(1<<(32-%PNUM%))-2" if "%HOSTS%"=="0" ( echo [Error] No usable hosts in this range. pause goto MENU ) if %HOSTS% LSS 1 ( echo [Error] Invalid host count calculated. pause goto MENU ) REM ============================================================ REM CALCULATE NETWORK ADDRESS REM ============================================================ set /a "N1=O1&M1" set /a "N2=O2&M2" set /a "N3=O3&M3" set /a "N4=O4&M4" set "NETWORK_ADDR=%N1%.%N2%.%N3%.%N4%/%PNUM%" REM ============================================================ REM CALCULATE BROADCAST ADDRESS REM Broadcast = Network + wildcard mask REM Wildcard mask = 255 - subnet mask octet REM ============================================================ set /a "B1=N1+(255-M1)" set /a "B2=N2+(255-M2)" set /a "B3=N3+(255-M3)" set /a "B4=N4+(255-M4)" set "BROADCAST_ADDR=%B1%.%B2%.%B3%.%B4%" REM ============================================================ REM CALCULATE START IP REM Start IP = Network + 1 REM ============================================================ set /a S1=N1 set /a S2=N2 set /a S3=N3 set /a S4=N4+1 if %S4% GTR 255 ( set /a S4=0 set /a S3+=1 ) if %S3% GTR 255 ( set /a S3=0 set /a S2+=1 ) if %S2% GTR 255 ( set /a S2=0 set /a S1+=1 ) set "START_IP=%S1%.%S2%.%S3%.%S4%" REM ============================================================ REM CALCULATE END IP REM End IP = Broadcast - 1 REM ============================================================ set /a E1=B1 set /a E2=B2 set /a E3=B3 set /a E4=B4-1 if %E4% LSS 0 ( set /a E4=255 set /a E3-=1 ) if %E3% LSS 0 ( set /a E3=255 set /a E2-=1 ) if %E2% LSS 0 ( set /a E2=255 set /a E1-=1 ) set "END_IP=%E1%.%E2%.%E3%.%E4%" REM ============================================================ REM PREPARE OUTPUT FILE REM ============================================================ set "SAFE_IP=%IP_ADDR:.=_%" set "SAFE_IP=%SAFE_IP:/=_%" set "OUT_FILE=ping_%SAFE_IP%_p%PNUM%.txt" call :GET_TIME_CS START_CS set "START_STAMP=%DATE% %TIME%" echo. echo Network: %NETWORK_ADDR% echo Start IP: %START_IP% echo End IP: %END_IP% echo Broadcast: %BROADCAST_ADDR% echo Hosts: %HOSTS% echo Timeout: %TOUT% ms echo Pings: %CNT% echo File: %OUT_FILE% echo. echo Scanning... Please wait. echo. REM ============================================================ REM CREATE OUTPUT FILE ONLY IF IT DOES NOT EXIST REM Existing file is used as an accumulated inventory. REM ============================================================ if not exist "%OUT_FILE%" ( > "%OUT_FILE%" ( echo ============================================================ echo NETWORK SCANNER ACCUMULATED REPORT echo ============================================================ echo. echo File created;%START_STAMP% echo Initial CIDR;%CIDR% echo Initial network;%NETWORK_ADDR% echo Initial start IP;%START_IP% echo Initial end IP;%END_IP% echo Initial broadcast IP;%BROADCAST_ADDR% echo Broadcast excluded;Yes echo Hostname lookup;nslookup, nslookup PTR, nbtstat -A, ping -a echo. echo ------------------------------------------------------------ echo UNIQUE HOST RECORDS echo ------------------------------------------------------------ echo IP;MAC;HOSTNAME;HOSTNAME_SOURCE ) ) REM ============================================================ REM INIT SCAN COUNTERS REM Start scan exactly from calculated Start IP. REM Broadcast IP is not included. REM ============================================================ set /a IP1=S1 set /a IP2=S2 set /a IP3=S3 set /a IP4=S4 set /a CUR=0 set /a ALIVE=0 set /a NEW_RECORDS=0 set /a SKIPPED_RECORDS=0 set /a UPDATED_RECORDS=0 set /a PE=HOSTS/50 if %PE% LSS 1 set PE=1 REM ============================================================ REM SCAN LOOP REM ============================================================ setlocal EnableDelayedExpansion for /L %%I in (1,1,%HOSTS%) do ( set /a CUR+=1 set "CIP=!IP1!.!IP2!.!IP3!.!IP4!" ping -n %CNT% -w %TOUT% !CIP! >nul 2>&1 if !errorlevel! EQU 0 ( set /a ALIVE+=1 REM Clear values before every lookup set "MAC=" set "HOSTNAME=" set "HOSTNAME_SOURCE=" REM ---------------------------------------------------- REM Get MAC address from local ARP table REM ---------------------------------------------------- for /f "tokens=1,2" %%A in ('arp -a') do ( if "%%A"=="!CIP!" set "MAC=%%B" ) if not defined MAC set "MAC=UNKNOWN" REM ---------------------------------------------------- REM Try to get hostname by all available methods: REM 1. nslookup reverse DNS REM 2. nslookup PTR query REM 3. nbtstat NetBIOS lookup REM 4. ping -a reverse lookup REM ---------------------------------------------------- call :GET_HOSTNAME "!CIP!" HOSTNAME HOSTNAME_SOURCE if not defined HOSTNAME set "HOSTNAME=UNKNOWN" if not defined HOSTNAME_SOURCE set "HOSTNAME_SOURCE=UNKNOWN" set "PAIR_PREFIX=!CIP!;!MAC!" set "FULL_RECORD=!CIP!;!MAC!;!HOSTNAME!;!HOSTNAME_SOURCE!" REM ---------------------------------------------------- REM Check only IP;MAC pair. REM If the same IP;MAC already exists, do not duplicate it. REM If an existing record contains UNKNOWN and current lookup is better, REM update the existing line in the accumulated file. REM ---------------------------------------------------- findstr /L /B /C:"!PAIR_PREFIX!" "%OUT_FILE%" >nul 2>&1 if errorlevel 1 ( echo(!FULL_RECORD!>> "%OUT_FILE%" set /a NEW_RECORDS+=1 echo New: !CIP! MAC: !MAC! HOSTNAME: !HOSTNAME! SOURCE: !HOSTNAME_SOURCE! ) else ( set "EXISTING_HAS_UNKNOWN=0" findstr /L /B /C:"!PAIR_PREFIX!" "%OUT_FILE%" | findstr /I /L /C:";UNKNOWN" >nul 2>&1 if !errorlevel! EQU 0 set "EXISTING_HAS_UNKNOWN=1" if /I not "!HOSTNAME_SOURCE!"=="UNKNOWN" if "!EXISTING_HAS_UNKNOWN!"=="1" ( call :UPDATE_RECORD_BY_PREFIX "%OUT_FILE%" "!PAIR_PREFIX!" "!FULL_RECORD!" UPDATE_RESULT set /a UPDATED_RECORDS+=1 echo Updated: !CIP! MAC: !MAC! HOSTNAME: !HOSTNAME! SOURCE: !HOSTNAME_SOURCE! ) else ( set /a SKIPPED_RECORDS+=1 echo Existing: !CIP! MAC: !MAC! HOSTNAME: !HOSTNAME! SOURCE: !HOSTNAME_SOURCE! ) ) ) set /a MOD=CUR%%PE if !MOD! EQU 0 ( echo Progress: !CUR!/%HOSTS% Alive: !ALIVE! New: !NEW_RECORDS! Updated: !UPDATED_RECORDS! Existing: !SKIPPED_RECORDS! ) REM -------------------------------------------------------- REM Move to next IP address REM -------------------------------------------------------- set /a IP4+=1 if !IP4! GTR 255 ( set /a IP4=0 set /a IP3+=1 ) if !IP3! GTR 255 ( set /a IP3=0 set /a IP2+=1 ) if !IP2! GTR 255 ( set /a IP2=0 set /a IP1+=1 ) ) REM Save counter values before leaving local delayed expansion scope endlocal & ( set "ALIVE=%ALIVE%" set "NEW_RECORDS=%NEW_RECORDS%" set "SKIPPED_RECORDS=%SKIPPED_RECORDS%" set "UPDATED_RECORDS=%UPDATED_RECORDS%" ) REM ============================================================ REM CALCULATE SCAN TIME REM ============================================================ call :GET_TIME_CS END_CS set "END_STAMP=%DATE% %TIME%" set /a ELAPSED_CS=END_CS-START_CS REM Midnight rollover protection if %ELAPSED_CS% LSS 0 set /a ELAPSED_CS+=24*60*60*100 call :FORMAT_ELAPSED %ELAPSED_CS% ELAPSED_TEXT REM ============================================================ REM WRITE SCAN HISTORY ENTRY REM ============================================================ >> "%OUT_FILE%" ( echo. echo ------------------------------------------------------------ echo SCAN HISTORY ENTRY echo ------------------------------------------------------------ echo Started;%START_STAMP% echo Finished;%END_STAMP% echo Elapsed;%ELAPSED_TEXT% echo Input CIDR;%CIDR% echo Network;%NETWORK_ADDR% echo Start IP;%START_IP% echo End IP;%END_IP% echo Broadcast IP;%BROADCAST_ADDR% echo Broadcast excluded;Yes echo Timeout ms;%TOUT% echo Pings;%CNT% echo Hostname lookup;nslookup, nslookup PTR, nbtstat -A, ping -a echo Hosts scanned;%HOSTS% echo Alive hosts in this scan;%ALIVE% echo New records added;%NEW_RECORDS% echo Existing records updated;%UPDATED_RECORDS% echo Existing IP-MAC pairs skipped;%SKIPPED_RECORDS% echo ============================================================ ) echo. echo ============================================================ echo Complete! echo Network: %NETWORK_ADDR% echo Start IP: %START_IP% echo End IP: %END_IP% echo Broadcast: %BROADCAST_ADDR% echo Alive hosts this scan: %ALIVE% echo New records added: %NEW_RECORDS% echo Existing records updated: %UPDATED_RECORDS% echo Existing records skipped: %SKIPPED_RECORDS% echo Time: %ELAPSED_TEXT% echo Results: %OUT_FILE% echo ============================================================ pause goto MENU REM ============================================================ REM ERROR HANDLER REM ============================================================ :ERR_RANGE echo [Error] Value out of range. Prefix must be 1-30, octets must be 0-255. pause goto MENU REM ============================================================ REM SUBROUTINE: GET HOSTNAME BY ALL AVAILABLE METHODS REM Argument 1: IP address REM Argument 2: output hostname variable name REM Argument 3: output hostname source variable name REM REM Lookup order: REM 1. nslookup reverse DNS REM 2. nslookup PTR query REM 3. nbtstat -A IP REM 4. ping -a IP REM ============================================================ :GET_HOSTNAME set "%~2=UNKNOWN" set "%~3=UNKNOWN" set "LOOKUP_IP=%~1" set "FOUND_NAME=" REM ------------------------------------------------------------ REM Method 1: nslookup direct reverse lookup. REM Works if DNS server exists and has PTR/reverse records. REM Timeout is limited to reduce scan delay. REM ------------------------------------------------------------ for /f "tokens=1,* delims=:" %%A in ('nslookup -timeout^=1 %LOOKUP_IP% 2^>nul ^| findstr /I /B /C:"Name:"') do ( if not defined FOUND_NAME ( set "FOUND_NAME=%%B" ) ) if defined FOUND_NAME ( call :TRIM_VALUE FOUND_NAME call :REMOVE_TRAILING_DOT FOUND_NAME if defined FOUND_NAME ( set "%~2=%FOUND_NAME%" set "%~3=NSLOOKUP" exit /b ) ) REM ------------------------------------------------------------ REM Method 2: nslookup explicit PTR query. REM This may return lines like: REM 25.1.168.192.in-addr.arpa name = host.example.local REM ------------------------------------------------------------ set "FOUND_NAME=" for /f "tokens=1-4 delims=." %%A in ("%LOOKUP_IP%") do ( set "REV_PTR=%%D.%%C.%%B.%%A.in-addr.arpa" ) for /f "tokens=1,* delims==" %%A in ('nslookup -timeout^=1 -type^=PTR %REV_PTR% 2^>nul ^| findstr /I /C:"name ="') do ( if not defined FOUND_NAME ( set "FOUND_NAME=%%B" ) ) if defined FOUND_NAME ( call :TRIM_VALUE FOUND_NAME call :REMOVE_TRAILING_DOT FOUND_NAME if defined FOUND_NAME ( set "%~2=%FOUND_NAME%" set "%~3=NSLOOKUP_PTR" exit /b ) ) REM ------------------------------------------------------------ REM Method 3: NetBIOS lookup. REM Works if NetBIOS over TCP/IP is enabled on the remote host REM and UDP/137 is not blocked by firewall. REM ------------------------------------------------------------ set "FOUND_NAME=" for /f "tokens=1" %%N in ('nbtstat -A %LOOKUP_IP% 2^>nul ^| findstr /I /C:"<00>" ^| findstr /I /C:"UNIQUE"') do ( if not defined FOUND_NAME ( set "FOUND_NAME=%%N" ) ) if defined FOUND_NAME ( call :TRIM_VALUE FOUND_NAME if defined FOUND_NAME ( set "%~2=%FOUND_NAME%" set "%~3=NBTSTAT" exit /b ) ) REM ------------------------------------------------------------ REM Method 4: ping -a fallback. REM This may use Windows local name resolution mechanisms: REM DNS, NetBIOS, cache, LLMNR/mDNS depending on OS settings. REM ------------------------------------------------------------ set "FOUND_NAME=" set "PING_PREFIX=" for /f "tokens=1 delims=[" %%L in ('ping -a -n 1 -w 300 %LOOKUP_IP% 2^>nul ^| find "["') do ( if not defined PING_PREFIX set "PING_PREFIX=%%L" ) if defined PING_PREFIX ( for %%W in (%PING_PREFIX%) do set "FOUND_NAME=%%W" ) if defined FOUND_NAME ( call :TRIM_VALUE FOUND_NAME call :REMOVE_TRAILING_DOT FOUND_NAME if defined FOUND_NAME ( if /I not "%FOUND_NAME%"=="Pinging" ( set "%~2=%FOUND_NAME%" set "%~3=PING_A" exit /b ) ) ) REM ------------------------------------------------------------ REM Nothing found. REM ------------------------------------------------------------ set "%~2=UNKNOWN" set "%~3=UNKNOWN" exit /b REM ============================================================ REM SUBROUTINE: UPDATE RECORD BY IP;MAC PREFIX REM Argument 1: file path REM Argument 2: IP;MAC prefix REM Argument 3: new full record REM Argument 4: output update result variable REM ============================================================ :UPDATE_RECORD_BY_PREFIX setlocal EnableDelayedExpansion set "TARGET_FILE=%~1" set "TARGET_PREFIX=%~2" set "NEW_LINE=%~3" set "TMP_FILE=%TARGET_FILE%.tmp" set "UPDATED=0" > "!TMP_FILE!" ( for /f "usebackq delims=" %%L in ("!TARGET_FILE!") do ( set "LINE=%%L" if "!LINE:%TARGET_PREFIX%=!" NEQ "!LINE!" ( echo(!NEW_LINE! set "UPDATED=1" ) else ( echo(!LINE! ) ) ) move /Y "!TMP_FILE!" "!TARGET_FILE!" >nul 2>&1 endlocal & set "%~4=%UPDATED%" exit /b REM ============================================================ REM SUBROUTINE: TRIM_VALUE REM Removes leading spaces from variable value. REM Argument 1: variable name to trim. REM ============================================================ :TRIM_VALUE set "TRIM_VAR_NAME=%~1" call set "TRIM_VALUE=%%%TRIM_VAR_NAME%%%" for /f "tokens=* delims= " %%T in ("%TRIM_VALUE%") do set "TRIM_VALUE=%%T" set "%TRIM_VAR_NAME%=%TRIM_VALUE%" exit /b REM ============================================================ REM SUBROUTINE: REMOVE_TRAILING_DOT REM Removes trailing dot from DNS names. REM Argument 1: variable name. REM ============================================================ :REMOVE_TRAILING_DOT set "DOT_VAR_NAME=%~1" call set "DOT_VALUE=%%%DOT_VAR_NAME%%%" if "%DOT_VALUE:~-1%"=="." set "DOT_VALUE=%DOT_VALUE:~0,-1%" set "%DOT_VAR_NAME%=%DOT_VALUE%" exit /b REM ============================================================ REM SUBROUTINE: GET CURRENT TIME IN CENTISECONDS REM Argument 1: output variable name. REM ============================================================ :GET_TIME_CS set "T=%TIME: =0%" for /f "tokens=1-4 delims=:., " %%A in ("%T%") do ( set /a "%~1=((1%%A-100)*360000)+((1%%B-100)*6000)+((1%%C-100)*100)+(1%%D-100)" ) exit /b REM ============================================================ REM SUBROUTINE: FORMAT CENTISECONDS AS HH:MM:SS.CC REM Argument 1: elapsed centiseconds. REM Argument 2: output variable name. REM ============================================================ :FORMAT_ELAPSED set /a _CS=%~1 set /a _H=_CS/360000 set /a _CS=_CS%%360000 set /a _M=_CS/6000 set /a _CS=_CS%%6000 set /a _S=_CS/100 set /a _C=_CS%%100 if %_H% LSS 10 set "_H=0%_H%" if %_M% LSS 10 set "_M=0%_M%" if %_S% LSS 10 set "_S=0%_S%" if %_C% LSS 10 set "_C=0%_C%" set "%~2=%_H%:%_M%:%_S%.%_C%" exit /b