バッチファイルでFTP転送

おお、アクセスカウンタが10,000を超えた。


アクセスログを見てみるとGoogleかYahoo Searchで引っかかってやってくる人がほとんど。最近はどうもWebDAVでファイル転送をする記事(参考)にGoogle先生で引っかかってくる人が多い。
ただ、どうも検索語が「VBScript FTP アップロード」で引っかかる人がちょくちょくおり、ちょい補足したい気分に。
Windowsからタスクスケジューラなどを使って自動的にFTPファイル転送を行いたいのであれば、VBScriptではなくWindowsのバッチファイル(.bat, .cmd)を使ったほうが良い。


Vistaで使えたかどうかは忘れたがWindows XP, Windows Server 2003にはコマンドプロンプトで使えるFTPクライアントソフトが標準で備わっており、コマンドプロンプトを起動して "ftp" とすれば使用できる。
このFTPクライアントだが、テキストファイルに記述してある内容を入力コマンドとして指定できるオプション(-s)があり、バッチファイルで例えば下のようにFTPコマンドファイルを生成してFTPクライアントに食わせることで、ある程度柔軟性のあるサーバ間ないしクライアント−サーバ間のファイル転送運用を実現することが出来る。


以下はファイル送信するケース。


ftpsend.bat


rem 転送するファイルをまとめて以下のフォルダに置く
set DATADIR=C:\ftpsend\senddata

rem 一時的なコマンドファイルなどを出力するためのtempフォルダを指定する
set TEMPDIR=C:\ftpsend\temp

rem ftp コマンドに食わせるコマンドファイル
set FTPCMD=%TEMPDIR%\ftpcmd.txt

rem 接続先FTPサーバを設定
echo open xxx.yyy.zzz.www>%FTPCMD%
rem ログインユーザー名を設定
echo ftpuser>>%FTPCMD%
rem ログインパスワードを設定
echo ftppass>>%FTPCMD%

rem ファイル転送先のフォルダを TESTDIR としディレクトリ作成
echo mkdir TESTDIR>>%FTPCMD%
echo cd TESTDIR>>%FTPCMD%

echo bi>>%FTPCMD%

pushd %DATADIR%

rem DATADIRに指定したフォルダ内のすべてのファイルを転送する
for /F "usebackq tokens=*" %%i in (`dir /b`) do (
echo put "%%i">>%FTPCMD%
)

echo bye>>%FTPCMD%

rem ftpコマンドを実行する
ftp -s:%FTPCMD%

popd

rem 生成したftpコマンドファイルはいらなかったら削除する
del %FTPCMD%


上記は単純にファイル転送をするだけのバッチファイルだが、使えるならMicrosoftが提供しているファイルチェックサムを生成するfcivというツール(参考)を使うと、送信先のサーバで転送したファイルを検証できるようになって良い。


ついでに、上のスクリプトではFTPサーバに接続する際のユーザー名とパスワードを指定しているが、それも最近はあまり良いやり方とはいえず、出来るならサーバ側でAnonymousユーザのファイル書き込みを許可し、接続可能なクライアントのIPアドレスを制限するほうが良い。ftpではパスワードがクリアテキストのままネットワーク上を流れるからというのもあるにはあるが、それよりも最近はいかなるユーザのパスワードも定期的に変更しなければならないというセキュリティ要件が設けられたシステムが多いため、その場合変更するたびにバッチファイルを合わせて変更しなおす必要があるのでパスワード埋め込みだと手間がかかる。


ついでにWindowsに標準でついているFTPクライアントだが、これが困ったことにルータ等で勝手にコネクションを切られる等の理由でファイル転送が異常終了しても、ftpコマンド自体はERRORLEVEL 0で終了するためにファイル転送の失敗が検知できない。
そんなわけで、少なくとも最後のファイルが転送されるまではコネクションが切断されなかったことを確認するためには、適当な小さいファイルを一度アップロードしてそれをファイル名を変えてダウンロードして、FTPコマンド実行後に最後にダウンロードを指定したファイルが存在するか等のチェックを行う必要がある。


そんなことを実装すると上のバッチファイルは以下のような感じになる。


ftpsend2.bat


rem 転送するファイルをまとめて以下のフォルダに置く
set DATADIR=C:\ftpsend\senddata

rem 一時的なコマンドファイルなどを出力するためのtempフォルダを指定する
set TEMPDIR=C:\ftpsend\temp

rem ftp コマンドに食わせるコマンドファイル
set FTPCMD=%TEMPDIR%\ftpcmd.txt

rem fciv.exeの場所を指定する
set FCIV=C:\Tools\fciv\fciv.exe

rem 転送したファイルを検証するためのfcivファイルを生成する
%FCIV% -wp %DATADIR%>%TEMPDIR%\fciv.txt

rem 送信後に受信するためのfcivファイルをあらかじめ削除する
del %TEMPDIR%\fciv.res

rem ftp コマンドに食わせるコマンドファイル
set FTPCMD=%TEMPDIR%\ftpcmd.txt

rem 接続先FTPサーバを設定
echo open xxx.yyy.zzz.www>%FTPCMD%
rem ログインユーザー名を設定(匿名ユーザー)
echo anonymous>>%FTPCMD%
rem ログインパスワードを設定
echo anonymous@%COMPUTERNAME%>>%FTPCMD%

rem ファイル転送先のフォルダを TESTDIR としディレクトリ作成
echo mkdir TESTDIR>>%FTPCMD%
echo cd TESTDIR>>%FTPCMD%

echo bi>>%FTPCMD%

pushd %DATADIR%

rem DATADIRに指定したフォルダ内のすべてのファイルを転送する
for /F "usebackq tokens=*" %%i in (`dir /b`) do (
echo put "%%i">>%FTPCMD%
)

rem fcivファイルを転送する
echo put %TEMPDIR%\fciv.txt>>%FTPCMD%

rem 転送したfcivファイルをダウンロードする
echo get fciv.txt %TEMPDIR%\fciv.res>>%FTPCMD%

echo bye>>%FTPCMD%

rem ftpコマンドを実行する
ftp -s:%FTPCMD%

popd

rem 生成したftpコマンドファイルはいらなかったら削除する
del %FTPCMD%

rem 送信したfcivファイルと受信したファイルを比較
rem というか受信したファイルが存在すれば多分ファイル転送成功
fc /b %TEMPDIR%\fciv.txt %TEMPDIR%\fciv.res
if %ERRORLEVEL% == 0 (
echo 転送成功
set RC=0
) else (
echo 転送失敗(FTP通信中に不意にコネクションが閉じられたなど)
set RC=1
)

rem fcivファイルはいらなかったら削除する
del %TEMPDIR%\fciv.txt
del %TEMPDIR%\fciv.res

exit /b %RC%


・・まあ、さらにもっといえば、上記の方法ではfcivファイルを使って事後にFTPサーバ側でファイル内容が正しいかを確認することは出来るのだけど、コマンド実行時に送信側で確認できるのは最後のファイル転送コマンドが実行された所までで送信したファイルの内容が転送元と転送先で一致していることは確認出来ない。


コマンド実行時にファイル転送先のファイル内容が正しいことを確認するためには、FTPサーバ側でfcivコマンドを実行してその結果を送信側で取得出来る必要があるのだけど、少なくとも例えばFTPサーバがIISである場合はFTPセッション内で自由にサーバ側のコマンドを実行することが出来ないので、その部分(fcivを実行してその出力ファイルを得る)だけWebサービスなりにして、VBScriptのXMLHTTPとか使って実行&結果を取ってくる必要がある。