🖥️

サイレントインストールにかなり手こずった話

2020/10/19に公開

初めに

私が勤める会社のシステムは、顧客の作業環境にサーバやクライアントをドカッと置いて動かす、いわゆるオンプレミス的なシステム。従って、「環境構築が自動で出来るようになると導入コストも抑えられていいよね」という話から、「個人の作業PCにもデモ環境を構築できると楽だよね」という話になり、社内で暇を持て余していた私に白羽の矢が立った(実際に暇をしていたわけではなく、営業が使用するPCの環境構築をやっていた流れがあっての話)。

社内には数年前に作成された自動環境構築用のバッチがあったが、私がざっと見返してみて以下の点で整備が不十分だと感じた。
 ・まず、バッチファイル名が日本語(正直、「えっ」って思いました)
 ・ログファイルへの出力がない(標準出力のみしかなく、後から見返せない)
 ・例外処理がない(どこで失敗したかが分からない)
 ・環境構築というには不十分(インストールされていること前提のアプリケーションが幾つかある、細かい設定反映が別途必要)

個人的に、「環境をまるっと、何も考えずにインストールしたい」という思いだった(そのおかげでめちゃくちゃ苦労したわけだが。。。)
とりあえず、頑張ったのでその苦労を以下に纏めていく。

環境

作業端末の環境は以下の通り。

  • OS … Windows10 / Windows Server 2016以上(※)

※あくまで「ローカル」に環境を構築することが目的なので、サーバOS側はあまり意識していないが、検証用にAWSを利用していたので念のため記載

アプリケーション

今回の環境構築の対象となるアプリケーションは以下の通り。

  • SQL Server … ローカルに構築するため、Express Editionで十分
  • SQL Server Management Studio … 別途インストールしなきゃいけないのどうにかして
  • Java … 社内システムの都合でJava8
  • Apache Tomcat … Webサーバ

※その他、幾つかアプリケーションがあるが割愛


下準備

I. ログの出力

まず何よりも例外処理をするために、どこでエラーが起きているかを把握しておかないといけないので、ログの出力に取り掛かった。

標準出力とログ出力を同時に実現出来ないものか

リアルタイム(コンソール上)でも状況を把握したいし、ログとして管理しておかないと読み返さない。インターネットで調べると、多くの人が同じ悩みを抱えていた。
 標準出力とログファイルに同時にECHOする方法
 Windowsコマンドプロンプト出力の表示とファイルへのリダイレクト

結論、1つの処理で同時に出力することは無理そうだったので、上記Webページを参考に、疑似的な同時出力関数を作成。以下の点で調整を重ねた。

  • 一般的なloggerのようにログの内容が一目で分かりやすいように、先頭にタグ付け
  • コメント部分は「""」で囲い、コード上でコメント部分を分かりやすくした
  • 上記対応によりログ出力時も「""」が出力されてしまうため、トリミングして調整
// 関数呼び出し側
CALL :OUTPUT INFO "テストコードを出力します."
CALL :OUTPUT WARN "テスト警告コードを出力します."
CALL :OUTPUT ERROR "テストエラーコードを出力します."
// 出力関数
:OUTPUT
REM 第一引数を代入(INFO, WARN, ERROR)
for /f "delims= " %%a in ("%*") do set first=%%a
REM 第二引数以降をまとめて代入
for /f "tokens=1,* delims= " %%a in ("%*") do set EXCEPT_FIRST=%%b

