ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【iexpress】iexpressでインストーラとアンインストーラを作成する

 インストーラアンインストーラについて今まで作成したことがなかったのですが、Windows標準のアプリである「iexpress」を使えば一応のそれらが作成できることがわかったので、今回はその備忘録になります。


 では、始めます。


1:iexpressとは
 iexpressは簡単に言うと「複数のファイルをまとめてexeに固めてくれる」というようなアプリです。

 過去の記事でpyinstallerについて書きましたが、要はこれと同じでexeを起動するとその中身が展開されて特定ファイルを実行できるという代物です。

 この機能を使って疑似的なインストーラアンインストーラを作っていきます。


2:iexpresでexeを作成する
 まずはiexpressを使ってみます。

 「Windows+R」キーで出てきたダイアログに「iexpress」と入力してエンターキーを押下します。

 すると以下のようにiexpressが起動します。

 初めてのexeを作成する場合は、チェックボックスの上の方を選択した状態で「次へ」ボタンをクリックします。

 Package purposeの画面が表示されます。単にファイルの圧縮・解凍のみを行う(Extract files only)のか、解凍時にファイルを自動実行させるか(Extract files and run an installation command)を選択します。
 今回はインストーラアンインストーラを作成したいので、解凍時にファイルを自動実行する「Extract files and run an installation command」の方を選択して「次へ」ボタンをクリックします。

 Package titleの画面が表示されます。ここではexe実行時に出てくるダイアログのタイトル名を設定します。次の画面でダイアログを表示するかどうか選択できますが、表示しない場合でも何か入力しておく必要があります。値を設定したら「次へ」ボタンをクリックします。

 Confirmation promptの画面が表示されます。先に説明したようにここではexe実行時のダイアログを表示するかどうかを設定します。表示しない場合は「No prompt」、表示する場合は「Prompt user with」を選択して表示させたいメッセージを入力します。
 今回はダイアログを表示させないので「No prompt」を選択して「次へ」ボタンをクリックします。

 License agreementの画面が表示されます。ここではライセンスを表示するかどうかを設定します。表示しない場合は「Do not display a license」、表示する場合は「Display a licence」を選択してライセンスファイルを設定します。
 今回はライセンスは表示しないので「Do not display a license」を選択して「次へ」ボタンをクリックします。

 Package filesの画面が表示されます。ここではexeに含めるファイルを選択します。「add」ボタンをクリックして必要なファイルをすべて追加してから「次へ」ボタンをクリックします。

 Install Program to Launchの画面が表示されます。Package purposeの画面で解凍時にファイル自動実行を選択した場合に表示され、ここで自動実行するファイルを選択します。上にある「Install Program」は必須でこれが解凍時に自動実行されます。そしてその処理が終わった後に、下の方で別のファイルを実行する設定もできます。
 ここで注意なのですが、どうやら設定できるのは「拡張子が.cmdのファイルのみ」のようです。プルダウンではbatファイルなどが出てきますが、batファイルを設定してもインストーラ実行時にはエラーになるので「拡張子がcmdのファイル」を設定するようにしてください。中身はbatでcmdの拡張子に変更するだけで対応できます。
 今回は実行ファイルに「INSTALL_CALL.cmd」とcmdファイルを設定して「次へ」をクリックします。

 Show windowの画面が表示されます。これは先ほど設定したファイル実行中にウインドウを表示するかを設定します。
 今回は表示させたくないので「Hidden」を選択して「次へ」をクリックします。

 Finished messageの画面が表示されます。exeの解凍処理が終了した後にメッセージを表示するかどうかを設定します。
 今回は表示しないので「No message」を選択して「次へ」をクリックします。

 Packege Name and Optionsの画面が表示されます。「Browse」ボタンを押下してexeを保存する階層とファイル名を設定します。Optionsは下の方は「長いファイル名を採用するかどうか」のチェックなので基本的にチェックを入れます。Optionsの上の方は解凍中にダイアログでファイルの展開状況を表示させるかどうかを設定します。
 今回は以下のように設定し、「次へ」をクリックします。

 Configure restartの画面が表示されます。exe実行後に再起動を行うかどうかの設定を行います。
 今回は再起動の必要がないので「No restart」を選択して「次へ」をクリックします。

 Save Self Extractionの画面が表示されます。今までiexpressの設定をしてきた内容をファイルに保存するかを設定します。毎回設定するのは面倒なので上を選択してファイルに保存しておくのが良いです。
 今回はファイルに保存するよう設定して「次へ」をクリックします。

 Create packageの画面が表示されるので「次へ」をクリックします。

 しばらく待つとexeが作成されるので「完了」をクリックします。

 ちゃんと保存フォルダにexeとiexpressの設定ファイルであるsedファイルが作成されているのが確認できると思います。

 ちなみにsedファイルを使いたい場合は、iexpressの最初の画面で下の方を選び「Browse」ボタンから対象のsedファイルを選択します。

 後は出てくる画面全てで「次へ」を選択すればexeが作成されます。


