四方山話/PowerShellでのWiXインストーラ作成

Last-modified: 2016-03-18 (金) 12:01:16

PowerShellスクリプトでWiXの吉里吉里製ゲーム用WiX設定ファイル(.wxs)を作成する方法をまとめたもの。詳細な手順は省いているのでこんなタイトル。

スクリプト

wix_make.ps1

wix_make.ps1は設定ファイル(.ini)からWiX設定ファイル(.wxsファイル)を出力する。

#
# WiX 3.5の設定ファイルを出力するPowerShellスクリプト
#
# 使い方:
#   .\wix_make.ps1 [設定ファイル]
#
# 備考:
# ‐PowerShellの実行セキュリティポリシーがRemoteSinged以下であること
#   ・確認には Get-ExecutionPolicy を、変更には Set-ExecutionPolicy を使用する
# ‐Program Files以外のフォルダ(例えばルート直下)には対応させていない(インストール時にフォルダを変更することは可能)
# ‐設定ファイルに関する補足:
#   ・$PRODUCT_NAMEの値は、Program Files下に作るフォルダ名や、[プログラムの追加と削除]の登録名として使用される
#   ・$RELEASE_FOLDER下の実行形式ファイル(.exe)、テキストファイル(.txt)をショートカットして登録する
#   ・$KIRIKIRI2の値はショートカットでも利用するので、ゲームの名前に改名しておくことが望ましい
#
# $DebugPreference = 'Continue'
# 呼び出す度に新しいGUIDを返す関数
function getGUID {
  $guid = [Guid]::NewGuid()
  $guid.ToString()
}
# 呼び出す度に新しいファイルIDを返す関数
function getFileID {
  'file_' + ((getGUID) -replace '-', '')
}
# 呼び出す度に新しいディレクトリIDを返す関数
function getDirID {
  'dir_' + ((getGUID) -replace '-', '')
}
# 呼び出す度に新しいショートカットIDを返す関数
function getShortID {
  'short_' + ((getGUID) -replace '-', '')
}
# Element要素を作成し、属性値を設定し、親ノードに追加する
function CreateElementCombo([xml]$xml, [string]$name, [hashtable]$hash, [Xml.XmlNode]$parent)
{
  $element = $xml.CreateElement($name)
  $enum = $hash.GetEnumerator()
  while ($enum.MoveNext()) {
    $element.SetAttribute($enum.Key, $enum.Value.ToString())
  }
  $parent.AppendChild($element) | Out-Null
  $element
}
# Comment要素を作成し、コメントを設定し、親ノードに追加する
function CreateCommentCombo([xml]$xml, [string]$message, [Xml.XmlNode]$parent)
{
  $comment = $xml.CreateComment($message)
  $parent.AppendChild($comment) | Out-Null
}
# CDATA要素を作成し、値を設定し、親ノードに追加する
function CreateCDataSectionCombo([xml]$xml, [string]$text, [Xml.XmlElement]$parent)
{
  $cdata = $xml.CreateCDataSection($text)
  $parent.AppendChild($cdata) | Out-Null
}
# サブフォルダを登録
function appendSubFolder([xml]$xml, [Xml.XmlElement]$parent, [IO.DirectoryInfo]$folder)
{
  if ($folder.Attributes -ieq 'Directory') {
    $dir_elem = CreateElementCombo $xml 'Directory' @{Id=$folder.Name.ToLower()+$sub_dir_postfix; Name=$folder.Name} $parent
    $subfolders = Get-ChildItem $folder.FullName | Where-Object { $_.Attributes -eq 'Directory' }
    foreach ($i in $subfolders) {
      appendSubFolder $xml $dir_elem $i
    }
  }
}
# サブフォルダ内のファイルを登録
function appendSubFolderFiles([xml]$xml, [Xml.XmlElement]$product, [IO.DirectoryInfo]$folder)
{
  # 子要素(DirectoryRef)を作成し、Productの子として追加
  $directory_ref = CreateElementCombo $xml 'DirectoryRef' @{Id=$folder.Name.ToLower()+$sub_dir_postfix} $product
    # 子要素(Component)を作成し、DirectoryRefの子として追加
    $file_component = CreateElementCombo $xml 'Component' @{Id=$folder.Name.ToLower()+$sub_component_postfix; Guid=(getGUID)} $directory_ref
      # コメントを作成し、Componentの子として追加
      CreateCommentCombo $xml "$($folder.Name) フォルダ下のインストール対象ファイルを登録" $file_component
      # 子要素(File)を作成し、Componentの子として追加
      $files = Get-ChildItem $folder.FullName | Where-Object { $_.Attributes -eq 'Archive' }
      foreach ($i in $files) {
        $file_elem = CreateElementCombo $xml 'File' @{Id=(getFileID); Source=$i.FullName} $file_component
      }
}
# 予め設定ファイル(コマンドラインの第1引数で指定)の変数の値を編集する
# $PRODUCT_NAME = 'Foo'                         # 製品表示名
# $PRODUCT_VERSION = '1.0.0'                    # 製品バージョン(0~255.0~255.0~65535);0.0.0は不可
# $MANUFACTURER = 'Foo'                         # 会社名、ブランド、開発者名など
# $LICENSE_FILE = 'C:\Release\Foo\license.rtf'  # ライセンスファイル(.rtf)のフルパス
# $RELEASE_FOLDER = 'C:\Release\Foo'            # インストール対象ファイル群のあるフォルダ
# $KIRIKIRI2 = 'Foo.exe'                        # 吉里吉里2の実行形式ファイル名(改名しなければkrkr.eXe)
# $WIX_FILE_PATH = 'C:\WiX\Foo\setup.wxs'       # 出力するWiX設定ファイル(.wxs)のフルパス(既存のファイルは上書き)
# $EMBED_CABINET = $false                       # インストーラにキャビネットを埋め込む場合は $true 、分離する場合は $false
# $ALL_USERS = $true    # プログラムグループ、ショートカットをAll Usersに作成する場合は $true 、そうでない場合は $false
# $PRIVILEGED = $true   # 管理者のみインストール可能にするなら $true 、そうでない場合は $false(Vistaより前のWindowsで有効)
# コマンドライン引数のチェック
if ($Args.Length -lt 1) {
  Write-Warning 'コマンドライン引数が足りません。'
  $script = Split-Path $MyInvocation.MyCommand.Definition -leaf
  Write-Output ("使用法:.\{0} 設定ファイル" -f $script)
  exit 1
}
# 設定ファイルを読み込み
$inifile = $Args[0]
Get-Content $inifile | Invoke-Expression
Write-Debug "`$PRODUCT_NAME = $PRODUCT_NAME"
Write-Debug "`$PRODUCT_VERSION = $PRODUCT_VERSION"
Write-Debug "`$MANUFACTURER = $MANUFACTURER"
Write-Debug "`$LICENSE_FILE = $LICENSE_FILE"
Write-Debug "`$RELEASE_FOLDER = $RELEASE_FOLDER"
Write-Debug "`$KIRIKIRI2 = $KIRIKIRI2"
Write-Debug "`$WIX_FILE_PATH = $WIX_FILE_PATH"
Write-Debug "`$EMBED_CABINET = $EMBED_CABINET"
Write-Debug "`$ALL_USERS = $ALL_USERS"
Write-Debug "`$PRIVILEGED = $PRIVILEGED"
if ($PRODUCT_NAME -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$PRODUCT_NAME が設定されていません。"
  exit 1
}
if ($PRODUCT_VERSION -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$PRODUCT_VERSION が設定されていません。"
  exit 1
}
if ($MANUFACTURER -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$MANUFACTURER が設定されていません。"
  exit 1
}
if ($LICENSE_FILE -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$LICENSE_FILE が設定されていません。"
  exit 1
}
if ($RELEASE_FOLDER -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$RELEASE_FOLDER が設定されていません。"
  exit 1
}
if ($KIRIKIRI2 -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$KIRIKIRI2 が設定されていません。"
  exit 1
}
if ($WIX_FILE_PATH -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$WIX_FILE_PATH が設定されていません。"
  exit 1
}
if ($EMBED_CABINET -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$EMBED_CABINET が設定されていません。"
  exit 1
}
if ($ALL_USERS -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$ALL_USERS が設定されていません。"
  exit 1
}
if ($PRIVILEGED -eq $null) {
  Write-Error "設定ファイル $inifile に変数 `$PRIVILEGED が設定されていません。"
  exit 1
}
if (-not (Test-Path $LICENSE_FILE -pathType Leaf)) {
  Write-Error "$inifile において、変数 `$LICENSE_FILE で指定されたファイル $LICENSE_FILE が存在しません。"
  exit 1
}
if (-not (Test-Path $RELEASE_FOLDER -pathType Container)) {
  Write-Error "$inifile において、変数 `$RELEASE_FOLDER で指定されたフォルダ $RELEASE_FOLDER が存在しません。"
  exit 1
}
if (-not (Test-Path (Join-Path $RELEASE_FOLDER $KIRIKIRI2) -pathType Leaf)) {
  Write-Error "$inifile において、変数 `$KIRIKIRI2 で指定されたファイル $KIRIKIRI2 がフォルダ $RELEASE_FOLDER 内に存在しません。"
  exit 1
}
if ($ALL_USERS -eq $true) {
  if ($PRIVILEGED -eq $false) {
    Write-Error "`$ALL_USERS を `$true に設定した場合、 `$PRIVILEGED も `$true に設定してください。"
    exit 1
  }
}
# 設定ファイルの拡張子を.guidに置き換えたファイル名を求める
$guid_file = [IO.Path]::ChangeExtension((Convert-Path $inifile), "guid")
Write-Debug "`$guid_file = $guid_file"
# この変数は決め打ち(変更不可)
$CODE_PAGE = '932'  # 日本語コードページ
$LANGUAGE = '1041'  # 日本語ロケールID
$AUTO_GUID = '*'    # GUID自動発行
$sub_dir_postfix = '_dir'
$sub_component_postfix = '_component'
# GUIDを獲得(Product要素のId属性、UpgradeCode属性で使用する)
if (-not (Test-Path $guid_file -pathType Leaf)) {
  Write-Output "$guid_file を新規作成 ..."
  $sw = New-Object IO.StreamWriter $guid_file, ([Text.Encoding]::UTF8)
  $sw.WriteLine('# 重要:このファイルは削除しないでください')
  $sw.WriteLine("`$PRODUCT_ID='{0}'" -f (getGUID))
  $sw.WriteLine("`$UPGRADE_ID='{0}'" -f (getGUID))
  $sw.Close()
}
if (-not (Test-Path $guid_file -pathType Leaf)) {
  Write-Error "$guid_file が作成できませんでした。"
  exit 1
}
Get-Content $guid_file | Invoke-Expression
Write-Debug "`$PRODUCT_ID = $PRODUCT_ID"
Write-Debug "`$UPGRADE_ID = $UPGRADE_ID"
# XMLドキュメントを作成
$xml = New-Object Xml.XmlDocument
# コメントを作成
CreateCommentCombo $xml "`r`n  $PRODUCT_NAME $PRODUCT_VERSION 用WiX設定ファイル`r`n" $xml
# ルート要素(Wix)を作成し、属性を追加
$wix = CreateElementCombo $xml 'Wix' @{xmlns='http://schemas.microsoft.com/wix/2006/wi'} $xml
  # コメントを作成し、Wixの子として追加
  CreateCommentCombo $xml '製品仕様の設定' $wix
  # 子要素(Product)を作成し、Wixの子として追加
  $product = CreateElementCombo $xml 'Product' @{Id=$PRODUCT_ID; Name=$PRODUCT_NAME; Version=$PRODUCT_VERSION; Manufacturer=$MANUFACTURER; Language=$LANGUAGE; Codepage=$CODE_PAGE; UpgradeCode=$UPGRADE_ID} $wix
    # コメントを作成し、Productの子として追加
    CreateCommentCombo $xml 'インストーラサマリーの設定' $product
    # 子要素(Package)を作成し、Productの子として追加
    # 以下は決め打ち:Windows Installer 2.0(Windows 95)以降、x86プラットフォーム、現在のユーザーにインストール、キーワード、圧縮
    $h = @{Id=$AUTO_GUID; Description="$PRODUCT_NAME インストーラ"; Comments="$PRODUCT_NAME インストーラデータベース"; InstallerVersion='200'; Platform='x86'; Manufacturer=$MANUFACTURER; InstallScope='perUser'; Keywords='Installer,MSI,Database'; Languages=$LANGUAGE; SummaryCodepage=$CODE_PAGE; Compressed='yes'}
    if ($ALL_USERS -eq $true) {
      $h['InstallScope'] = 'perMachine' # All Usersにインストール
    }
    $package = CreateElementCombo $xml 'Package' $h $product
    # コメントを作成、Productの子として追加
    CreateCommentCombo $xml 'Windows XP より前のバージョンの Windows ではインストール不可に設定' $product
    # 子要素(Condition)を作成し、Productの子として追加
    $condition = CreateElementCombo $xml 'Condition' @{Message='Windows XP より前のバージョンの Windows では [ProductName] はインストールできません'} $product
      # CDATAセクションを作成し、Conditionの子として追加
      CreateCDataSectionCombo $xml 'VersionNT >= 501' $condition  # Windows XP以降(Windows Server含む)
    if ($PRIVILEGED -eq $true) {
      # コメントを作成、Productの子として追加
      CreateCommentCombo $xml '管理者のみインストールできるように設定(Windows Vistaより前のWindowsで有効)' $product
      # 子要素(Condition)を作成し、Productの子として追加
      $condition = CreateElementCombo $xml 'Condition' @{Message='[ProductName] をインストールできるのは管理者のみです'} $product
        # CDATAセクションを作成し、Conditionの子として追加
        CreateCDataSectionCombo $xml 'Privileged' $condition  # 管理者のみ(Windows Vistaより前、Windows Server含む)
    }
    # 子要素(Media)を作成し、Productの子として追加
    $h = @{Id='1'; Cabinet='Product.cab'; EmbedCab='no'}  # キャビネットファイル名は Product.cab で決め打ち
    if ($EMBED_CABINET -eq $true) {
      $h['EmbedCab'] = 'yes'
    }
    $media = CreateElementCombo $xml 'Media' $h $product
    # 子要素(Directory)を作成し、Productの子として追加
    $directory0 = CreateElementCombo $xml 'Directory' @{Id='TARGETDIR'; Name='SourceDir'} $product
      # コメントを作成し、Directoryの子として追加
      CreateCommentCombo $xml 'インストール先フォルダの作成' $directory0
      $directory1A = $directory0
      if ($PRIVILEGED -eq $true) {
        # 子要素(Directory)を作成し、Directoryの子として追加
        $directory1A = CreateElementCombo $xml 'Directory' @{Id='ProgramFilesFolder'} $directory0
      }
        # 子要素(Directory)を作成し、Directoryの子として追加
        $directory2A = CreateElementCombo $xml 'Directory' @{Id='MANUFACTURERDIR'; Name=$MANUFACTURER} $directory1A
          # 子要素(Directory)を作成し、Directoryの子として追加
          $directory3A = CreateElementCombo $xml 'Directory' @{Id='INSTALLDIR'; Name=$PRODUCT_NAME} $directory2A
            # $RELEASE_FOLDER下にサブフォルダがあればそれを登録
            $subfolders = Get-ChildItem $RELEASE_FOLDER | Where-Object { $_.Attributes -eq 'Directory' }
            foreach ($i in $subfolders) {
              appendSubFolder $xml $directory3A $i
            }
      # コメントを作成し、Directoryの子として追加
      CreateCommentCombo $xml 'プログラムグループ用フォルダの作成' $directory0
      # 子要素(Directory)を作成し、Directoryの子として追加
      $directory1B = CreateElementCombo $xml 'Directory' @{Id='ProgramMenuFolder'} $directory0
        # 子要素(Directory)を作成し、Directoryの子として追加
        $directory2B = CreateElementCombo $xml 'Directory' @{Id='ApplicationProgramsFolder'; Name=$PRODUCT_NAME} $directory1B
      # コメントを作成し、Directoryの子として追加
      CreateCommentCombo $xml 'デスクトップフォルダの登録' $directory0
      # 子要素(Directory)を作成し、Directoryの子として追加
      $directory1C = CreateElementCombo $xml 'Directory' @{Id='DesktopFolder'; Name='Desktop'} $directory0
    # 子要素(DirectoryRef)を作成し、Productの子として追加
    $directory_ref0 = CreateElementCombo $xml 'DirectoryRef' @{Id='INSTALLDIR'} $product
      # 子要素(Component)を作成し、DirectoryRefの子として追加
      $file_component = CreateElementCombo $xml 'Component' @{Id='FileComponent'; Guid=(getGUID)} $directory_ref0
        # コメントを作成し、Directoryの子として追加
        CreateCommentCombo $xml 'インストール対象ファイルの登録' $file_component
        # 子要素(File)を作成し、Componentの子として追加
        # $RELEASE_FOLDER直下のファイルを登録する(サブフォルダ下のファイルは後で登録)
        # 吉里吉里2の実行形式ファイルにはKeyPath属性を追加する
        $files = Get-ChildItem $RELEASE_FOLDER | Where-Object { $_.Attributes -eq 'Archive' }
        foreach ($i in $files) {
          $h = @{Id=(getFileID); Source=$i.FullName}
          if ($i.Name -ieq $KIRIKIRI2) {
            $h['KeyPath'] = 'yes'
          }
          $file_elem = CreateElementCombo $xml 'File' $h $file_component
        }
    # サブフォルダ内のファイルを登録
    $subfolders = Get-ChildItem $RELEASE_FOLDER -recurse | Where-Object { $_.Attributes -eq 'Directory' }
    foreach ($i in $subfolders) {
      appendSubFolderFiles $xml $product $i
    }
    # 子要素(DirectoryRef)を作成し、Productの子として追加
    $directory_ref1 = CreateElementCombo $xml 'DirectoryRef' @{Id='ApplicationProgramsFolder'} $product
      # 子要素(Component)を作成し、DirectoryRefの子として追加
      $shortcut_component = CreateElementCombo $xml 'Component' @{Id='ShortcutComponent'; Guid=(getGUID)} $directory_ref1
        # コメントを作成し、Componentの子として追加
        CreateCommentCombo $xml 'ショートカットを登録' $shortcut_component
        # 子要素(Shortcut)を作成し、Componentの子として追加
        # $RELEASE_FOLDER直下にある.txtファイル、.exeファイルを登録する
        foreach ($i in $files) {
          if ($i.Extension -ieq '.exe' -or $i.Extension -ieq '.txt') {
            $f = $i.Name -ireplace '.(exe|txt)$', ''
            $shortcut = CreateElementCombo $xml 'Shortcut' @{Id=(getShortID); WorkingDirectory='INSTALLDIR'; Name=$f; Target="[INSTALLDIR]$i"} $shortcut_component
          }
        }
        # 子要素(Shortcut)を作成し、Componentの子として追加
        # デスクトップにショートカットを作成
        $f = $KIRIKIRI2 -ireplace '.exe$', ''
        $shortcut = CreateElementCombo $xml 'Shortcut' @{Id='DesktopShortcut'; Directory='DesktopFolder'; WorkingDirectory='INSTALLDIR'; Name=$f; Target="[INSTALLDIR]$KIRIKIRI2"} $shortcut_component
        # 子要素(Shortcut)を作成し、Componentの子として追加
        $shortcut = CreateElementCombo $xml 'Shortcut' @{Id='UninstallProduct'; Name="$PRODUCT_NAME のアンインストール"; Target='[System64Folder]msiexec.exe'; Arguments='/x [ProductCode]'} $shortcut_component
        # 子要素(RemoveFolder)を作成し、Componentの子として追加
        $remove_folder = CreateElementCombo $xml 'RemoveFolder' @{Id='ApplicationProgramsFolder'; On='uninstall'} $shortcut_component
        # 子要素(RegistryValue)を作成し、Componentの子として追加
        $registry_value = CreateElementCombo $xml 'RegistryValue' @{Root='HKCU'; Key="Software\$MANUFACTURER\$PRODUCT_NAME"; Name='Installed'; Type='integer'; Value='1'; KeyPath='yes'} $shortcut_component
    # 子要素(Feature)を作成し、Productの子として追加
    $feature = CreateElementCombo $xml 'Feature' @{Id='Complete'; Level='1'} $product
    # 子要素(ComponentRef)を作成し、Featureの子として追加
    $component_ref0 = CreateElementCombo $xml 'ComponentRef' @{Id='FileComponent'} $feature
    # 子要素(ComponentRef)を作成し、Featureの子として追加
    $component_ref1 = CreateElementCombo $xml 'ComponentRef' @{Id='ShortcutComponent'} $feature
    # 子要素(ComponentRef)を作成し、Featureの子として追加
    # $RELEASE_FOLDERのサブフォルダのファイルを登録する
    foreach ($i in $subfolders) {
      $component_ref2 = CreateElementCombo $xml 'ComponentRef' @{Id=$i.Name.ToLower()+$sub_component_postfix} $feature
    }
    # コメントを作成し、Productの子として追加
    CreateCommentCombo $xml 'ローカライズファイルのインクルード' $product
    # 子要素(UIRef)を作成し、Productの子として追加
    $uiref0 = CreateElementCombo $xml 'UIRef' @{Id='WixUI_ErrorProgressText'} $product
    # コメントを作成し、Productの子として追加
    CreateCommentCombo $xml 'ダイアログセットの設定(ライセンス条項の同意とインストール先の指定のみ)' $product
    # 子要素(UIRef)を作成し、Productの子として追加
    $uiref1 = CreateElementCombo $xml 'UIRef' @{Id='WixUI_InstallDir'} $product
    # コメントを作成し、Productの子として追加
    CreateCommentCombo $xml 'ライセンスファイルの設定' $product
    # 子要素(WixVariable)を作成し、Productの子として追加
    $wix_variable = CreateElementCombo $xml 'WixVariable' @{Id='WixUILicenseRtf'; Value=$LICENSE_FILE} $product
    # コメントを作成し、Productの子として追加
    CreateCommentCombo $xml 'デフォルトのインストール先フォルダの設定' $product
    # 子要素(Property)を作成し、Productの子として追加
    $property0 = CreateElementCombo $xml 'Property' @{Id='WIXUI_INSTALLDIR'; Value='INSTALLDIR'} $product
    # WindowsVolume(Windowsインストール先ドライブ)下にフォルダを作成する(デフォルトでは最も空き容量の大きいドライブを選択する)
