Windowsサーバー上のTomcatにデプロイするbatファイルを作ってみた

# はじめに
今のPJでは開発サーバー(Windows Server 2008 R2)は社内環境上に、Jenkinsは別の環境上にあるという状況のため開発サーバーにあるTomcatにデプロイするために、
1. 自分のローカルマシンにインターネットでJenkinsに繋いで、ビルドしたwarをダウンロード
2. 自分のローカルマシンから開発サーバーにリモートデスクトップで繋いで、warをデプロイ
ということを手作業で行っています。
※開発サーバーはインターネットに繋いじゃダメという前提です。

![env.png](https://qiita-image-store.s3.amazonaws.com/0/62776/ad03e0c7-a81a-55e8-80d0-e60933a203ae.png)

1回、2回の作業なら別にいいんですが、毎日となると面倒臭くてしょうがなかったため、batファイルを使って一発でデプロイできるものを作ってみました。

# Windows用のwgetとPsToolsのインストール
今回作ったbatファイルでは、Jenkinsからwarをダウンロードするのにwgetコマンドを、Tomcatのworkディレクトリを削除したりするのにPsToolsのpsexecコマンドを使っています。
wgetはLinuxに入っているのとまったく同じように使えるものなので、説明は割愛します。
psexecはリモートマシン上にあるコマンドを実行することができるコマンドです。

というわけで、まずwgetとpsexecが使えるようにします。
## wgetをインストール
1. http://gnuwin32.sourceforge.net/packages/wget.htm からBinariesとDependenciesのzipをローカルに保存
2. 保存したzipを両方解凍してDependencies側のbinフォルダ配下の全てのdllをBinaries側のbinフォルダにコピー
3. Binaries側のbinにPathを通す

## PsToolsのインストール
1. https://technet.microsoft.com/ja-jp/sysinternals/bb896649.aspx からPsToolsをダウンロード
2. ダウンロードしたzipを解凍して、Pathを通す

# デプロイするbatファイル
作ったbatファイルは以下の様な感じです。

```bat:deploy.bat

@echo off

rem ***************************************
rem ログファイル名取得
rem ***************************************
echo %date%
echo %time%
 
set yyyy=%date:~0,4%
set mm=%date:~5,2%
set dd=%date:~8,2%
 
set time2=%time: =0%
 
set hh=%time2:~0,2%
set mn=%time2:~3,2%
set ss=%time2:~6,2%
 
set filename=%yyyy%-%mm%%dd%-%hh%%mn%%ss%

set log=%filename%.log

rem **********************************************************
rem Jenkinsからwarを取得
rem **********************************************************
wget -N -P "C:\for_remote_deploy_temp"  http://<jenkins_host>/jenkins/job/<target_job>/lastSuccessfulBuild/artifact/dist/target.war >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

rem **********************************************************
rem APサーバーのTomcatを停止する
rem **********************************************************
for /F "tokens=1,2,3,4,5 usebackq delims= " %%a in (`sc <deploy_server_host> query "Tomcat6" ^| findstr -i state`) do ( 
  if "%%a"=="STATE" ( 
      if "%%d"=="RUNNING" ( 
           sc <deploy_server_host> stop "Tomcat6"  >> %log% 2>&1
           if %ERRORLEVEL% neq 0 goto error
      ) 
   ) 
 ) 
rem **********************************************************
rem サーバーが止まっているか確認して、止まっていなかったら30秒待つ
rem **********************************************************
set /a CNT=0

:wait_for_stopping
for /F "tokens=1,2,3,4,5 usebackq delims= " %%a in (`sc <deploy_server_host> query "Tomcat6" ^| findstr -i state`) do (
    if "%%a"=="STATE" ( 
        if "%%d" neq "STOPPED" ( 
            timeout 30 
        ) else ( 
            goto continue_process 
        ) 
    ) 
 ) 
set /A CNT=%CNT%+1

if "%CNT%" lss "2" ( 
  goto wait_for_stopping 
  
 ) else ( 
  goto exit 
 )


:continue_process
rem ******************************************************************
rem psexecコマンドを使用して、warと展開ディレクトリ、workを全削除する
rem ******************************************************************
rem war削除
psexec <deploy_server_host> cmd /c del <tomcat_catalina_base>\webapps\target.war  >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

rem 展開ディレクトリ削除
psexec <deploy_server_host> cmd /c rd <tomcat_catalina_base>\webapps\target /s /q  >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

rem work削除
psexec <deploy_server_host> cmd /c rd <tomcat_catalina_base>\work\target /s /q  >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

rem **********************************************************
rem xcopyでwarをAPサーバーにコピーする
rem **********************************************************
xcopy "C:\for_remote_deploy_temp\target.war" "<tomcat_catalina_base>\webapps"  >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

rem **********************************************************
rem APサーバーのTomcatを開始する
rem **********************************************************
sc <deploy_server_host> start "Tomcat6"  >> %log% 2>&1
if %ERRORLEVEL% neq 0 goto error

:error

:exit
exit

```

# 適当な解説
## scコマンド
サービス管理/操作するためのコマンドです。ホストを指定してあげるとリモートマシンにも使うことができます。対象のサービスは、タスクマネージャのサービスタブに表示されるサービス表示名ではなく、内部KEY名で指定する必要があります。
TomcatをWindowsにデフォルトインストールした場合は、

* 内部KEY名：Tomcat6
* サービス表示名：Apache Tomcat 6

となっています。
サービス表示名から内部KEY名を引くためには

```bat:GetKeyName.bat
C:\>sc <host> GetKeyName "Apache Tomcat 6"
[SC] GetServiceKeyName SUCCESS
名前 = Tomcat6
```

とするとわかります。

scコマンドでサービスの起動/停止をすると、起動/停止のシグナルは送信しますが実際にサービスが動き始めたのか、止まったのかまではわかりません。(シグナルを送信した時点でコマンドが返ってきてしまうため)

現時点でのサービスの状態を知るためには、下記のようにqueryを使います。

```bat:statusCheck.bat
C:\>sc <host> query "Tomcat6"
SERVICE_NAME: Tomcat6
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
```

今回作ったbatファイルではfor文のtokens,usebackq,delimsを使い、STATE部分の文字列を抜き出し、取得したサービスの状態によって次処理を切り替えるようにしています。

# もしコピって使いたいという方がいたら・・・
自分が作っていて、ハマったポイントがあるので書いておきます。コピって使う方がいたら注意して下さい。(batファイル作る上で常識っぽい事なので、知っている方は読み飛ばして下さい。)

1. if文の括弧 "(" と ")"の前後には、”必ず”空白を入れて下さい。
空白がデリミタになっているようなので、空白がないと正しく動きません。

2. for文でusebackqを指定しinでコマンドを発行する場合、パイプでコマンドをつなぐことが可能ですが、パイプの前にキャレット"^"でエスケープする必要があります。

3. for文で半角スペースをデリミタにして文字列を分割するためdelimsを指定しています。なので、delimsの直後には半角スペースが入っています。


# psserviceコマンドについて
今回作ったbatファイルでは、Tomcatサービスの起動/停止にscコマンドを使っていますが、PsToolsにもリモートマシンのサービスを管理/操作するpsserviceというコマンドがあります。最初はpsserviceを使っていたのですがバグ？があるようで、psserviceでサービスを操作するとリモートマシンのCPUが100%に張り付いてしまうため、使用を断念しました。

# 追記(2015/06/25更新)
## scコマンドのqueryexについて
上のscコマンドのqueryよりさらに詳細情報を取得できるqueryexというのがあります。

```bat:queryex.bat
c:\>sc <host> queryex "Tomcat6"

SERVICE_NAME: Tomcat6
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 8872
        FLAGS              :
```

これを使うとPIDも取得できます。
例えば、サービスがハングアップしてしまっている状態(STOP_PENDINGなど)で強制的にプロセスをkillしたい場合は、以下のようにするといいかもしれません。

```bat:taskkill.bat
for /F "tokens=1,2,3,4,5 usebackq delims= " %%a in (`sc <host> queryex "Tomcat6" ^| findstr -i PID`) do ( 
        psexec <host> cmd /c taskkill /PID "%%c" /F >> %log% 2>&1 
        if %ERRORLEVEL% neq 0 goto error 
 ) 
```

まぁサービスの状態だけで、いきなりプロセスをkillしていいのかっていう話はありますが・・・
ご参考までに