echo. 
REM 標準出力(先頭と末尾の「"」を除く)
echo %EXCEPT_FIRST:~1,-1%
REM ログ出力
echo [%first% %DATE% %TIME:~0,8%] %EXCEPT_FIRST:~1,-1%>> %LOGFILE%
exit /b

ファイルへの書き込みが出来ない

次に直面したのが、ファイル書き込みが出来ない問題。上記の書き方のままだと、ファイル書き込みのリダイレクトとログ出力のリダイレクトが重複してしまう。
加えて、「a="test code"」のように設定値を書き込む必要がある場合、「=」や「"」をうまく渡せない(原因までは詳しく調べていないので、どなたかご存知であれば)。

散々調べたが良さそうな文献はなかったので、記号を渡さないような形の処理を追加。

  • 通常のログ管理と区別するため、「REDIRECT」を宣言して分岐
  • 呼び出し時では記号を使わず、ログやファイル書き込み時に宣言して記入
  • 空白を含めたケースを認識できるよう、「""」で囲む
// 関数呼び出し側(REDIRECT ファイル名 変数名 代入値)
CALL :OUTPUT REDIRECT test.txt "a" "test code"
// 出力関数
:OUTPUT
REM 第一引数を代入(INFO, WARN, ERROR, REDIRECT)
for /f "delims= " %%a in ("%*") do set first=%%a

REM REDIRECT以外は別処理
if %first% neq REDIRECT goto :NOT_RED

REM %%b:ファイル名 / %%c:変数名 / %%d:代入値
for /f "tokens=1,2* delims= " %%a in ("%*") do set TMP_FILE=%%b
for /f "tokens=1,2,3* delims= " %%a in ("%*") do set TMP_LEFT=%%c
for /f "tokens=1,2,3* delims= " %%a in ("%*") do set TMP_RIGHT=%%d
set TMP_LOG="【 %TMP_FILE% 】に次の内容を書き込みます. "

REM ログ出力(書き込み内容確認のため)
echo [INFO %DATE% %TIME:~0,8%] %TMP_LOG:~1,-1%>> %LOGFILE%
echo [INFO %DATE% %TIME:~0,8%] %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%>> %LOGFILE%

echo.
REM 標準出力
echo %TMP_LOG:~1,-1%
echo %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%
REM ログ出力
echo %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%>> %TMP_FILE%
exit /b

:NOT_RED
REM 第二引数以降をまとめて代入
for /f "tokens=1,* delims= " %%a in ("%*") do set EXCEPT_FIRST=%%b

echo.
REM 標準出力
echo %EXCEPT_FIRST:~1,-1%
REM ログ出力
echo [%first% %DATE% %TIME:~0,8%] %EXCEPT_FIRST:~1,-1%>> %LOGFILE%
exit /b

コマンド実行

次に、コマンド実行時のログ出力について考えた。下記Webページを参考に処理を追加。
 バッチファイルのログ作成方法

  • これまで(INFO/WARN/ERROR/RDIRECT)のどれにも当てはまらない場合で分岐
  • コマンド実行時に「2>&1」を付けてログを管理(実際に試せていないので上手くいくか。。。)
  • バッチファイルでは、if文の「or」が使えないとのことだったので、複数条件を並列して評価する時は、1つのケースのみで成立する条件から処理していく(以下ソースの「コマンド実行との分岐」のif文の嵐がそれ)
// 関数呼び出し側
REM 設定ファイルの読み込み
CALL :OUTPUT call setting.bat %ROOT%: %PROCESSOR_ARCHITECTURE%
// 出力関数
:OUTPUT
REM 第一引数を代入(INFO, WARN, ERROR, REDIRECT, その他(今回の例の場合はcall))
for /f "delims= " %%a in ("%*") do set first=%%a

REM REDIRECT以外は別処理
if %first% neq REDIRECT goto :NOT_RED

REM %%b:ファイル名 / %%c:変数名 / %%d:代入値
for /f "tokens=1,2* delims= " %%a in ("%*") do set TMP_FILE=%%b
for /f "tokens=1,2,3* delims= " %%a in ("%*") do set TMP_LEFT=%%c
for /f "tokens=1,2,3* delims= " %%a in ("%*") do set TMP_RIGHT=%%d
set TMP_LOG="【 %TMP_FILE% 】に次の内容を書き込みます. "

REM ログ出力(書き込み内容確認のため)
echo [INFO %DATE% %TIME:~0,8%] %TMP_LOG:~1,-1%>> %LOGFILE%
echo [INFO %DATE% %TIME:~0,8%] %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%>> %LOGFILE%

echo.
REM 標準出力
echo %TMP_LOG:~1,-1%
echo %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%
REM ログ出力
echo %TMP_LEFT:~1,-1%=%TMP_RIGHT:~1,-1%>> %TMP_FILE%
exit /b

:NOT_RED
REM 第二引数以降をまとめて代入
for /f "tokens=1,* delims= " %%a in ("%*") do set EXCEPT_FIRST=%%b

REM コマンド実行との分岐
if %first% neq INFO if %first% neq WARN if %first% neq ERROR goto DO_COMMAND

echo.
REM 標準出力(先頭と末尾の「"」を除く)
echo %EXCEPT_FIRST:~1,-1%
REM ログ出力
echo [%first% %DATE% %TIME:~0,8%] %EXCEPT_FIRST:~1,-1%>> %LOGFILE%
exit /b

:DO_COMMAND
REM ログ出力
echo [INFO %DATE% %TIME:~0,8%] %*>> %LOGFILE%
REM コマンド実行
%*>> %LOGFILE% 2>&1
exit /b

こうして、ログ出力の準備は大方できた。

実行時間の計測

最後に実行時間の計測。単純な引き算では計算が出来なかったため、以下のWebページを参考に一部改良。
 Windowsバッチファイルの時間差を計算する

  • 「type nul > %logfile%」で毎回ログファイルを初期化(今更ながらファイル名に実行時間付けて世代管理できるようにしておけばよかったと後悔)
  • 日付またいだ際の時間調整の処理が上手く出来ていなかったため調整
// 開始時間の取得
REM ログの出力設定
set LOGFILE=%CD%\test_log.log
SET START_DATE=%DATE%
SET START_TIME=%TIME: =0%

REM ログファイル初期化
type nul > %logfile%

CALL :OUTPUT INFO "--------------------------------------------------------"
CALL :OUTPUT INFO "環境構築を開始します."
CALL :OUTPUT INFO "開始日時: %START_DATE% %START_TIME%"
CALL :OUTPUT INFO "--------------------------------------------------------"
// 終了時間の取得 & 時間計測
REM 実行時間の計算
set FIN_DATE=%DATE%
set FIN_TIME=%TIME: =0%

set end=!FIN_TIME:%time:~8,1%=%%100)*100+1!
set start=!START_Time:%time:~8,1%=%%100)*100+1!