#     if ($PRIVILEGED -eq $false) {
#       CreateCommentCombo $xml 'デフォルトで Windows インストール先ドライブ下にフォルダを作成するよう設定する' $product
#       $custom = CreateElementCombo $xml 'CustomAction' @{Id='SetDataLocationDefault'; Property='MANUFACTURERDIR'; Value="[WindowsVolume]$MANUFACTURER"} $product
#       $exec_seq = CreateElementCombo $xml 'InstallExecuteSequence' @{} $product
#         $exec_seq = CreateElementCombo $xml 'Custom' @{Action='SetDataLocationDefault'; After='CostInitialize'} $exec_seq
#       $ui_seq = CreateElementCombo $xml 'InstallUISequence' @{} $product
#         $exec_seq = CreateElementCombo $xml 'Custom' @{Action='SetDataLocationDefault'; After='CostInitialize'} $ui_seq
#     }
# .wxsファイルの保存先フォルダを作成
if (-not (Test-Path (Split-Path $WIX_FILE_PATH -parent) -pathType Container)) {
  New-Item (Split-Path $WIX_FILE_PATH -parent) -type directory
}
# XML宣言を追加し、Unicode(UTF-8)で保存
$writer = New-Object Xml.XmlTextWriter($WIX_FILE_PATH, [Text.Encoding]::UTF8)
$writer.Formatting = [Xml.Formatting]::Indented  # インデントする
$xml.Save($writer)
$writer.Close()
# コンソールに出力
Get-Content $WIX_FILE_PATH

