月別アーカイブ: 2015年04月

4 posts

WiX Toolset – これまでとこれから

なんとかカスタムアクションを動かすところまで来ました。

これまで、主に参考にしたのは、WiX3.6: A Developer’s Guide to Windows Install XML   (PACKT 2012、以下Guide といいます)と、[WiX Toolset チュートリアル](日本語訳)です。

[Guide]はとてもいい本で、この本をしっかり読めば大抵のことはわかると思います。
私は、しっかり読んだとは言えませんし、それに今半分程度読んだところです。

WiXに関して、私は自分に興味のあるところだけ勉強するつもりなので、全450ページの内、後3分の1程度読めばいいかなと思っています。

残された私の興味は、開発したアプケーションが動作するために必要な、Office等のランタイムが、すでにターゲットコンピュータにインストールされているかどうか調べ、なければWebを介してインストールする仕組みを作ることです。

これは多分[BootStrap]というテクニックを使うと思うのですが、この話が[Guide]の一番最後の方に書かれているので、そこまで辛抱して勉強しなければいけません。

気分転換にまた現代史の本を読みたいと思います。
山のように積まれて待っていますので。

WiX Toolset – CustomActionの組込

ユーザにプロダクトキーの入力を求め、正当性を検査するダイアログUserRegistrationDlgを作成します。

出来上がりは次のような画面です。

コードは次の通りです。プログラミングの経験がある人は、何をしているか見当がつくと思います。ただし、InvalidPidDlgは不正入力があったときの画面です。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
 <Fragment>
  <!-- TODO: Put your code here. -->
    <UI>
      <Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
        <Control Id="NameLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="ユーザー名(&amp;U):" />
        <Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="USERNAME" Text="{80}" />
        <Control Id="OrganizationLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="会社名(&amp;O):" />
        <Control Id="OrganizationEdit" Type="Edit" X="45" Y="122" Width="220" Height="18" Property="COMPANYNAME" Text="{80}" />
        <Control Id="CDKeyLabel" Type="Text" X="45" Y="147" Width="50" Height="10" TabSkip="no">
          <Text>CD キー(&amp;K)</Text>
        </Control>
        <Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="159" Width="250" Height="16" Property="PIDKEY" Text="[PIDTemplate]" />
        <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="戻る(&amp;B)">
          <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
        </Control>
        <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="次へ(&amp;N)">
          <Publish Event="DoAction" Value="CheckingPID" Order="1">1</Publish>
          <Publish Event="SpawnWaitDialog" Value="InvalidPidDlg" Order="2"><![CDATA[PIDACCEPTED = "1"]]></Publish>
          <Publish Event="NewDialog" Value="SetupTypeDlg" Order="3"><![CDATA[PIDACCEPTED = "1"]]></Publish>
        </Control>
        <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="キャンセル">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>
        <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
        <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>あなたのユーザー情報を入力して下さい。</Text>
        </Control>
        <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
        <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>{WixUI_Font_Title}ユーザー情報</Text>
        </Control>
        <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
      </Dialog>
      <Dialog Id="InvalidPidDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
        <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24"
                 ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="Exclam.ico" />
        <Control Id="Return" Type="PushButton" X="100" Y="57" Width="56" Height="17"
                 Default="yes" Cancel="yes" Text="戻る(&amp;R)">
          <Publish Event="EndDialog" Value="Return">1</Publish>
        </Control>
        <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30" TabSkip="no">
          <Text>入力されたユーザー・キーは無効です。弊社発行のキーを入力してください。</Text>
        </Control>
      </Dialog>
    </UI>
  </Fragment>
</Wix>

ここで注目していただきたいのは、15行目の[Back]PushButtonと18行目の[Next]PushButtonです。これらの[Control]要素の中にある [Publish]要素は、例えばBackコントロールでいえば、このボタンを押せば、新しいダイアログLicenseAgreementDlgに遷移しろという意味です。

[Next]ボタンを押したら、まず前回ご説明したCheckingPIDを実行しろ、その結果[PIDACCEPTED = 1](キー正常)でなければInvalidPidDlgに遷移しろ、[PIDACCEPTED = 1]ならばSetupTypeDlg画面に遷移しろ、ということになります。

