windowsのバッチで、遠隔のOS種別を推測するバッチ（勉強用）を作ってみました。

#  ping応答を利用して、遠隔のサーバのOSを調べてみました。
ネットで調べると、pingのTTLで相手のOSを推測できることが分かりました。

でも、それを覚えるのはしんどいので、バッチで表示するようにしていました。

初めての投稿なので、とりあえず、ほぼプレーンテキストで投稿します。
もし間違っていたら指摘ください。

#  バッチのコードは以下の通りです。

１００％オリジナルなので、どこで利用いただいても問題ないです。

一応自分では、ping2.batという名前で保存して使ってます。
ping2 サーバ名またはipアドレス
で、すぐに結果が表示されます。

勉強を兼ねて作っているので、コメントだらけです。
注意いただきたいのは、-listという一括調査機能を使うと、「バッチ名.csv」というファイルを上書きすることがある点です。
「上書きしていいですか？」はうっとうしいので、あえて出してません。

```bat:ping2.bat
@echo off
setlocal enabledelayedexpansion
chcp 932 >nul
set err_code=0

rem *Encode with shift-JIS(Japanese)
rem このファイルを拡張子.txtで入手した場合は、拡張子を.batに変更してから利用ください。
rem shift-JISで保存しないと文字化けして正常に動作しません。
rem 勉強用に、コメントを多用しています。remで始まる分はコメントなので、削除しても構いません。
rem RFCによりHOST名の制約（記号は-のみ利用可）があるので、大丈夫と思いますが、"(",")","&",""",",",スペース等がホスト名に使われると、誤動作します。
rem バグを含めてコンピュータ名に使われるかもしれませんが、対応するつもりはありません。

rem 第1パラメータは、必須で、IPアドレス または、ホスト名、または-listでホスト名をファイル指定です。
rem /iは大文字小文字を区別しないという意味です。
if "%1"=="" ( goto :usage )
if /i "%1"=="/h" ( goto :usage )
if /i "%1"=="/help" ( goto :usage )
if /i "%1"=="-list" ( goto :list_scan )

rem 一つのホストを指定して実行するパターン
call :scan_host %1
set err_code=0
goto :end

:list_scan
rem 複数のホストを指定して実行のパターン
set list_file=%~n0.list
if not exist %list_file% (
  echo 「%list_file%」ファイルがカレントディレクトリに見つかりません。
  echo -list指定の場合は、調べたいホスト名を「%list_file%」に1行、1ホストで列挙してください。
  set err_code=1
  goto :end
)

rem 出力結果ファイルが既にあればタイトルで初期化します。
rem %~n0.csvは常時上書きになるので、ご注意ください。
echo 指定ターゲット,IP-addr,OS,TTL,応答時間>%~n0.csv

rem 指定ホストを1台ずつ調べて、結果出力します。
rem %0.listに1行も無い場合は、メッセージが不自然ですが、複雑になるだけなので、あえてコーディングしません。
rem for文中で、変化する変数を扱う場合は、setlocal enabledelayedexpansionを宣言しておいて、該当変数を%でなく、!で囲みます。
rem %で囲むと、for文に入った時点の値に固定されるので、for文中で値を変更しても参照値は変化しません。
rem また、ping応答の形式が変わった場合は、全ターゲットを調べてもしょうがないので中断します。

for /f "delims=" %%a in (%list_file%) do (
  call :scan_host %%a
  if "!ERRORLEVEL!"=="2" ( 
    set err_code=2
    goto :end
    )
  echo !target!,!ip_addr!,!target_machine!,!TTL!,!response_time!>>%~n0.csv
  )
echo.
echo 「%list_file%」で指定された上記の結果をファイル「%~n0.csv」に出力しました。
set err_code=0
goto :end

rem ===============Sub Routines=================
:scan_host
rem 【このサブルーチンの仕様概要】
rem ホスト名または、IPアドレスから、PINGコマンドを利用し、OSを判定・表示し、判定結果をERRORLEVELとして返します。
rem また、pingの結果から、一部の情報をグローバル変数としてセットします。

rem 【入出力変数】
rem 入力(パラメータ):ホスト名または、IPアドレス。
rem 出力(ERRORLEVEL):0=判定OK、1=ping応答なし、または期限切れ（この場合、スキップ）、2=ping応答フォーマット変更（この場合、中止）
rem その他出力:グローバル変数として以下の変数を出力:
rem %ip_addr%=PINGコマンド内のIPアドレス、%target_machine%=OS種別、%TTL%=TTL、%response_time%=response time

rem 【OS判断のしくみの概要解説】
rem pingコマンドの応答にあるTTL(Time To Live)でOSを判定・表示します。
rem TTLでOSを判断する方法は、ネットで「TTL OS」で調べると見つかります。
rem なお、デフォルトのTTLは公式に規程されているわけでなく、例外的なものもあるので、１００％正確ではありません。
rem 詳細は以下のページに良くまとまっていますが、例外的なものは一般的でないOSが多いので、例外にはあまり当たらないかと。
rem https://subinsb.com/default-device-ttl-values/

rem input
set target=%1

rem output
set ip_addr=-
set target_machine=OS不明
set TTL=-
set response_time=-

rem for文で、usebackq指定すると、ファイルでなく、バッククオート(`)の中をコマンドとみなして、コマンド出力結果を処理できます。
rem これを使うと、一時ファイルに出力せずに、コマンド出力結果をハンドリングできます。
rem ちなみに、バッククオート(`)は、半角で、シフト押しながら＠で入力できます。普段使わないなー。
rem 現状バージョンのPINGでは、スペース区切りで、1番目にIPアドレス、6番目に=応答時間、7番目にTTL=XXXが入っています。
rem 調査の時間を短縮したいので、pingは1回だけ、タイムアウトは、デフォルトの1/5である 800msとしています。
FOR /F "usebackq tokens=1,5,6,7" %%a in (`ping -n 1 -w 800 %target% ^| findstr TTL=`) DO (
  set ip_addr=%%a
  set kigengire=%%b
  set response_time=%%c
  set TTL_phrase=%%d
)

rem pingに応答がないと、上記で代入されない。
if "%ip_addr%"=="-" (
  echo （※ %target% は、PINGの応答がないので、OSは不明です。）
  exit /b 1
)

rem TTLが期限切れになることもあるらしい。
if "%kigengire:~1,4%"=="期限切れ" ( 
  echo （※ %target% は、TTLが期限切れになりました。特殊な環境と思われます。）
  exit /b 1
)

rem コマンド形式が変わっていなければ、%TTL_phrase%はTTL=で始まるはずです。
if not "%TTL_phrase:~0,4%"=="TTL=" ( 
  echo PING応答のフォーマット（TTL=の場所など）が変わった可能性があります。
  echo プログラムを修正ください。
  exit /b 2
)

rem %V:~m%	m文字目から、最後まで
rem response_timeは、0文字目が=なので、それを取るために、1文字目から最後まで。
set response_time=%response_time:~1%
rem 「TTL=」を取って数字だけにするので、4文字目から最後まで。
set TTL=%TTL_phrase:~4%

rem TTLの値でOSを推測します。
rem UNIX系>128>=Windows系>64>=Linux系(Macを含む)>32>=Windows95/98 となっています。
set target_machine=UNIX OS / Network機器
if %TTL% leq 128 ( set target_machine=Windows OS)
if %TTL% leq  64 ( set target_machine=Linux OS / Mac OS)
if %TTL% leq  32 ( set target_machine=Windows 95/98かもしれない！)

rem 結果を表示します。（ターゲットをipアドレスで指定した場合、ダブって表示すると見にくいので、片方を省略します。）
if "%ip_addr%"=="%target%" ( set disp_name=[%ip_addr%]
 ) else ( set disp_name=%target% [%ip_addr%])
echo ■ %disp_name% は、「%target_machine%」です。 -------------- TTLは %TTL% ,応答時間は %response_time% でした。

rem 正常終了
exit /b 0

rem =====================================================================
rem PingコマンドのTTLを利用して、OSを推定するお遊びプログラムです。
rem 実際には、ほぼ100%推定できますので、「あれ？このIPなんだ？」という時に、OSだけですが分かります。
rem Ver1.0 Created by Ken.Matsubara 2023/3/7
rem Ver1.1 Updated by Ken.Matsubara 2023/3/8 一括調査機能追加(-listパラメータ)
rem Ver1.2 Updated by Ken.Matsubara 2023/3/16 プログラムの簡素化と表示見直ししました。
rem Ver1.3 Updated by Ken.Matsubara 2023/3/16 コメントを見直ししました。

:usage
rem 一般的にプログラムの使い方は:usageというラベルでまとめることが多いです。
echo Pingコマンドの応答を(TTL)利用して、ターゲットのOSを調べます。
echo.
echo 使用法: %~n0 [ターゲット（IPアドレス または、ホスト名）、または -list]
echo.
echo オプション:
echo ターゲットを-listとした場合は、カレントディレクトリの「%~n0.list」にIPアドレスまたは、ホスト名を「1行に1つ」書いてください。
echo -listとした場合は、画面表示だけでなく、カレントディレクトリの「%~n0.csv」ファイルに「, 区切り」で結果を出力します。
echo ※「%~n0.csv」ファイルは、確認せずに常に上書きします。
echo.
echo なお、Pingコマンドの応答のTTL(Time To Live)を代表的なOSの値として判断しています。
echo ほぼ、間違いはありませんが、100%%正確ではありません。
echo.
echo 調査の時間を短縮したいので、pingは1回だけ、タイムアウトは、デフォルトの1/5である 800msとしています。

rem 使い方表示の際に、自分のPCのIPアドレスを表示するようにした。
set key_word=IPv6
set key_word=IPv4

rem ipconfigコマンドから情報を取得する。
set count=0
for /f "usebackq delims=" %%a in (`ipconfig^|findstr /i "%key_word%"`) do (
  set contents=%%a
  set "contents=!contents:*: =!"
  set /a count+=1
  set contents[!count!]=!contents!
)

rem 情報を表示する。
rem echo.
rem echo なお、このPCの情報は、以下の通りです。
rem echo ドメイン名は、%USERDOMAIN%
rem echo ログインユーザ名は、%USERNAME%
rem echo コンピュータ名は、%COMPUTERNAME%
rem for /l %%i in (1,1,%count%) do ( echo IPアドレスは、%key_word%[%%i]=!contents[%%i]!)
rem echo.

rem 注意）LFの下にある改行を削除するとうまく動かなくなるよ！
set LF=^


set ip_msg=
for /l %%i in (1,1,%count%) do (set ip_msg=!ip_msg!IPアドレスは、%key_word%[%%i]=!contents[%%i]!!LF!)

msg %username% なお、このPCの情報は、以下の通りです。^

-------------------------------------^

 ドメイン名は、%USERDOMAIN%^

 ログインユーザ名は、%USERNAME%^

 コンピュータ名は、%COMPUTERNAME%^

 %ip_msg%

rem この辺りまで、無駄に見える改行を変更しないでね！

:end
if "%SESSIONNAME%"=="" pause
endlocal
exit /b %err_code%
```


## 上記のポイントを解説します。

１．コマンドの結果を変数に入れる。

for文のusebackqというバッククォート指定を使います。
``で囲んだコマンドを、for文のインプットにしています。
コマンドの結果の1つ目、5つ目、6つ目、7つ目にそれぞれ、IPアドレス、期限切れ、応答時間、TTLが返ってくるので、それを取り出しています。

```bat
FOR /F "usebackq tokens=1,5,6,7" %%a in (`ping -n 1 -w 800 %target% ^| findstr TTL=`) DO (
  set ip_addr=%%a
  set kigengire=%%b
  set response_time=%%c
  set TTL_phrase=%%d
)
```