BuildMSI.ps1

BuildMSI.ps1はインストーラをビルドする。

#
# WiX 3.5の設定ファイルをビルドするPowerShellスクリプト
#
# 使い方:
#   .\BuildMSI.ps1 .wxsファイル1 [.wxsファイル2 ...]
#
# 備考:
# ‐PowerShellの実行セキュリティポリシーがRemoteSinged以下であること
#   ・確認には Get-ExecutionPolicy を、変更には Set-ExecutionPolicy を使用する
# ‐WiXのインストール先フォルダは以下の順で検索~実行する(どちらかを適切な値に変更のこと)
#     環境変数WIXDIRの値 → 変数$WIXDIRの値(C:\wix35-binaries)
#
# $DebugPreference = 'Continue'
# コマンドライン引数のチェック
if ($Args.Length -lt 1) {
  Write-Warning 'コマンドライン引数が足りません。'
  $script = Split-Path $MyInvocation.MyCommand.Definition -leaf
  Write-Output ("使用法:.\{0} .wxsファイル1 [.wxsファイル2 ...]" -f $script)
  exit 1
}
# 環境変数WIXDIRが設定されていなければ、デフォルトの値を設定しておく
$WIXDIR = 'C:\wix35-binaries'
#$WIXDIR = Join-Path $Env:ProgramFiles 'Windows Installer XML v3.5\bin' # Wix35.msi で WiX 3.5 をインストールした場合
if ($Env:WIXDIR -ne $null) {
  $WIXDIR = $Env:WIXDIR
}
$CANDLE = (Join-Path $WIXDIR 'candle.exe') -replace ' ', '` '
$LIGHT = (Join-Path $WIXDIR 'light.exe') -replace ' ', '` '
# 出力先フォルダがなければ作成、あれば中のファイルを削除
if (Test-Path 'obj' -pathType Container) {
  Remove-Item 'obj\*.*'
} else {
  New-Item 'obj' -type directory
}
if (Test-Path 'bin' -pathType Container) {
  Remove-Item 'bin\*.*'
} else {
  New-Item 'bin' -type directory
}
# インストーラをビルド
$s = 0
$e = 0
foreach ($i in $Args) {
  Write-Output "ビルド:$i"
  $j = $i -replace ' ', '` '
  Invoke-Expression "$CANDLE $j -out obj\"
  if ($LastExitCode -ne 0) {
    $e++
    continue
  }
  $n = (Split-Path $j -leaf) -ireplace '.(wxs|xml)$', ''
  Invoke-Expression "$LIGHT -ext WixUIExtension -cultures:ja-jp obj\$n.wixobj -out bin\$n.msi"
  if ($LastExitCode -ne 0) {
    $e++
    continue
  }
  $s++
}
Write-Output "ビルドが完了しました(正常終了:$s/エラー終了:$e)"
trap [System.Management.Automation.CommandNotFoundException] {
  Write-Host "以下のいずれかの方法で対処してください:`r`n‐変数 `$WIXDIR の値を WiX 3.5 インストール先フォルダに変更する`r`n‐環境変数 WIXDIR に WiX 3.5 インストール先フォルダを設定する" -foregroundColor Magenta
  break
}
  • ファイルの出力先としてobjとbinというサブフォルダを作成し、そこに中間ファイルやインストーラ(キャビネットも)を出力する
  • WiXのインストール先フォルダは、環境変数WIXDIRで指定するか、BuildMSI.ps1の$WIXDIR = ~の行を修正して指定する

