NSISでインストーラを作ってみる その3

#はじめに
前回に引き続き、NSISのスクリプトをサンプルを用いてレクチャーします。
その1・その2を読んでいない方は、以下から読んでください。
[NSISでインストーラを作ってみる その1](https://qiita.com/yuki_nishina/items/176d6d77911f2b54bd75)
[NSISでインストーラを作ってみる その2](https://qiita.com/yuki_nishina/items/da0fe36ce4ac36809c28)

#nsDialogs
インストーラで何かを選択し、それに応じて異なった振る舞いをするのはインストーラの一般的な機能の一つです。
NSISでは以前は[InstallOptions](http://nsis.sourceforge.net/Docs/InstallOptions/Readme.html)というライブラリが用意されていましたが、現在では非推奨とされています。
代替のライブラリとして[nsDialogs](https://nsis.sourceforge.io/Docs/nsDialogs/Readme.html)が推奨されています。
これについてはサンプルだけでは理解が難しいので少し解説を交えます。
##ユーザーに何かを選択させる
ユーザーがドキュメントをインストールする指定をした時のみ、readme.txtをインストールするようにします。

```NSIS:Install.nsi
# nsDialogs
!include nsDialogs.nsh
# LogicLib
!include LogicLib.nsh

# 日本語UI
LoadLanguageFile "${NSISDIR}\Contrib\Language files\Japanese.nlf"
# アプリケーション名
Name "サンプル アプリケーション"
# 作成されるインストーラ
OutFile "Install.exe"
# インストールされるディレクトリ
InstallDir "$PROGRAMFILES\NSISSample"
# 圧縮メソッド
SetCompressor lzma
# XPマニフェスト
XPStyle on
# ページ
Page directory
Page custom OptionPage OptionPageLeave
Page instfiles
# アンインストーラ ページ
UninstPage uninstConfirm
UninstPage instfiles
# 変数
Var Checkbox_InstallDocs
Var Dialog_Options
Var InstallDocs
Var Label_DescriptionOptions

# 初期化時コールバック
Function .onInit
  # オプション値を初期化します。
  StrCpy $InstallDocs ${BST_CHECKED}
FunctionEnd

# オプション ページ
Function OptionPage
  # nsDialogsを作成します。
  nsDialogs::Create 1018
  # 作成されたnsDialogsを変数に代入します。
  Pop $Dialog_Options

  ${If} $Dialog_Options == error
    # ダイアログの作成に失敗した場合には終了します。
    Abort
  ${EndIf}

  # ラベルを作成します。
  ${NSD_CreateLabel} 0 0 100% 12u "オプションを選択してください。"
  # ラベルを変数に代入します。
  Pop $Label_DescriptionOptions

  ${NSD_CreateCheckbox} 0 13u 100% 12u "ドキュメントをインストールする(&D)"
  Pop $Checkbox_InstallDocs

  ${If} $InstallDocs == ${BST_CHECKED}
    # チェックが入力済の場合、チェックボックスにチェックを入れます。
    ${NSD_Check} $Checkbox_InstallDocs
  ${EndIf}
  nsDialogs::Show
FunctionEnd

# オプション ページ退出コールバック
Function OptionPageLeave
  ${NSD_GetState} $Checkbox_InstallDocs $InstallDocs
FunctionEnd

# デフォルト セクション
Section
  # 出力先を指定します。
  SetOutPath "$INSTDIR"
  # インストールされるファイル
  File "C:\Sample\Sample.exe"

  ${If} $InstallDocs == ${BST_CHECKED}
    # ドキュメントをインストールする場合
    # 出力先を指定します。
    SetOutPath "$INSTDIR\docs"
    # インストールされるファイル
    File "C:\Sample\docs\readme.txt"
  ${EndIf}

  # アンインストーラを出力
  WriteUninstaller "$INSTDIR\Uninstall.exe"
  # スタート メニューにショートカットを登録
  CreateDirectory "$SMPROGRAMS\NSISSample"
  SetOutPath "$INSTDIR"
  CreateShortcut "$SMPROGRAMS\NSISSample\サンプル アプリケーション.lnk" "$INSTDIR\Sample.exe" ""
  # レジストリに登録
  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSISSample" "DisplayName" "サンプル アプリケーション"
  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSISSample" "UninstallString" '"$INSTDIR\Uninstall.exe"'
SectionEnd

# アンインストーラ
!include Uninstall.nsi
```

スクリプトの構成が変わりました。アンインストーラはそのままです。
ポイントは、"nsDialogs.nsh"というヘッダーファイルを`!include`で指定する事で、nsDialogsの機能が使えるようになるところです。

又、`!include LogicLib.nsh`では、[LogicLib](https://nsis.sourceforge.io/LogicLib)によりIf文による条件分岐等のフロー等をサポートします。
これを使用しない場合、StrCpy関数を使用した冗長なスクリプトになってしまいますが、これを直感的に読みやすく簡素化できます。
他にもfor文のような制御等、色々できるようなので凝ったインストーラには必須となるでしょう。

`XPStyle on`については公式サンプルに記述されていたので使用しています。
Windows 10では指定しないとWindows 98の頃のような立体的なUIになり、指定するとWindows 8からの立体感のないUIになります。
XP ビジュアル スタイルについては、<span>@</span>ITの以下の記事で詳しく解説されています。
[.NET TIPS WindowsアプリケーションをWindows XPスタイルの外観にするには？ - C# VB.NET VS.NET Windowsフォーム - ＠IT](http://www.atmarkit.co.jp/fdotnet/dotnettips/234winxpstyle/winxpstyle.html)

変数をいくつか使用していますが、これはnsDialogsのインスタンス(実体)、ラベル(説明文)のインスタンス、チェックボックスのインスタンス、チェックボックスの入力値を記憶します。
ラベルのインスタンス変数については必要ないと思いますが、公式サンプルがそのようにしていたので使用しています。

nsDialogsは`Page custom OptionPage OptionPageLeave`で宣言しています。
宣言の文法については[Pageについての説明](https://nsis.sourceforge.io/Docs/Chapter4.html#page)に記載されていますが、3番目の引数はページを抜けた時のコールバックで、このサンプルではチェックボックスの入力値を変数に代入しています。
これにより再表示時(Function OptionPageが再度呼び出される)に前回入力値を表示し、デフォルト セクションでもこの変数により処理を分岐します。

デフォルト セクションではチェックボックスの入力値を変数から取得し、それによりドキュメントファイルをインストールするかどうかを分岐しています。
より厳密にするとすれば、アンインストーラではドキュメントファイルがあるかどうかを判断する処理を入れた方が良いと思います。
##起動
![001.png](https://qiita-image-store.s3.amazonaws.com/0/149140/d66c74e5-faf8-d78f-b4cc-3360512e58e3.png)
インストーラを起動し、インストール フォルダの指定の後に[次へ]をクリックして進むと、"オプションを選択してください。"という説明文と"ドキュメントをインストールする"というチェックボックスがあり、デフォルトでチェックが入っています。
ここにチェックを入れた場合、インストール先の "docs\readme.txt" がインストールされ、チェックを入れなかった場合、インストールされません。
尚、"ドキュメントをインストールする(D)"の"D"ですが、これはアクセラレータキーになっており、Alt+Dキーを押す事でクリックしたのと同じ効果があります。
アクセラレータキーを設定するにはテキストに`&D`のように記述します。これはWindowsの一般的なUI開発と同様です。
尚、オプションのページから[戻る]をクリックして再度オプションのページを表示した時にチェックボックスの入力値が戻っています。
これは、戻った時にはOptionPageLeave(コールバック関数)が呼び出されないためです。
#Modern UI
Modern UIはNSISの主流となっているライブラリです。
[NSIS Modern User Interface - Documentation](https://nsis.sourceforge.io/Docs/Modern%20UI%202/Readme.html)
nsDialogsを使用したインストーラを、Modern UIで書き換えてみます。
ただ書き換えるだけだとあまり変化がないので、ページを少し追加してみます。

```NSIS:Install.nsi
# Modern UI
!include MUI2.nsh
# nsDialogs
!include nsDialogs.nsh
# LogicLib
!include LogicLib.nsh

# アプリケーション名
Name "サンプル アプリケーション"
# 作成されるインストーラ
OutFile "Install.exe"
# 圧縮メソッド
SetCompressor lzma
# インストールされるディレクトリ
InstallDir "$PROGRAMFILES\NSISSample"
# XPマニフェスト
XPStyle on
# ページ
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
Page custom OptionPage OptionPageLeave
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
# アンインストーラ ページ
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
# 日本語UI
!insertmacro MUI_LANGUAGE "Japanese"
# インターフェース 設定
!define MUI_ABORTWARNING
# 変数
Var Checkbox_InstallDocs
Var Dialog_Options
Var InstallDocs
Var Label_DescriptionOptions

# 初期化時コールバック
Function .onInit
  # オプション値を初期化します。
  StrCpy $InstallDocs ${BST_CHECKED}
FunctionEnd

# オプション ページ
Function OptionPage
  # nsDialogsを作成します。
  nsDialogs::Create 1018
  # 作成されたnsDialogsを変数に代入します。
  Pop $Dialog_Options

  ${If} $Dialog_Options == error
    # ダイアログの作成に失敗した場合には終了します。
    Abort
  ${EndIf}

  # ラベルを作成します。
  ${NSD_CreateLabel} 0 0 100% 12u "オプションを選択してください。"
  # ラベルを変数に代入します。
  Pop $Label_DescriptionOptions

  ${NSD_CreateCheckbox} 0 13u 100% 12u "ドキュメントをインストールする(&D)"
  Pop $Checkbox_InstallDocs

  ${If} $InstallDocs == ${BST_CHECKED}
    # チェックが入力済の場合、チェックボックスにチェックを入れます。
    ${NSD_Check} $Checkbox_InstallDocs
  ${EndIf}
  nsDialogs::Show
FunctionEnd

# オプション ページ退出コールバック
Function OptionPageLeave
  ${NSD_GetState} $Checkbox_InstallDocs $InstallDocs
FunctionEnd

# デフォルト セクション
Section
  # 出力先を指定します。
  SetOutPath "$INSTDIR"
  # インストールされるファイル
  File "C:\Sample\Sample.exe"

  ${If} $InstallDocs == ${BST_CHECKED}
    # ドキュメントをインストールする場合
    # 出力先を指定します。
    SetOutPath "$INSTDIR\docs"
    # インストールされるファイル
    File "C:\Sample\docs\readme.txt"
  ${EndIf}

  # アンインストーラを出力
  WriteUninstaller "$INSTDIR\Uninstall.exe"
  # スタート メニューにショートカットを登録
  CreateDirectory "$SMPROGRAMS\NSISSample"
  SetOutPath "$INSTDIR"
  CreateShortcut "$SMPROGRAMS\NSISSample\サンプル アプリケーション.lnk" "$INSTDIR\Sample.exe" ""
  # レジストリに登録
  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSISSample" "DisplayName" "サンプル アプリケーション"
  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NSISSample" "UninstallString" '"$INSTDIR\Uninstall.exe"'
SectionEnd

# アンインストーラ
!include Uninstall.nsi
```

`!insertmacro`スクリプトを使用し、ページの宣言等はマクロ化されています。
日本語UIの指定もマクロですが、これはPage宣言の後に記述する必要があるので注意してください(コンパイル時に警告が出ます)。
`!insertmacro MUI_PAGE_WELCOME`
`!insertmacro MUI_PAGE_FINISH`
`!insertmacro MUI_UNPAGE_WELCOME`
`!insertmacro MUI_UNPAGE_FINISH`
これらのマクロにより、ページを追加しています。
##インストーラ
![001.png](https://qiita-image-store.s3.amazonaws.com/0/149140/ab19919e-1b6e-6f43-90bd-5d1d5e03731c.png)
![002.png](https://qiita-image-store.s3.amazonaws.com/0/149140/b15c852b-b734-c263-105f-3d6045fe6e52.png)
![003.png](https://qiita-image-store.s3.amazonaws.com/0/149140/263ab225-a9e9-3332-b854-d94f8d8cafa4.png)
![004.png](https://qiita-image-store.s3.amazonaws.com/0/149140/a4f75827-7147-1db6-0cb7-8cbb83365bd3.png)
"ようこそ"のページと"完了しました"のページが増え、UIが少し変わりました。
##アンインストーラ
![005.png](https://qiita-image-store.s3.amazonaws.com/0/149140/474d084b-6ff4-0f42-23a5-bef1b68686d7.png)
![006.png](https://qiita-image-store.s3.amazonaws.com/0/149140/f5b8e38a-3169-0d8e-c422-2b39247d0669.png)
![007.png](https://qiita-image-store.s3.amazonaws.com/0/149140/8340e8d5-46b8-9dc1-8296-b8dd5fcd2d36.png)
![008.png](https://qiita-image-store.s3.amazonaws.com/0/149140/dd085450-c1aa-1d05-a5c2-3c6b9fb4e135.png)

#最後に
Modern UIでは従来のスクリプトの記述を簡略化できるマクロが用意されています。
又、nsDialogsは、他にもテキストボックスやコンボボックス、ラジオボタン等のインストーラで一般的なUIを使用することができます。
今後、これらを使ったサンプルもレクチャーしたいと思います。