3:iexpressでインストーラを作成
 iexpressの使い方がわかったので、Program Files(x86)内に「TEST_IEXPRESS」というフォルダを作成しその中に「test.bat」ファイルを配置するインストーラ、を作成してみます。

 前提としてCドライブ直下に「iexpress_test」というフォルダを作成し、その中にさらに「INSTALLER」フォルダを作成してその中に必要なファイルを作っていく形にします。

 まずは適当なtest.batを作成します。

・test.bat

echo "テストです"
pause

 次にインストール処理のメインとなるpowershellのファイルを作成します。

・INSTALL_MAIN.ps1

# アセンブリの読み込み
Add-Type -Assembly System.Windows.Forms

# メッセージボックスを表示する関数
function Show-Message([string]$msg, [string]$title, [string]$type="OK", [string]$icon="None") {
  $showMessageRes = [System.Windows.Forms.MessageBox]::Show($msg, $title, [System.Windows.Forms.MessageBoxButtons]::$type, [System.Windows.Forms.MessageBoxIcon]::$icon)
  return $showMessageRes
}

# エラーが出たらストップする
$ErrorActionPreference = "Stop"

# スクリプト実行場所のディレクトリ取得
$currentDir = Split-Path $MyInvocation.MyCommand.Path

# インストール先ファイルパスを設定
$programFilesDir = "C:\Program Files (x86)"
$saveDir = "${programFilesDir}\TEST_IEXPRESS"

# ファイルパスを作成
$batFile = "${currentDir}\test.bat"
$toBatFile = "${saveDir}\test.bat"

# 既にインストールされているかチェック
if((Test-Path $saveDir)) {
  # 既にインストールされていたの場合は中断
  Show-Message "${saveDir} と同名のフォルダが既に存在したためインストールを中断します。" "確認" "OK" "Warning"
  exit 200
}

# フォルダ作成
try {
  New-Item $saveDir -ItemType Directory -Force > $null
} catch {
  Show-Message "${saveDir} フォルダの作成に失敗しました。" "エラー" "OK" "Error"
  exit 200
}

# ファイルコピー処理
try {
  Copy-Item $batFile $toBatFile > $null
} catch {
  Show-Message "${saveDir} 配下にファイルをコピーできませんでした。" "エラー" "OK" "Error"
  exit 200
}

# インストール完了
Show-Message "インストール処理が正常に終了しました。" "完了"
exit

 次にメイン処理のpowershellを管理者権限で呼び出すバッチファイルを作成します。

・INSTALL_RUN.bat

@echo off

@REM このファイルがある階層へ移動
cd /d %~dp0

@REM 管理者権限、ポリシー無制限で実行
powershell -NoProfile -ExecutionPolicy Unrestricted ".\INSTALL_MAIN.ps1" -verb runas

@REM 処理が終わったら処理が終わったファイルを作成
echo end > end.txt

 最後にexeの解凍時に実行されるcmdファイルを作成します。

・INSTALL_CALL.cmd

@echo off

@REM ウインドウ非表示、管理者権限で実行
powershell -Command Start-Process "INSTALL_RUN.bat" -WindowStyle Hidden -verb runas

@REM エラー起きたら終了
if %errorlevel% neq 0 (
    goto :END
)

@REM endファイルが作成されるまでこの処理を終了させない
:LOOP
if exist end.txt (
    del /f end.txt
    goto :END
)
goto :LOOP

:END

 LOOPで処理待ちをしているのはINSTALL_RUN.batを呼び出した瞬間に「INSTALL_CALL.cmd」の処理が終了し、呼び出し先も一緒に終了してしまうためです。
 それを防ぐために「INSTALL_RUN.bat」の処理が終了した時にファイルを作成し、そのファイルが作成されるまで終了せずに待つという形になっています。

 色々とファイルが出てきて少しややこしくなったので、一度整理します。以下のような順番で処理が進んでいきます。