20行目、SpawnWaitDialogは条件が「偽」の場合、Value=”InvalidPidDlg”に遷移しろ、という命令です。注意してください。

さて、このダイアログ・コードをメインプログラムに組み込む方法は二つあります。

一つは本体に対してライブラリー・プロジェクトを作成する方法です。VSのWindows Installer XMLからSetup Library Projectを選択して新規のプロジェクトを作成し、メインプロジェクトからは、他のライブラリー同様参照設定して使用します。

もう一つは、単純にメインプロジェクトに、WiXファイルを新規に追加して、メインプロジェクトの一ファイルとして開発します。

コードは全く同じですから、セットアップ・プロジェクトが大規模かどうか等で選択すればいいと思います。

さて、カスタムアクションと特注のダイアログをどのようにメインコードに組み込むか。

最初はカスタムアクションの定義です。

前々回ご紹介したカスタムアクションをビルドすると、ライブラリーCheckPID.CA.dllが得られます。メイン・プログラムの[Product]要素の直下に以下の文を挿入します。

<Binary Id="MyCustomActionDLL" SourceFile=".CheckPID.CA.dll" />
<CustomAction Id="CheckingPID" BinaryKey="MyCustomActionDLL" DllEntry="CheckPID" Execute="immediate" Return="check" />

少しご説明します。まず[Binary]要素を定義します。SourceFileはセットアップ・プロジェクト開発環境にあるカスタム・アクションのパス付ファイル名を指定します。

次はCustomAction の定義です。BinaryKeyはBinary要素のIdです。DllEntryはライブラリーCheckPID.CA.dllのエントリー関数名です。CustomActionのIdは、上にご紹介したUserRegistrationDlgのNextボタンで起動される、セットアッププロジェクトで使われるCustomAction の一意名です。

次はダイアログの組み込みです。同じくメイン・プログラムのProduct要素の直下に以下の文を挿入します。

まず、ユーザ定義のUI・MyWixUI_Mondoを宣言します。

この中で、WixUI_MondoとWixUI_ErrorProgressTextへの参照を宣言。続いて自作のUserRegistrationDlgを参照。次の「LicenseAgreementDlgの[Next]ボタンが押されたら、新しいダイアログUserRegistrationDlgに遷移しろ」、と「SetupTypeDlgダイアログでBackが押されたら、UserRegistrationDlgダイアログに遷移しろ」
を付け加えます。

    <UI Id="MyWixUI_Mondo">
      <UIRef Id="WixUI_Mondo" />
      <UIRef Id="WixUI_ErrorProgressText" />
      <DialogRef Id="UserRegistrationDlg" />
      <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="2">
        LicenseAccepted = "1"
      </Publish>
      <Publish Dialog="SetupTypeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg">
        1
      </Publish>
    </UI>

そのほか、アイコン指定や、グローバル変数(WiXではプロパティといいます)を定義します。以下、カスタムアクション関連のコードを再掲載します。

この中で、PIDTemplateはプロダクトキー入力用MaskedEditのテンプレートで、ここに入力された文字列からPIDKEYの値を取得します。

一番下の[WixVariable Id=”WixUILicenseRtf”] は使用許諾書の設定、
[WixVariable Id=”WixUIBannerBmp” ]はセットアップ画面に使う画像を指定しています。

<Binary Id="Exclam.ico" SourceFile="$(var.ImageFilesPath)warning.ico" />
<Property Id="PIDTemplate"><![CDATA[12345<^^^^ ^^^^ ^^^^ ^^^^>@@@@@]]></Property>
<Property Id="PIDACCEPTED" Value="123" />
<Binary Id="MyCustomActionDLL" SourceFile=".CheckPID.CA.dll" />
<CustomAction Id="CheckingPID" BinaryKey="MyCustomActionDLL" DllEntry="CheckPID" Execute="immediate" Return="check" />
<UI Id="MyWixUI_Mondo">
  <UIRef Id="WixUI_Mondo" />
  <UIRef Id="WixUI_ErrorProgressText" />
  <DialogRef Id="UserRegistrationDlg" />
  <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="2">
   LicenseAccepted = "1"
  </Publish>
  <Publish Dialog="SetupTypeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg">
   1
  </Publish>
