Windows Installer手引書 Part.32 MSIのインストール進捗をInno Setupで表示する

[**前の記事へ**](https://qiita.com/tohshima/items/a30f3c679ab30e3cf88f)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　[**次の記事へ**](https://qiita.com/tohshima/items/f509498fbf23b9facc8b)

## Windows Installerでは外部UIを使える

ほとんどの場合Windows InstallerでUIを表示する際、すでに用意されているダイアログセットを使用すると思います。しかし、一度に複数のMSIファイルでインストールすると、画面遷移がわずらわしくなってくるので、UI表示を一括して他のプログラムに任せたくなります。そんなときは、[MsiSetExternalUI](https://docs.microsoft.com/en-us/windows/win32/msi/monitoring-an-installation-using-msisetexternalui)というMSIの関数を利用して、MSIファイルによるインストール状況を外部のプログラムに知らせることができます。

## Inno SetupでMsiSetExternalUIを使用する

Windows Installerは外部のプログラムにインストールの進捗状況を伝えるために、[MsiSetExternalUI関数](https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msisetexternaluia)の第1引数に与えた関数を呼び出します。Inno Setupで書いた関数はPascalで書かれているので、Windows APIと関数の呼び出し方式が異なり、直接Windows APIから呼び出すことができません。そのため、Inno Setupには、[CreateCallback関数](https://jrsoftware.org/ishelp/topic_isxfunc_createcallback.htm)が用意されています。この関数が返す関数のアドレスを使うと、直接Windows APIからPascalの関数を呼び出すことができます。

## Windows Installerから渡される情報の中身を確認する

まず、適当なMSIファイルを用意します。この記事では、「Part32_01.msi」と「cab1.cab」を用意しました。このMSIファイルはUIを持っていなくても問題ありません（もちろんUIを持っていても問題ありません）。このMSIファイルをインストールし、Windows installerから渡されてくる情報をInno SetupのDebugログに出力するインストーラーを実装してみました。

```Pascal:Part32_01.iss
#define MsiName "Part32_01.msi"

[Setup]
DisableReadyPage=True
DisableReadyMemo=True
AppName=Part32_01
AppVersion=1.0.0
UsePreviousPrivileges=False
CreateAppDir=False
ShowLanguageDialog=no
Uninstallable=no
OutputBaseFilename=Part32_01_Installer

[Files]
Source: "bin\Debug\{#MsiName}"; DestDir: "{tmp}"; Flags: deleteafterinstall
Source: "bin\Debug\cab1.cab"; DestDir: "{tmp}"; Flags: deleteafterinstall

[Code]
const
  INSTALLUILEVEL_NOCHANGE = 0;    // UI level is unchanged
  INSTALLUILEVEL_DEFAULT  = 1;    // default UI is used
  INSTALLUILEVEL_NONE     = 2;    // completely silent installation
  INSTALLUILEVEL_BASIC    = 3;    // simple progress and error handling
  INSTALLUILEVEL_REDUCED  = 4;    // authored UI, wizard dialogs suppressed
  INSTALLUILEVEL_FULL     = 5;    // authored UI with wizards, progress, errors
  INSTALLUILEVEL_ENDDIALOG    = $80; // display success/failure dialog at end of install
  INSTALLUILEVEL_PROGRESSONLY = $40; // display only progress dialog
  INSTALLUILEVEL_HIDECANCEL   = $20; // do not display the cancel button in basic UI
  INSTALLUILEVEL_SOURCERESONLY = $100; // force display of source resolution even if quiet

  INSTALLMESSAGE_FATALEXIT      = $00000000; // premature termination, possibly fatal OOM
  INSTALLMESSAGE_ERROR          = $01000000; // formatted error message
  INSTALLMESSAGE_WARNING        = $02000000; // formatted warning message
  INSTALLMESSAGE_USER           = $03000000; // user request message
  INSTALLMESSAGE_INFO           = $04000000; // informative message for log
  INSTALLMESSAGE_FILESINUSE     = $05000000; // list of files in use that need to be replaced
  INSTALLMESSAGE_RESOLVESOURCE  = $06000000; // request to determine a valid source location
  INSTALLMESSAGE_OUTOFDISKSPACE = $07000000; // insufficient disk space message
  INSTALLMESSAGE_ACTIONSTART    = $08000000; // start of action: action name & description
  INSTALLMESSAGE_ACTIONDATA     = $09000000; // formatted data associated with individual action item
  INSTALLMESSAGE_PROGRESS       = $0A000000; // progress gauge info: units so far, total
  INSTALLMESSAGE_COMMONDATA     = $0B000000; // product info for dialog: language Id, dialog caption
  INSTALLMESSAGE_INITIALIZE     = $0C000000; // sent prior to UI initialization, no string data
  INSTALLMESSAGE_TERMINATE      = $0D000000; // sent after UI termination, no string data
  INSTALLMESSAGE_SHOWDIALOG     = $0E000000; // sent prior to display or authored dialog or wizard

  INSTALLLOGMODE_FATALEXIT      = 1 shl (INSTALLMESSAGE_FATALEXIT      shr 24);
  INSTALLLOGMODE_ERROR          = 1 shl (INSTALLMESSAGE_ERROR          shr 24);
  INSTALLLOGMODE_WARNING        = 1 shl (INSTALLMESSAGE_WARNING        shr 24);
  INSTALLLOGMODE_USER           = 1 shl (INSTALLMESSAGE_USER           shr 24);
  INSTALLLOGMODE_INFO           = 1 shl (INSTALLMESSAGE_INFO           shr 24);
  INSTALLLOGMODE_RESOLVESOURCE  = 1 shl (INSTALLMESSAGE_RESOLVESOURCE  shr 24);
  INSTALLLOGMODE_OUTOFDISKSPACE = 1 shl (INSTALLMESSAGE_OUTOFDISKSPACE shr 24);
  INSTALLLOGMODE_ACTIONSTART    = 1 shl (INSTALLMESSAGE_ACTIONSTART    shr 24);
  INSTALLLOGMODE_ACTIONDATA     = 1 shl (INSTALLMESSAGE_ACTIONDATA     shr 24);
  INSTALLLOGMODE_COMMONDATA     = 1 shl (INSTALLMESSAGE_COMMONDATA     shr 24);
  INSTALLLOGMODE_PROPERTYDUMP   = 1 shl (INSTALLMESSAGE_PROGRESS       shr 24); // log only
  INSTALLLOGMODE_VERBOSE        = 1 shl (INSTALLMESSAGE_INITIALIZE     shr 24); // log only
  INSTALLLOGMODE_EXTRADEBUG     = 1 shl (INSTALLMESSAGE_TERMINATE      shr 24); // log only
  INSTALLLOGMODE_PROGRESS       = 1 shl (INSTALLMESSAGE_PROGRESS       shr 24); // external handler only
  INSTALLLOGMODE_INITIALIZE     = 1 shl (INSTALLMESSAGE_INITIALIZE     shr 24); // external handler only
  INSTALLLOGMODE_TERMINATE      = 1 shl (INSTALLMESSAGE_TERMINATE      shr 24); // external handler only
  INSTALLLOGMODE_SHOWDIALOG     = 1 shl (INSTALLMESSAGE_SHOWDIALOG     shr 24); // external handler only

var
  StrCounter: Integer;
  
type
  INSTALLUILEVEL = Longint;
  LPHWND = Cardinal;
  LPVOID = Cardinal;
  LPCWSTR = Cardinal;

function MsiSetInternalUI(dwUILevel: INSTALLUILEVEL; phWnd: LPHWND): INSTALLUILEVEL;
external 'MsiSetInternalUI@msi.dll stdcall';
function MsiSetExternalUIW(puiHandler: Longword; dwMessageFilter: DWORD; pvContext: String): Integer;
external 'MsiSetExternalUIW@msi.dll stdcall';
function MsiInstallProductW(szPackagePath: String; szCommandLine: String): UINT;
external 'MsiInstallProductW@msi.dll stdcall';

function TestMyBasicUIHandler(pvContext: LPVOID; iMessageType: UINT; szMessage: String): Integer;
begin
  StrCounter := StrCounter + 1;
  Log(Format('%d : %X', [StrCounter, iMessageType]));
  case ($FF000000 and iMessageType) of
    INSTALLMESSAGE_FATALEXIT:     Log(Format('%d : INSTALLMESSAGE_FATALEXIT', [StrCounter]));
    INSTALLMESSAGE_ERROR:         Log(Format('%d : INSTALLMESSAGE_ERROR', [StrCounter]));
    INSTALLMESSAGE_WARNING:       Log(Format('%d : INSTALLMESSAGE_WARNING', [StrCounter]));
    INSTALLMESSAGE_USER:          Log(Format('%d : INSTALLMESSAGE_USER', [StrCounter]));
    INSTALLMESSAGE_INFO:          Log(Format('%d : INSTALLMESSAGE_INFO', [StrCounter]));
    INSTALLMESSAGE_FILESINUSE:    Log(Format('%d : INSTALLMESSAGE_FILESINUSE', [StrCounter]));
    INSTALLMESSAGE_RESOLVESOURCE: Log(Format('%d : INSTALLMESSAGE_RESOLVESOURCE', [StrCounter]));
    INSTALLMESSAGE_OUTOFDISKSPACE:Log(Format('%d : INSTALLMESSAGE_OUTOFDISKSPACE', [StrCounter]));
    INSTALLMESSAGE_ACTIONSTART:   Log(Format('%d : INSTALLMESSAGE_ACTIONSTART', [StrCounter]));
    INSTALLMESSAGE_ACTIONDATA:    Log(Format('%d : INSTALLMESSAGE_ACTIONDATA', [StrCounter]));
    INSTALLMESSAGE_PROGRESS:      Log(Format('%d : INSTALLMESSAGE_PROGRESS', [StrCounter]));
    INSTALLMESSAGE_COMMONDATA:    Log(Format('%d : INSTALLMESSAGE_COMMONDATA', [StrCounter]));
    INSTALLMESSAGE_INITIALIZE:    Log(Format('%d : INSTALLMESSAGE_INITIALIZE', [StrCounter]));
    INSTALLMESSAGE_TERMINATE:     Log(Format('%d : INSTALLMESSAGE_TERMINATE', [StrCounter]));
    INSTALLMESSAGE_SHOWDIALOG:    Log(Format('%d : INSTALLMESSAGE_SHOWDIALOG', [StrCounter]));
  end;
  Log(Format('%d : %s', [StrCounter, szMessage]));
  result := 0;
end;

procedure CurStepChanged(CurStep: TSetupStep);
var 
  tmpPath: String;
  ResultCode: Integer;
begin
  // msi/cabファイルの展開が終わったら、msiを起動する
  if CurStep=ssPostInstall then begin
    MsiSetInternalUI(INSTALLUILEVEL_NONE or INSTALLUILEVEL_SOURCERESONLY, 0);
    MsiSetExternalUIW(CreateCallback(@TestMyBasicUIHandler), INSTALLLOGMODE_PROGRESS 
         or INSTALLLOGMODE_FATALEXIT or INSTALLLOGMODE_ERROR
         or INSTALLLOGMODE_WARNING or INSTALLLOGMODE_USER or INSTALLLOGMODE_INFO
         or INSTALLLOGMODE_RESOLVESOURCE or INSTALLLOGMODE_OUTOFDISKSPACE
         or INSTALLLOGMODE_ACTIONSTART or INSTALLLOGMODE_ACTIONDATA
         or INSTALLLOGMODE_COMMONDATA or INSTALLLOGMODE_PROGRESS
         or INSTALLLOGMODE_INITIALIZE or INSTALLLOGMODE_TERMINATE
         or INSTALLLOGMODE_SHOWDIALOG,
        'TEST'
    );
    MsiInstallProductW(ExpandConstant('{tmp}\{#MsiName}'), '');
  end;
end;

function InitializeSetup(): Boolean;
begin
  StrCounter := 0;
  result := True;
end;
```

インストールを実行して、Debugログをファイルに出力して中身を確認してください。非常に多くの情報が渡されてきており、UI表示に関係なさそうな情報も渡されてくることが分かります。MSIファイルのインストールログを見たことがあるなら、内容がほとんど一緒であることに気付くでしょう。Inno Setupでは、この中からインストールの進捗表示で使う、現在のステータス文字列や、ファイル名やレジストリ項目、プログレスバーの進捗度合い等を取り出して、UIに反映させます。

## サンプルを移植する

Windows Installerから渡されてくる情報は、そのままUIに反映することが考慮されていないので、少し加工して使用する必要があります。また、プログレスバー向けの情報を反映するのもかなり面倒な作業です。ここでは、[マイクロソフトのサンプルコード](https://docs.microsoft.com/en-us/windows/win32/msi/handling-progress-messages-using-msisetexternalui)を移植してみました。これをこのまま製品に適用できるわけではありませんが、仕組みを読み解くには役に立つでしょう。製品に適用する場合は、FileInUseメッセージやエラー、ワーニングを受けたときの動作を決めておく必要があります。ここでは表示する文字列の加工も、最低限やっているだけです。また、できるだけマイクロソフトのサンプルに近づけるため、グローバル変数を多用していますので、組織の実装ルールに当てはめて実装を変える必要があるかもしれません。

```pascal:Part32_02.iss
#define MsiName "Part32_02.msi"

[Setup]
DisableReadyPage=True
DisableReadyMemo=True
AppName=Part32_02
AppVersion=1.0.0
UsePreviousPrivileges=False
CreateAppDir=False
ShowLanguageDialog=no
Uninstallable=no
OutputBaseFilename=Part32_02_Installer

[Files]
Source: "bin\Debug\{#MsiName}"; DestDir: "{tmp}"; Flags: deleteafterinstall
Source: "bin\Debug\cab1.cab"; DestDir: "{tmp}"; Flags: deleteafterinstall

[Code]
const
  WM_USER         = $0400;
  PBM_SETRANGE    = WM_USER+1;
  PBM_SETPOS      = WM_USER+2;
  PBM_DELTAPOS    = WM_USER+3;
  PBM_SETSTEP     = WM_USER+4;
  PBM_STEPIT      = WM_USER+5;
  PBM_SETRANGE32  = WM_USER+6;  // lParam = high, wParam = low
  PBM_GETRANGE    = WM_USER+7;  // wParam = return (TRUE ? low : high). lParam = PPBRANGE or NULL
  PBM_GETPOS      = WM_USER+8;
  PBM_SETBARCOLOR = WM_USER+9;  // lParam = bar color

  INSTALLUILEVEL_NOCHANGE      = 0;    // UI level is unchanged
  INSTALLUILEVEL_DEFAULT       = 1;    // default UI is used
  INSTALLUILEVEL_NONE          = 2;    // completely silent installation
  INSTALLUILEVEL_BASIC         = 3;    // simple progress and error handling
  INSTALLUILEVEL_REDUCED       = 4;    // authored UI, wizard dialogs suppressed
  INSTALLUILEVEL_FULL          = 5;    // authored UI with wizards, progress, errors
  INSTALLUILEVEL_ENDDIALOG     = $80;  // display success/failure dialog at end of install
  INSTALLUILEVEL_PROGRESSONLY  = $40;  // display only progress dialog
  INSTALLUILEVEL_HIDECANCEL    = $20;  // do not display the cancel button in basic UI
  INSTALLUILEVEL_SOURCERESONLY = $100; // force display of source resolution even if quiet

  INSTALLMESSAGE_FATALEXIT      = $00000000; // premature termination, possibly fatal OOM
  INSTALLMESSAGE_ERROR          = $01000000; // formatted error message
  INSTALLMESSAGE_WARNING        = $02000000; // formatted warning message
  INSTALLMESSAGE_USER           = $03000000; // user request message
  INSTALLMESSAGE_INFO           = $04000000; // informative message for log
  INSTALLMESSAGE_FILESINUSE     = $05000000; // list of files in use that need to be replaced
  INSTALLMESSAGE_RESOLVESOURCE  = $06000000; // request to determine a valid source location
  INSTALLMESSAGE_OUTOFDISKSPACE = $07000000; // insufficient disk space message
  INSTALLMESSAGE_ACTIONSTART    = $08000000; // start of action: action name & description
  INSTALLMESSAGE_ACTIONDATA     = $09000000; // formatted data associated with individual action item
  INSTALLMESSAGE_PROGRESS       = $0A000000; // progress gauge info: units so far, total
  INSTALLMESSAGE_COMMONDATA     = $0B000000; // product info for dialog: language Id, dialog caption
  INSTALLMESSAGE_INITIALIZE     = $0C000000; // sent prior to UI initialization, no string data
  INSTALLMESSAGE_TERMINATE      = $0D000000; // sent after UI termination, no string data
  INSTALLMESSAGE_SHOWDIALOG     = $0E000000; // sent prior to display or authored dialog or wizard
  INSTALLMESSAGE_PERFORMANCE    = $0F000000; // log only, to log performance number like action time
  INSTALLMESSAGE_RMFILESINUSE   = $19000000; // the list of apps that the user can request Restart Manager to shut down and restart
  INSTALLMESSAGE_INSTALLSTART   = $1A000000; // sent prior to server-side install of a product
  INSTALLMESSAGE_INSTALLEND     = $1B000000; // sent after server-side install

  INSTALLLOGMODE_FATALEXIT      = 1 shl (INSTALLMESSAGE_FATALEXIT      shr 24);
  INSTALLLOGMODE_ERROR          = 1 shl (INSTALLMESSAGE_ERROR          shr 24);
  INSTALLLOGMODE_WARNING        = 1 shl (INSTALLMESSAGE_WARNING        shr 24);
  INSTALLLOGMODE_USER           = 1 shl (INSTALLMESSAGE_USER           shr 24);
  INSTALLLOGMODE_INFO           = 1 shl (INSTALLMESSAGE_INFO           shr 24);
  INSTALLLOGMODE_RESOLVESOURCE  = 1 shl (INSTALLMESSAGE_RESOLVESOURCE  shr 24);
  INSTALLLOGMODE_OUTOFDISKSPACE = 1 shl (INSTALLMESSAGE_OUTOFDISKSPACE shr 24);
  INSTALLLOGMODE_ACTIONSTART    = 1 shl (INSTALLMESSAGE_ACTIONSTART    shr 24);
  INSTALLLOGMODE_ACTIONDATA     = 1 shl (INSTALLMESSAGE_ACTIONDATA     shr 24);
  INSTALLLOGMODE_COMMONDATA     = 1 shl (INSTALLMESSAGE_COMMONDATA     shr 24);
  INSTALLLOGMODE_PROPERTYDUMP   = 1 shl (INSTALLMESSAGE_PROGRESS       shr 24); // log only
  INSTALLLOGMODE_VERBOSE        = 1 shl (INSTALLMESSAGE_INITIALIZE     shr 24); // log only
  INSTALLLOGMODE_EXTRADEBUG     = 1 shl (INSTALLMESSAGE_TERMINATE      shr 24); // log only
  INSTALLLOGMODE_PROGRESS       = 1 shl (INSTALLMESSAGE_PROGRESS       shr 24); // external handler only
  INSTALLLOGMODE_INITIALIZE     = 1 shl (INSTALLMESSAGE_INITIALIZE     shr 24); // external handler only
  INSTALLLOGMODE_TERMINATE      = 1 shl (INSTALLMESSAGE_TERMINATE      shr 24); // external handler only
  INSTALLLOGMODE_SHOWDIALOG     = 1 shl (INSTALLMESSAGE_SHOWDIALOG     shr 24); // external handler only

  LANG_NEUTRAL = $00;

type
  INSTALLUILEVEL = Longint;
  INSTALLMESSAGE = Longint;
  LPHWND = Cardinal;
  LPVOID = Cardinal;
  LPCWSTR = Cardinal;

var
  StrCounter: Integer;
  bFirstTime: Boolean;
  g_bEnableActionData: Boolean;     // INSTALLOGMODE_ACTIONDATAメッセージが進捗情報を送るならTrue
  iField: array [0..3] of Integer;  // INSTALLOGMODE_PROGRESSデータハンドルへのレコードの配列
  g_bForwardProgress: Boolean;      // プログレスバーを正方向に進めるならTRUE
  g_iProgressTotal: Integer;        // プログレスバーの合計コマ数
  g_iProgress: Integer;             // 進捗の総数
  g_bScriptInProgress: Boolean;
  iCurPos: Integer;
  g_wLANGID: Word;                  // neutral languageに初期化
  g_rgiField: array [0..2] of Integer; // INSTALLOGMODE_COMMONDATAデータハンドルの配列
  progressStep: Integer;

// 外部DLLの関数宣言
function MsiSetInternalUI(dwUILevel: INSTALLUILEVEL; phWnd: LPHWND): INSTALLUILEVEL;
external 'MsiSetInternalUI@msi.dll stdcall';
function MsiSetExternalUIW(puiHandler: Longword; dwMessageFilter: DWORD; pvContext: String): Integer;
external 'MsiSetExternalUIW@msi.dll stdcall';
function MsiInstallProductW(szPackagePath: String; szCommandLine: String): UINT;
external 'MsiInstallProductW@msi.dll stdcall';

// プロトタイプ宣言
function ParseCommonDataString(sz: String): Boolean; forward;
function ParseProgressString(sz: String): Boolean; forward;

// ------------------------------------------------------------------
//  TestMyBasicUIHandler()
//  MSIで外部ユーザー・インターフェース・ハンドラを扱うデモ
function TestMyBasicUIHandler(pvContext: LPVOID; iMessageType: UINT; szMessage: String): Integer;
var
  r1: INSTALLUILEVEL;
  mt: INSTALLMESSAGE;
  uiFlags: UINT;
  wParam: Int64;  // WPARAM type
  nCmdShow: Integer;
begin
  result := 0;
  if bFirstTime then begin
    r1 := MsiSetInternalUI(INSTALLUILEVEL_BASIC, 0);
    bFirstTime := False;
  end;
  mt := $FF000000 and iMessageType;
  uiFlags := $00FFFFFF and iMessageType;
  case (mt) of
    INSTALLMESSAGE_FATALEXIT: begin
      // ここでfatal errorメッセージを受け取り表示する
      result := MsgBox(szMessage, mbError, uiFlags);
    end;
    INSTALLMESSAGE_ERROR: begin
      // ここでエラーメッセージを受け取り表示する。
      // 言語とタイトルはcommon dataメッセージから得られる
      result := MsgBox(szMessage, mbError, uiFlags);
    end;
    INSTALLMESSAGE_WARNING: begin
      // ここでwarningメッセージを受け取り表示する
      result := MsgBox(szMessage, mbError, uiFlags);
    end;
    INSTALLMESSAGE_USER: begin
      // ここでuserメッセージを受け取る
      // メッセージボックススタイルのフラグを得るためにuiFlagsをパースし、IDOK, IDYES等を返す
      result := IDOK;
    end;
    INSTALLMESSAGE_INFO: begin
      // Actionの実行状況などが返される、一般的にインストーラーのUIには表示しない
      result := IDOK;
    end;
    INSTALLMESSAGE_FILESINUSE: begin
      // FilesInUseダイアログを表示する
      // ユーザーにファイルを使っているアプリケーション名を提示するには、メッセージテキストをパースする
      result := 0;
    end;
    INSTALLMESSAGE_RESOLVESOURCE: begin
      // ResolveSourceのために常に0を返す
      result := 0;
    end;
    INSTALLMESSAGE_OUTOFDISKSPACE: begin
      // ここでユーザーメッセージを得る
      result := IDOK;
    end;
    INSTALLMESSAGE_ACTIONSTART: begin
      // 新しくアクションが開始された、action dataもこのアクションによって送られる
      WizardForm.StatusLabel.Caption := Copy(szMessage, 18, Length(szMessage)-17);
      WizardForm.FilenameLabel.Caption := '';
      g_bEnableActionData := False;
      result := IDOK;
    end;
    INSTALLMESSAGE_ACTIONDATA: begin
      // ファイル、レジストリ等の書き込みを順次知らせる
      // 進捗の合計が初期化されたら実行する
      if 0 <> g_iProgressTotal then begin
        // アクションテキストを表示する
        WizardForm.FilenameLabel.Caption := szMessage;
        if g_bEnableActionData then begin
          // progressStepに設定しただけ進める
          WizardForm.ProgressGauge.Position := WizardForm.ProgressGauge.Position + progressStep;
        end;
      end;
      result := IDOK;
    end;
    INSTALLMESSAGE_PROGRESS: begin
      // プログレスバーの進捗を管理する
      if ParseProgressString(szMessage) then begin
        case (iField[0]) of
          0: begin  // プログレスバーをリセットする
            // field 1 = 0, field 2 = コマ数の合計,  field 3 = 方向, field 4 = 進行中か
            // プログレスバーの合計コマ数を取得する
            g_iProgressTotal := iField[1];
            // 方向を決める
            if iField[2] = 0 then begin
              g_bForwardProgress := True;
            end else begin  // iField[2] == 1
              g_bForwardProgress := False;
            end;
            // 方向に応じたプログレスバーの現在位置を取得する
            // 前進なら現在位置は0
            // 後進なら現在位置は合計コマ数
            if g_bForwardProgress then begin
              g_iProgress := 0;
            end else begin
              g_iProgress := g_iProgressTotal;
            end;
            WizardForm.ProgressGauge.Max := g_iProgressTotal;
            // もしg_bScriptInProgressが真ならプログレスバーは終わり、そうでなければリセット（そして進行方向に従ってセットアップする）
            if g_bScriptInProgress then begin
              WizardForm.ProgressGauge.Position := g_iProgressTotal;
            end else begin
              WizardForm.ProgressGauge.Position := g_iProgress;
            end;
            iCurPos := 0;
            // 新しい値を決定する
            // もしnew state = 1 (script in progress)なら、"Please wait..."メッセージを送る
            // new state = 1は総コマ数が推定であり、正確でないことを示す
            if iField[3] = 1 then begin
              g_bScriptInProgress := True;
            end else begin
              g_bScriptInProgress := False;
            end;
          end;
          1: begin  // ActionInfo
            // field 1 = 1, field 2はバーを進めるコマ数を得られる
            // field 3がゼロなら無視する
            if iField[2]<>0 then begin
                // g_bForwardProgressによって進捗方向が決まり、reset progress msgでセットされる
                if g_bForwardProgress then begin
                  wParam := iField[1];
                end else begin
                  wParam := -1 * iField[1];
                end;
                progressStep := wParam;
                g_bEnableActionData := True;
            end else begin
                g_bEnableActionData := False;
            end;
          end;
          2: begin  //ProgressReport
            // プログレスバーの合計コマ数が初期化されたら実行する
            if 0 <> g_iProgressTotal then begin
              iCurPos := iCurPos + iField[1];
              //field 1 = 2,field 2は動いたバーのコマ数を持っていることを示す
              // reset progressメッセージでセットされたg_bForwardProgressによって移動方向が決まる
              if g_bForwardProgress then begin
                wParam := iCurPos;
              end else begin
                wParam := -1 * iCurPos;
              end;
              WizardForm.ProgressGauge.Position := wParam;
            end;
          end;
          3: begin  // ProgressAddition - そのまま抜ける (これを使って何かする必要はない -- 総コマ数の調整のためにある)
            result := IDOK;
          end;
        end;
      end;
    end;
    INSTALLMESSAGE_COMMONDATA: begin
      // キャンセルボタンの表示制御等
      if ParseCommonDataString(szMessage) then begin
        case (g_rgiField[0]) of
          0: begin
            // field 1 = 0, field 2 = LANGID, field 3 = CodePage
            g_wLANGID := g_rgiField[1];
          end;
          1: begin
            // field 1 = 1, field 2 = CAPTION
            // これはMessageBoxesのキャプションとして使うことができる
          end;
          2: begin
            // field 1 = 2, field 2 = 0 (cancelボタンを隠す) OR 1 (cancelボタンを表示する)
            if g_rgiField[1] = 0 then begin
              WizardForm.CancelButton.Hide;
            end else begin
              WizardForm.CancelButton.Show;
            end;
          end;
        end;
      end;
      result := IDOK;
    end;
    INSTALLMESSAGE_INITIALIZE: begin
      // 一番最初に1回だけ来るメッセージ
      // このメッセージは内部のUI初期化の前に受け取る、文字列データは含まない
      result := IDOK;
    end;
    INSTALLMESSAGE_TERMINATE: begin
      // 一番最後に１回だけ来るメッセージ
      // UI停止後に送られる、文字列データは含まない
      result := IDOK;
    end;
    INSTALLMESSAGE_SHOWDIALOG: begin
      // ダイアログやウィザードが表示される前に送られる
      result := IDOK;
    end;
    else begin
      result := 0;
    end;
  end;
end;

function ParseCommonDataString(sz: String): Boolean;
var
  StrLength, i: Integer;
begin
  StrLength := Length(sz);
  g_rgiField[0] := StrToInt(sz[4]);
  g_rgiField[1] := StrToInt(Copy(sz, 9, StrLength-8));
  Result := True;
end;

function ParseProgressString(sz: String): Boolean;
var
  i, segCounter: Integer;
  segIndex: array[1..8] of Integer;
begin
  segCounter := 1;
  segIndex[segCounter] := 1;
  for i:=1 to Length(sz) do begin
    if sz[i]=' ' then begin
      segCounter := segCounter + 1;
      segIndex[segCounter] := i + 1;
    end;
    if segCounter=8 then break;
  end;
  iField[0] := StrToInt(Copy(sz, segIndex[2], segIndex[3]-segIndex[2]-1));
  iField[1] := StrToInt(Copy(sz, segIndex[4], segIndex[5]-segIndex[4]-1));
  iField[2] := StrToInt(Copy(sz, segIndex[6], segIndex[7]-segIndex[6]-1));
  iField[3] := StrToInt(Copy(sz, segIndex[8], Length(sz)-segIndex[8]+1));
  Result := True;
end;

// ------------------------------------------------------------------
// 一番最初に呼ばれるイベント
function InitializeSetup(): Boolean;
begin
  StrCounter := 0;  // for Test
  g_wLANGID := LANG_NEUTRAL;
  g_iProgressTotal := 0;
  g_iProgress := 0;
  iCurPos := 0;
  bFirstTime := True;
  g_bForwardProgress := True;
  g_bScriptInProgress := False;
  progressStep := 1;
  Result := True;
end;

// ------------------------------------------------------------------
//
procedure CurStepChanged(CurStep: TSetupStep);
var 
  tmpPath: String;
  ResultCode: Integer;
begin
  // msi/cabファイルの展開が終わったら、msiを起動する
  if CurStep=ssPostInstall then begin
    MsiSetInternalUI(INSTALLUILEVEL_NONE or INSTALLUILEVEL_SOURCERESONLY, 0);
    MsiSetExternalUIW(CreateCallback(@TestMyBasicUIHandler), INSTALLLOGMODE_PROGRESS 
         or INSTALLLOGMODE_FATALEXIT or INSTALLLOGMODE_ERROR
         or INSTALLLOGMODE_WARNING or INSTALLLOGMODE_USER or INSTALLLOGMODE_INFO
         or INSTALLLOGMODE_RESOLVESOURCE or INSTALLLOGMODE_OUTOFDISKSPACE
         or INSTALLLOGMODE_ACTIONSTART or INSTALLLOGMODE_ACTIONDATA
         or INSTALLLOGMODE_COMMONDATA or INSTALLLOGMODE_PROGRESS
         or INSTALLLOGMODE_INITIALIZE or INSTALLLOGMODE_TERMINATE
         or INSTALLLOGMODE_SHOWDIALOG,
        'TEST'
    );
    MsiInstallProductW(ExpandConstant('{tmp}\{#MsiName}'), '');
  end;
end;
```

[**前の記事へ**](https://qiita.com/tohshima/items/a30f3c679ab30e3cf88f)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　[**次の記事へ**](https://qiita.com/tohshima/items/f509498fbf23b9facc8b)