使い方

設定ファイルの編集

テキストエディタで設定ファイルを作成・編集する(シングルクォーテーション内の値を変更するだけ)。以降、この設定ファイル名はwix_make.iniという前提で説明する。

#
# wix_make.ps1用設定ファイル
#
$PRODUCT_NAME = 'Foo'        # 製品表示名
$PRODUCT_VERSION = '1.0.0'      # 製品バージョン(0~255.0~255.0~65535);0.0.0は不可
$MANUFACTURER = 'Bar'        # 会社名、ブランド、開発者名など
$LICENSE_FILE = 'C:\Release\Foo\license.rtf'  # ライセンスファイル(.rtf)のフルパス
$RELEASE_FOLDER = 'C:\Release\Foo'    # インストール対象ファイル群のあるフォルダ
$KIRIKIRI2 = 'Foo.exe'        # 吉里吉里2の実行形式ファイル名(改名しなければkrkr.eXe)
$WIX_FILE_PATH = 'C:\WiX\Foo\setup.wxs'    # 出力するWiX設定ファイルのフルパス(既存のファイルは上書き)
$EMBED_CABINET = $false  # インストーラにキャビネットを埋め込む場合は $true 、分離する場合は $false
$ALL_USERS = $false  # プログラムグループ、ショートカットをAll Usersに作成する場合は $true 、そうでない場合は $false
$PRIVILEGED = $false  # 管理者のみインストール可能にするなら $true 、そうでない場合は $false
#
# ‐←半角 # から改行までコメント
# ‐値はシングルクォーテーションで囲むこと
# ‐$PRODUCT_NAMEの値は、Program Files下に作るフォルダ名や、[プログラムの追加と削除]の登録名として使用される
# ‐$RELEASE_FOLDER下にサブフォルダがあっても良い(ただし一階層までしか対応させていない)
# ‐$KIRIKIRI2の値はショートカットでも利用するので、ゲームの名前に改名しておくことが望ましい
# ‐$ALL_USERSを$trueにする場合、$PRIVILEGEDも$trueにしておく必要がある
#