</UI>
<WixVariable Id="WixUILicenseRtf" Value=".使用許諾契約書.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="$(var.ImageFilesPath)myImage.bmp" />

WiX Toolset – CustomAction

インストールの途中でプロダクトキーの入力を求め、正当なインストールかどうか調べるプログラムを作成します。

ソフトメーカーがどのようなプロダクトキー作成アルゴリズムを使っているのか、日本語の情報を見つけることができませんでしたが、Code Projectのサイトにそれらしいものがあります(私自身内容を確認していません)。

今はプロダクトキーの作成方法が論点ではないので、こちらは簡単にして、WiXでCustomActionをどのように作成して、どのように組み込むかをご説明します。

CustomActionは様々な方法で作成できます。PACKT本に詳しく書かれていますが、今回はVBでDllを作成します。

アプリケーションの注文が来たら、ユーザ名を使ったプロダクトキーを作成・返送し、ユーザがソフトをインストールするときに、名前とキーを使ってインストールが正規かどうかチェックします。

入力用のダイアログ画面ではユーザ名、会社名、プロダクトキーの入力用TextBoxがあって、ユーザが入力した値が、USERNAME、COMPANYNAME、PIDKEYグローバル・プロパティにセットされます。

これをチェックする簡単なアクションプログラムを下に示します。セットアップ本体とCustomActionとは、sessionを使って簡単にデータの授受ができます。

プロダクトキーはにユーザ名を16桁のHexで表示したものです。Hexアレイの順番を入れ替えるとか、ダミーの文字を入れるとかするともう少しもっともらしくなります。

VBのプロジェクトを以下のように作成します。Visual Studio 2013で「新しいプロジェクト」をクリック、Windows Installer XMLタブから、VB Custom Action Projectをクリックします。

自動生成されたメインのファイル(CustomAction.vb)を以下のように変更し、ビルドするとCheckPID.CA.dllファイルが作成されます

Imports System.IO
Imports System.Text
Imports Microsoft.Deployment.WindowsInstaller
Public Class CustomActions2
    <CustomAction()> _
    Public Shared Function CheckPID(ByVal session As Session) As ActionResult
        Dim strName As String = session("USERNAME")
        Dim strPid As String = session("PIDKEY")
        Dim O_CheckID = New clsProductKey2("Shift_JIS")
        session("PIDACCEPTED") = O_CheckID.CheckKey(strName, strPid)
        Return ActionResult.Success
    End Function
End Class
Public Class clsProductKey2
    Private enc As Encoding
    Private Const strPpack As String = "m6bk9UoUXzgTvmwv"
    Public Sub New(ByVal encStr As String)
        enc = Encoding.GetEncoding(encStr)
    End Sub
    Public Function CheckKey(P_Name As String, P_ProductKey As String) As String
        Dim strFromName As String = MakeKey(P_Name)
        If strFromName.Equals(DelDelimiter(P_ProductKey, " ")) _
            Or strFromName.Equals(DelDelimiter(P_ProductKey, "-")) Then
            Return "1"
        Else
            Return "0"
        End If
    End Function
    Public Function DelDelimiter(p_strHyphen As String, p_strDelimiter As String) As String
        Dim strArray() As String = p_strHyphen.Split(p_strDelimiter)
        Dim strHex As String = ""
        For Each strOneChar As String In strArray
            strHex &= strOneChar
        Next
        Return strHex
    End Function
    Public Function MakeKey(p_Name As String) As String
        Dim strPackedName As String = p_Name & strPpack
        Dim nameBytes() As Byte = enc.GetBytes(strPackedName)
        Dim str As String = ""
        Dim i As Integer
        For i = 0 To nameBytes.Length - 1
            If i < 8 Then
                str &= String.Format("{0:X2}", nameBytes(i))
            Else
                Exit For
            End If
        Next
        Return str
    End Function
End Class

次回は、プロダクトキー入力ダイアログと、これらをどのようにセットアッププログラムに組み込むか、ご説明いたします。

WiX Toolset – インストール・シーケンス

インストールの途中でユーザにメッセージを出したり、プロダクトキーの入力を促し、キーの正当性を検査したりするには、独自のカスタムアクションを作成し、ユーザインターフェースをカスタマイズしなければいけません。