set /A elap=((((10!end:%time:~2,1%=%%100)*60+1!%%100)-((((10!start:%time:~2,1%=%%100)*60+1!%%100)
if %elap% lss 0 set /A elap+=((-elap/86400+1)*86400)
set /A cc=elap%%100+100,elap/=100,ss=elap%%60+100,elap/=60,mm=elap%%60+100,hh=elap/60+100

CALL :OUTPUT INFO "--------------------------------------------------------"
CALL :OUTPUT INFO "終了日時: %FIN_DATE% %FIN_TIME%"
CALL :OUTPUT INFO "経過時間: %hh:~1%%time:~2,1%%mm:~1%%time:~2,1%%ss:~1%%time:~8,1%%cc:~1%"
CALL :OUTPUT INFO "--------------------------------------------------------"

こうして下準備が整ったので次のステップへ(実際にはトライアンドエラーでやっていたので順番通りにしていたわけではないが)

II. 例外処理

これは単純に、"%ERRORLEVEL%"を見てifで分岐。

// 例外処理
/* 何らかの処理 */

if not %ERRORLEVEL% == 0 (
	CALL :OUTPUT ERROR "読み込みに失敗しました. 環境構築を中止します."
	REM 終了処理の記述があるところまでスキップ
	goto FINISH
)

III. スコープ

batにおいてこの言葉が適切かは分からないが、これが非常に厄介だった。「遅延環境変数」とか調べてもよく分からなかったので、以下の点に注意した。

  • if文やfor文などはなるべく括弧を使わないように修正(ほとんどこれで済んだので、遅延環境変数自体そんなに気にしなかった)
  • for文などでどうしても必要な場合は、とりあえず「!!」で囲む(正直その判断基準が分かっていない←)
// 冒頭
@echo off
setlocal enabledelayedexpansion

REM if文(エラー処理)
if not %ERRORLEVEL% == 0 CALL :OUTPUT ERROR "エラーが発生しました."

REM for文(例:ファイアーウォールの設定を反映する)
for /f "tokens=1,2-3,4*" %%a in (%FIREWALL_FILE%) do (
	if "%%a" neq "REM" if "%%d" neq "delete" (
		SET STR_DEL=%%a %%b %%c %%d %%e
		echo !STR_DEL!>> %LOGFILE%
		!STR_DEL!>> %LOGFILE%

		if not !ERRORLEVEL! == 0 (
			CALL :OUTPUT WARN "ファイアーウォールの設定反映に失敗しました. 詳細を確認の上、適切に処理してください."
		)
	)
)

endlocal
pause
EXIT

/* 以下に疑似関数の記述部分が続く */

ようやく一通りの下準備が出来たので、本題に入る。


サイレントインストール

ようやく、本題の自動環境構築に入る。

I. SQL Server

以下のサイトを参考に、バッチからpower shellを起動する形で実装。
 ・バッチから設定ファイル(ConfigurationFile.ini)を渡す
 ・isoイメージのマウント
 ・setup.exeの実行
 PowerShellでSQL Server2016をサイレントインストールする

なお、サイレントインストールするために、設定ファイルに以下の内容を反映。

  • SAPWD="password"
  • ADDCURRENTUSERASSQLADMIN="false"
  • TCPENABLED="1"
  • // UIMODE="Normal"
// 呼び出し元
REM %SQL_IST%:インストールファイル名 / %SQL_FILE%:isoファイル名 / %SQL_CONF%:設定ファイル(ConfigurationFile.ini)
CALL :OUTPUT powershell -NoProfile -ExecutionPolicy Unrestricted ".\%SQL_IST%" %SQL_FILE% %SQL_CONF%
// インストール
# PATHの設定
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
$isoPath = $scriptDir + "\" + $Args[0]
$confPath = $scriptDir + "\" + $Args[1]

# ISOのマウント
Mount-DiskImage $isoPath

# ドライブの取得
$driveLetter = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter
$driveRootPath = "{0}:\" -f $driveLetter
$sqlServerSetupExe = $driveRootPath + "SETUP.exe"

# SQL Serverインストール
Start-Process -FilePath $sqlServerSetupExe -ArgumentList "/QS /IAcceptSQLServerLicenseTerms=True /Configurationfile=$confPath" -Verb runas -Wait

# ISOマウント解除
Dismount-DiskImage $isoPath

version / editionの違いによる障害

最初、SQL Server 2019のExpress Editionのインストールを試していたため、それ用の設定ファイルを使っていた。
次に、ローカルで試す分にはExpressでも問題ないが、念のため別のものも試そうと、SQL Server 2019のDeveloper Editionで試したところ、インストール状況の確認ウィンドウが突然落ちる現象が発生した。ログにもコンソール上にもエラーが残らず、原因が全く分からず困っていた。

1度、手動でDeveloper Editionのインストーラから設定ファイルの作成を行い、Express Editionの設定ファイルの差分を確認。
幾つかの差分が確認され、1つ1つ設定を合わせながら確認していったところ、以下の設定が悪さをしていることが判明。

  • AGTSVCSTARTUPTYPE = Disable(→ Enable に変更)
  • ENABLERANU = True(→ False に変更)

II. SQL Server Management Studio

SSMSのインストール自体は、以下サイトを参考にすることですんなり行けた。
 silent_install_Microsoft-SQL-Server-Management-Studio

REM %SSMS_FILE%:インストーラファイル名
CALL :OUTPUT %SSMS_FILE% /install /quiet /norestart /passive

DBリストア時の障害

しかしその後、肝心のDBのリストアが上手くいかない。
というのも、SQL Serverのバージョンによって、DBのデータファイルが格納されるディレクトリが違うという。何と厄介な。。。
 SQL Server の既定のインスタンスおよび名前付きインスタンスのファイルの場所

そこで、デフォルトで存在するtempdbからパスを取得する方法で回避。
 SQL ServerのDATAフォルダの場所を取得する方法は?
 SQL Server 実践「SQL Server 2016 への移行とアップグレードの実践」

以下、リストア用のSQLファイルを使って、2種類のDB(.bakファイル)を同時にリストアする。

  • バッチファイル側から該当DBバックアップファイル名を引数として渡して処理させた
  • 取得したデータパスの値を利用して、一時テーブルからリストアを実行
  • 一時テーブルは、SQL Serverのバージョンに合わせて必要カラムを調整
// 呼び出し元
REM %RST_SQL%:リストアSQLファイル名 / %DB1_NAME%:DBバックアップファイル1 / %DB2_NAME%:DBバックアップファイル2
SET TMP_PRM="%CD%\"
SET OPT="-S localhost -U %DB_USR% -P %DB_PSW% -d master -i %RST_SQL% -v Param1=%TMP_PRM% Param2=%DB1_NAME% Param3=%DB2_NAME%"
CALL :OUTPUT sqlcmd %OPT:~1,-1%
-- リストアSQL
use master
go

-- 変数定義
DECLARE @bkPath varchar(200) = '$(Param1)'; -- バックアップファイルのPATH
DECLARE @dbName varchar(100); -- データベースの名称
DECLARE @bkFname varchar(200); -- 読み込みバックアップファイルのPATH + ファイル名

DECLARE @outPath varchar(1000); -- データパス(mdf、ldfファイルの配置先)
DECLARE @mdfName varchar(100); -- mdfファイルの名称
DECLARE @ldfName varchar(100); -- ldfファイルの名称
DECLARE @outMdf varchar(200); -- mdfファイルのPATH + ファイル名
DECLARE @outLdf varchar(200); -- ldfファイルのPATH + ファイル名

DECLARE @command varchar(1000); -- 実行コマンド
DECLARE @check integer; -- テーブルの存在チェック用


/* print の結果がログに出るので、適宜出力させると良いかも */
-- 0. 開始処理(テーブルの有無、データの有無の確認)
select @check = object_id from [tempdb].sys.objects where name like '#FileListHeaders%'
IF @check IS NOT NULL
	BEGIN
		delete from #FileListHeaders;
		drop table #FileListHeaders;
	END

-- 1. データパスの取得
SELECT @outPath = replace(physical_name, 'master.mdf', '') FROM master.sys.database_files WHERE name='master'


-- 2. バックアップファイルの設定データ格納用テーブル作成
CREATE TABLE #FileListHeaders (
	  LogicalName				nvarchar(128)
	, PhysicalName				nvarchar(260)
	, [Type]					char(1)
	, FileGroupName				nvarchar(128) NULL
	, Size						numeric(20,0)
	, MaxSize					numeric(20,0)
	, FileID					bigint
	, CreateLSN					numeric(25,0)
	, DropLSN					numeric(25,0) NULL
	, UniqueID					uniqueidentifier
	, ReadOnlyLSN				numeric(25,0) NULL
	, ReadWriteLSN				numeric(25,0) NULL
	, BackupSizeInBytes			bigint
	, SourceBlockSize			int
	, FileGroupID				int
	, LogGroupGUID				uniqueidentifier NULL
	, DifferentialBaseLSN		numeric(25,0) NULL
	, DifferentialBaseGUID		uniqueidentifier NULL
	, IsReadOnly				bit
	, IsPresent					bit
);

-- Greater than SQL 2005
IF cast(cast(SERVERPROPERTY('ProductVersion') as char(4)) as float) > 9
	BEGIN
		ALTER TABLE #FileListHeaders ADD TDEThumbprint varbinary(32) NULL
	END

-- Greater than 2014
IF cast(cast(SERVERPROPERTY('ProductVersion') as char(2)) as float) > 12
	BEGIN
		ALTER TABLE #FileListHeaders ADD SnapshotURL nvarchar(360) NULL
	END


/* DBリストア自体の評価もできると良い */
-- 3. 設定値の取得 ~ DBのリストア
-- 3-1. 【DB1】
set @dbName = '$(Param2)';
set @bkFname = @bkPath + @dbName + '.bak';

select @command = 'RESTORE FILELISTONLY FROM DISK = N''' + @bkFname + '''';
INSERT INTO #FileListHeaders EXEC (@command);
SELECT @mdfName = LogicalName FROM #FileListHeaders WHERE Type = 'D';
SELECT @ldfName = LogicalName FROM #FileListHeaders WHERE Type = 'L';
SELECT @outMdf = @outPath + LOWER(@mdfName) + '.mdf';
SELECT @outLdf = @outPath + LOWER(REPLACE(@ldfName, '_log', '')) + '_0.ldf';


SELECT @command = 'RESTORE DATABASE ' + @dbName + ' FROM DISK = N''' + @bkFname + ''' WITH REPLACE';
SELECT @command += ', MOVE ''' + @mdfName + ''' to ''' + @outMdf + '''';
SELECT @command += ', MOVE ''' + @ldfName + ''' to ''' + @outLdf + '''';
EXEC (@command);


-- 3-2. 【DB2】
delete from #FileListHeaders
set @dbName = '$(Param3)';
set @bkFname = @bkPath + @dbName + '.bak';

select @command = 'RESTORE FILELISTONLY FROM DISK = N''' + @bkFname + '''';
INSERT INTO #FileListHeaders EXEC (@command);
SELECT @mdfName = LogicalName FROM #FileListHeaders WHERE Type = 'D';
SELECT @ldfName = LogicalName FROM #FileListHeaders WHERE Type = 'L';
SELECT @outMdf = @outPath + LOWER(@mdfName) + '.mdf';
SELECT @outLdf = @outPath + LOWER(REPLACE(@ldfName, '_log', '')) + '_0.ldf';

SELECT @command = 'RESTORE DATABASE ' + @dbName + ' FROM DISK = N''' + @bkFname + ''' WITH REPLACE';
SELECT @command += ', MOVE ''' + @mdfName + ''' to ''' + @outMdf + '''';
SELECT @command += ', MOVE ''' + @ldfName + ''' to ''' + @outLdf + '''';
EXEC (@command);

III. Java

インターネットで調べても、JREのサイレントインストールについては幾つか記事が出てくるが、JDKについてはあまりなくて、ひたすらに調べた。最終的には以下の記事が参考になった(と思う)。
 コマンドラインからWindowsマシンにJDK 8およびJRE 8をサイレントインストールする
 Windows上の特定のディレクトリにJava JDKを静かにインストールする方法

  • JDKのインストール先を環境に合わせれるように、ベースの設定ファイルを準備
REM 環境に合わせた設定ファイルを作成
echo F | xcopy %JDK_CONF_TMP% %JDK_CONF% /Y
CALL :OUTPUT REDIRECT %JDK_CONF% "INSTALLDIR" "%JDK_INSTALL_PATH%"

CALL :OUTPUT %JDK_FILE% INSTALLCFG=%CD%\%JDK_CONF%

IV. Apache Tomcat

Javaがインストール出来ていないと上手く動作しない模様。
また、サイレントインストールだと、スタートメニューに構成メニューが出てこないようなので、ショートカットを作成して配置するように。

  • ショートカット作成は、バッチファイルから設定用ファイルを作成し、wsfファイルにて読み込んで作成する流れに
// インストール
CALL :OUTPUT %TOMCAT_FILE% /S /D=%TOMCAT_INSTALL_PATH%

// 設定反映、ショートカット作成
REM サービスの自動起動
CALL :OUTPUT sc config Tomcat8 start= auto

REM 設定系の反映
CALL :OUTPUT copy "conf\conf" "%TOMCAT_INSTALL_PATH%\" /Y
if not %ERRORLEVEL% == 0 CALL :OUTPUT ERROR "Tomcat の設定変更に失敗しました. スキップして後続処理に移ります."

REM ショートカットの作成
if not exist %START_MENU_PATH%\ApacheTomcat8.0 (
	mkdir %START_MENU_PATH%\ApacheTomcat8.0
)

echo "Configure Tomcat","%START_MENU_PATH%\ApacheTomcat8.0","C:\work\Tomcat8.0\bin\Tomcat8w.exe //ES//Tomcat8","StartMenu","ApacheTomcat8.0"> %SC_CONF%
echo "Monitor Tomcat","%START_MENU_PATH%\ApacheTomcat8.0","C:\work\Tomcat8.0\bin\Tomcat8w.exe //MS//Tomcat8","StartMenu","ApacheTomcat8.0">> %SC_CONF%
CALL :OUTPUT cscript %SC_FILE%
// Windows Script File
<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<package>
  <job id="shortcut">
  <?job error="true" debug="true" ?>
  <object id="objFs" progid="Scripting.FileSystemObject" />
  <script language="VBScript">
  <![CDATA[
  Const CONFIG=".\sc_config.txt"
  Set objTs=objFs.OpenTextFile(CONFIG, 1, False)
  Do While Not objTs.AtEndOfStream
    aryDat=Split(objTs.readLine, Chr(Asc(",")))
    Set objShl=WScript.CreateObject("WScript.Shell")

    If UBound(aryDat) = 4 Then
      Set objCut=objShl.CreateShortcut(objFs.BuildPath(objShl.SpecialFolders("aryDat(3)") & "\" & aryDat(4), aryDat(0) & ".lnk"))
    Else
      Set objCut=objShl.CreateShortcut(objFs.BuildPath(objShl.SpecialFolders("aryDat(3)"), aryDat(0) & ".lnk"))
    End If

    objCut.WorkingDirectory=aryDat(1)
    objCut.TargetPath=aryDat(2)
    objCut.Save
  Loop
  objTs.Close
  ]]>
  </script>
  </job>
</package>
# ショートカット作成用ファイル
"Configure Tomcat","%START_MENU_PATH%\ApacheTomcat8.0","C:\work\Tomcat8.0\bin\Tomcat8w.exe //ES//Tomcat8","StartMenu","ApacheTomcat8.0"
"Monitor Tomcat","%START_MENU_PATH%\ApacheTomcat8.0","C:\work\Tomcat8.0\bin\Tomcat8w.exe //MS//Tomcat8","StartMenu","ApacheTomcat8.0"

V. ファイアーウォール

インストールアプリケーション用のポートを開放するために、ファイアーウォールの設定を反映させる。
今回は、外部ファイル(これまたbatファイル)に設定する内容を記述し、読み込んで処理していく。

なお、ファイアーウォールの設定スクリプトの仕様上、「name="SQL Server"」のような記述が必要で、前述の通りログ出力が上手くいかない為、ここは仕方なく関数には渡さずに処理内で実行。
また、例外処理などの副産物的な記述については割愛。

既存の設定の削除

// 設定内容
REM ******** 削除用設定 ********
netsh advfirewall firewall delete rule name="SQL Server"
netsh advfirewall firewall delete rule name="Tomcat"
// 呼び出し元
for /f "tokens=1,2-3,4*" %%a in (%FIREWALL_FILE%) do (
	if "%%a" neq "REM" if "%%d" equ "delete" (
		SET STR_DEL=%%a %%b %%c %%d %%e
		echo !STR_DEL!>> %LOGFILE%
		!STR_DEL!>> %LOGFILE%

		if not !ERRORLEVEL! == 0 (
			CALL :OUTPUT WARN "ファイアーウォールの設定削除に失敗しました. 詳細を確認の上、適切に処理してください."
		)
	)
)

設定追加

// 設定内容
REM ******** 追加・編集用設定 ********
netsh advfirewall firewall add rule name="SQL Server" dir=in action=allow protocol=tcp localport=1433 profile=private
netsh advfirewall firewall add rule name="Tomcat" dir=in action=allow protocol=tcp localport=8085 profile=private
// 呼び出し元
for /f "tokens=1,2-3,4*" %%a in (%FIREWALL_FILE%) do (
	if "%%a" neq "REM" if "%%d" neq "delete" (
		SET STR_DEL=%%a %%b %%c %%d %%e
		echo !STR_DEL!>> %LOGFILE%
		!STR_DEL!>> %LOGFILE%

		if not !ERRORLEVEL! == 0 (
			CALL :OUTPUT WARN "ファイアーウォールの設定反映に失敗しました. 詳細を確認の上、適切に処理してください."
		)
	)
)

VI. その他

ドライブの管理

「ローカル」の環境構築を見越していたため、とりあえずCドライブで問題ないだろうと高を括って作業していた矢先、社内から「システムに関係するデータはDドライブが良いからそっちにインストール出来るようにしてください」との声が。(発狂)

なので、ドライブを自由に切り替えられるように修正。(そこまで大したほどの内容でもなかった←)

REM インストール先設定
SET /P ROOT="インストール先のドライブを指定してください. ( 例:  C ) > "
if not exist %ROOT%:\ (
	CALL :OUTPUT ERROR "指定されたドライブが見つからなかったため、環境構築を中止します."
	goto FINISH
)
CALL :OUTPUT INFO "%ROOT%ドライブ直下に環境を構築します."

最後に

まだ最終系ではない

最初に記載の通り、今回は「ローカル」に環境を構築するために整備した、叩き台的なところまでしか完成できていない。
今後、社内システムの本番環境にも対応できるように、Apache Tomcatを複数本構築出来るようにm修正したり、SQL Serverのバージョンの検証をしたりなど、まだまだ課題が残っている。

感想

正直なところ、社内にはシステムの環境構築用のマニュアルも整備されていたので、そっちで我慢してほしいところではあった。
が、自分のスキルも身に付いたし、同じ問題に直面した人達の役に立つのであれば、作業も意味を成すのかなと思う。
おそらく、自分も同じ道をもう一回辿ることになるので、こうして形に残しておく。

(この記事があまりにも長くなったので、ステップ別に分けるかは別途検討しよう。)

Discussion