設定ファイルの値によってデフォルトの設定が以下のように変わる。

  • キャビネットファイルの有無
    • $EMBED_CABINETが$trueの場合…インストーラにキャビネットを埋め込む
    • $EMBED_CABINETが$falseの場合…キャビネットファイル(Product.cabに決め打ち)を分離*1
  • デフォルトのインストール先フォルダ
    • $PRIVILEGEDを$trueにした場合…%ProgramFiles%下にフォルダを作成する
    • $PRIVILEGEDを$falseにした場合…最も空き容量の大きいドライブのルート下にフォルダを作成する*2
  • プログラムグループやショートカットの作成先
    • $ALL_USERSを$trueにした場合…All Users以下
    • $ALL_USERSを$falseにした場合…各ユーザーごと
  • $ALL_USERSを$trueにする場合、$PRIVILEGEDも$trueにしておく必要がある

WiX設定ファイルの作成

PowerShellコンソール上で以下のように入力する。予めPowerShellの実行セキュリティポリシーをRemoteSigned以下に設定しておくこと。

.\wix_make.ps1 設定ファイル

今回の例では、次のようになる(wix_make.ps1とwix_make.iniは、どちらもカレントフォルダにあると仮定している)。

.\wix_make.ps1 wix_make.ini

正常終了時、設定ファイルと同じフォルダに.guidファイルが作られる。2回目以降のWiX設定ファイル作成時に参照するものなので、削除しないこと。このファイルに記述されているGUIDはProduct要素のId属性、UpgradeCode属性で使用する(アップデート用インストーラなどを作る際に必要な値)。

インストーラのビルド

PowerShellコンソール上で以下のように入力する。

.\BuildMSI.ps1 WiX設定ファイル

今回の例では、次のようになる。

.\BuildMSI.ps1 C:\WiX\Foo\setup.wxs

ダウンロード

動作環境

  • WindowsXP SP3
    • .NET Framework 4
  • Windows PowerShell 2.0
  • WiX 3.5.2519.0

参考


*1 CD/DVDにインストーラを焼くなら、即オートラン可能なこちらを推奨。
*2 Windowsインストール先ドライブ(%SystemDrive%)のルート下にしたい場合は、wix_make.ps1中のコメントを外せば対応できる。