しかし、解説書の説明は的確ではなく、実際の動作がどうなっているのかよく分からないのですが、ともかく、解説書に書いていることと、実際にやって確認したことをご説明いたします。

参考にした解説は、WiX3.6: A Developer’s Guide to Windows Install XML   (PACKT 2012)と、[WiX Toolset チュートリアル](日本語訳)です。

Windows Installerは、インストールの途中で以下のステップ(アクション)を実行します。この他にも必要であれば、ユーザは独自にアクションを作成し、実行することができます。

FindRelatedProducts
AppSearch
LaunchConditions
ValidateProductID
CostInitialize
FileCost
CostFinalize
MigrateFeatureStates
ExecuteAction
InstallValidate
InstallInitialize
ProcessComponents
UnpublishFeatures
RemoveShortcuts
RemoveFiles
InstallFiles
CreateShortcuts
RegisterUser
RegisterProduct
PublishFeatures
PublishProduct
InstallFinalize
RemoveExistingProducts

実行の順番を変更したり、ユーザのカスタムアクションを挿入することができますが、おおむね上から下に向かって実行されます。

実はこのSequenceは機能的に二つに分けて管理されます。

インストールプログラム(msiファイル)を起動すると、Welcome画面が出たり、ライセンス確認画面が出たり、インストール先のフォルダーを指定したりしますが、「インストールの準備完了」画面まではInstallerはユーザコンピュータに何の変更も加えていません。

「インストールの準備完了」画面でユーザが[インストール]ボタンをクリックすると、
上のExecuteActionが実行され、ここで初めて、インストーラはアプリケーションソフトをユーザ・コンピュータにインストールしていきます。

ExecuteActionから前のSequenceをInstallUISequenceといい、以降をInstallExecuteSequenceといいます。

InstallUISequenceでは上のアクションの最初からExecuteActionまで実行し、InstallExecuteSequenceでもまた最初から最後まで実行します(ExecuteActionを除いて)。

解説では今一つ分からないので、練習プログラムを実行しログをとってみました。
ログからアクション部分だけを抽出したものを下に示します。

 —–InstallUISequence
INSTALL 開始
FindRelatedProducts
PrepareDlg
AppSearch
LaunchConditions
ValidateProductID
CostInitialize
FileCost
CostFinalize
MigrateFeatureStates
WelcomeDlg。Dialog created
LicenseAgreementDlg。Dialog created
UserRegistrationDlg。Dialog created
CheckingPID。
SetupTypeDlg。Dialog created
VerifyReadyDlg。Dialog created
WelcomeDlg。 Dialog created
ProgressDlg。 Dialog created

ExecuteAction 開始
—–InstallExecuteSequence
INSTALL 開始
FindRelatedProducts
LaunchConditions
ValidateProductID
CostInitialize
FileCost
CostFinalize
MigrateFeatureStates
InstallValidate
RemoveExistingProducts
InstallInitialize
ProcessComponents
GenerateScript
ProcessComponents
UnpublishFeatures
UnpublishFeatures
RemoveRegistryValues
RemoveShortcuts
RemoveFiles
RemoveFolders
CreateFolders
InstallFiles
CreateShortcuts
WriteRegistryValues
RegisterUser
RegisterProduct
PublishFeatures
PublishProduct
InstallFinalize
INSTALL。 終了

ExecuteAction。 終了
ExitDialog
INSTALL。 終了

上でUserRegistrationDlgは標準Sequenceに追加したダイアログです。この画面でプロダクトキーを入力し、[次へ]をクリックするとカスタムアクションCheckingPIDがキーの正当性を検査します。

プロダクトキー入力待ち画面では、InstallUISequenceのWelcomeDlg作成アクションまで実行されています。

プロダクトキーが正しければ、下に示す[セットアップの種類の選択]画面が表示されますが、この段階で、InstallUISequenceのCheckingPIDアクションが終了しています。CheckingPIDはInstallExecuteSequenceでも実行されていますが、実際にはどうなっているのか分かりません。

ValidateProductIDアクションは解説書では、プロダクトキーの検査をするとなっていますが、プロダクトキー入力画面が表示される前に実行されていますし、実際になにをしているのかわかりません。