1:exe解凍後、INSTALL_CALL.cmdが自動実行
2:INSTALL_CALL.cmdからINSTALL_RUN.batを呼び出し
3:INSTALL_RUN.batからINSTALL_MAIN.ps1を管理者権限で実行
4:INSTALL_MAIN.ps1内の処理でProgram Files(x86)にフォルダを作成してtest.batをコピーして配置
5:INSTALL_MAIN.ps1の処理終了後、INSTALL_RUN.batで終了したとわかるファイルを作成
6:処理終了ファイルが存在すればINSTALL_CALL.cmdの処理を終了

 これらのファイルを「C:\iexpress_test\INSTALLER」配下に以下のように配置します。

 あとは2の手順と同じようにiexpressでexeを作成します。exeとsedの保存先は「C:\iexpress_test」直下にしておくと色々と楽です。

 実際にiexpressで作成すると以下のようになります。

 これでexeを実行すると環境によっては管理者権限の許可が求められ、許可をするとインストール処理が実行されます。


4:iexpressでアンインストーラを作成
 次はアンインストーラを作成していきます。インストーラと違って削除するだけなのと呼び出し部分はほぼ同じでいけるので比較的楽に作成できます。

 前提としてCドライブ直下に「iexpress_test」というフォルダを作成し、その中にさらに「UNINSTALLER」フォルダを作成してその中に必要なファイルを作っていく形にします。

 まずはアンインストールのメイン処理になるpowershellファイルを作成します。

・UNINSTALL_MAIN.ps1

# アセンブリの読み込み
Add-Type -Assembly System.Windows.Forms

# メッセージボックスを表示する関数
function Show-Message([string]$msg, [string]$title, [string]$type="OK", [string]$icon="None") {
  $showMessageRes = [System.Windows.Forms.MessageBox]::Show($msg, $title, [System.Windows.Forms.MessageBoxButtons]::$type, [System.Windows.Forms.MessageBoxIcon]::$icon)
  return $showMessageRes
}

# エラーが出たらストップする
$ErrorActionPreference = "Stop"

# インストール先ファイルパスを設定
$programFilesDir = "C:\Program Files (x86)"
$saveDir = "${programFilesDir}\TEST_IEXPRESS"

# 既存のインストール先フォルダがある場合は強制削除
if ((Test-Path $saveDir)) {
  try {
    Remove-Item $saveDir -Recurse -Force
  } catch {
    Show-Message "${saveDir} のフォルダの削除に失敗しました。" "エラー" "OK" "Error"
    exit 200
  }
}

Show-Message "アンインストール処理が正常に終了しました。" "完了"

exit

 次にメイン処理のpowershellを管理者権限で呼び出すバッチファイルを作成します。

・UNINSTALL_RUN.bat

@echo off

@REM このファイルがある階層へ移動
cd /d %~dp0

@REM 管理者権限、ポリシー無制限で実行
powershell -NoProfile -ExecutionPolicy Unrestricted ".\UNINSTALL_MAIN.ps1" -verb runas

@REM 処理が終わったら処理が終わったファイルを作成
echo end > end.txt

 最後にexeの解凍時に実行されるcmdファイルを作成します。

・UNINSTALL_CALL.cmd

@echo off

@REM ウインドウ非表示、管理者権限で実行
powershell -Command Start-Process "UNINSTALL_RUN.bat" -WindowStyle Hidden -verb runas

@REM エラー起きたら終了
if %errorlevel% neq 0 (
    goto :END
)

@REM endファイルが作成されるまでこの処理を終了させない
:LOOP
if exist end.txt (
    del /f end.txt
    goto :END
)
goto :LOOP

:END

 整理すると、アンインストーラは以下のような順番で処理が進んでいきます。
1:exe解凍後、UNINSTALL_CALL.cmdが自動実行
2:UNINSTALL_CALL.cmdからUNINSTALL_RUN.batを呼び出し
3:UNINSTALL_RUN.batからUNINSTALL_MAIN.ps1を管理者権限で実行
4:UNINSTALL_MAIN.ps1内の処理でProgram Files(x86)に「TEST_IEXPRESS」があれば削除
5:UNINSTALL_MAIN.ps1の処理終了後、UNINSTALL_RUN.batで終了したとわかるファイルを作成
6:処理終了ファイルが存在すればUNINSTALL_CALL.cmdの処理を終了

 これらのファイルを「C:\iexpress_test\UNINSTALLER」配下に以下のように配置します。

 あとはiexpressでexeとsedを作成します。

 この作成したアンインストーラを実行すると、Program Files(x86)に「TEST_IEXPRESS」フォルダがあれば削除されます。


 以上がiexpressでインストーラアンインストーラを作成する方法になります。

 exe解凍時の自動実行ファイルからメイン処理まで何度も呼び出しを繰り返すので少しややこしいですし、ここまで呼び出すファイルが必要ではないかもしれませんが、一応この方法でインストーラアンインストーラを作成できます。
 iexpressはWindowsの標準ソフトとして入っているので、開発環境の制限が厳しいところでもインストーラアンインストーラを作成できるというのを知っておけばどこかで役に立つかもしれません。


・参考資料