EternalWindows
MSI インストール編 / パッチファイル

アプリケーションにパッチを割り当てるという言葉がありますが、 これはインストール済みの製品に対して、何らかの修正や機能追加を行うことを意味しています。 前節で紹介したアップグレードは正確にはMajor Upgradesと呼ばれ、 これはあくまでMSIファイルによる製品の書き換えでした。 修正や機能追加はパッチファイルとも呼ばれるMSPファイルで可能で、 次に示すプロパティが旧バージョンのMSIファイルと異なっているかによって、 その目的が変わってきます。

種類 PackageCode ProductVersion ProductCode 使用するファイル
Small Update 変更する 変更しない 変更しない MSPファイル
Minor Upgrade 変更する 変更する 変更しない MSPファイル
Major Upgrades 変更する 変更する 変更する MSIファイル

上記の表から分かるようには、Small UpdateとMinor UpgradeについてはMSPファイルを使用し、 Major UpgradesについてはMSIファイルを使用することになります。 また、いずれの種類に関してもPackageCodeは変更しておかなければなりません。 PackageCodeは、MSIファイルやMSPファイルのようなファイルを一意に識別します。 Small Updateとは、いわば修正を適応するだけで追加機能がなく、 そのためProductVersionも変更するべきではありません。 一方、追加機能を考えている場合は、それがMinor Upgradeと分かるように、 ProductVersionを変更しておくようにします。 製品に割り当てられたパッチは、MsiEnumPatchesで列挙することができます。

UINT MsiEnumPatches(
  LPCTSTR szProduct,
  DWORD iPatchIndex,
  LPTSTR lpPatchBuf,
  LPTSTR lpTransformsBuf,
  DWORD *pcchTransformsBuf
);

szProductは、パッチを列挙したい製品の製品コードを指定します。 iPatchIndexは、パッチのインデックスをゼロベースで指定します。 lpPatchBufは、パッチのGUIDが返ります。 これは、パッチのPackageCodeです。 lpTransformsBufは、パッチに対してのトランスフォームのリストのようですが、 詳しい使い方は分かりません(NULLを指定することはできません)。 pcchTransformsBufは、lpTransformsBufのバッファのサイズを指定します。

今回のプログラムは、左側のリストボックスに製品の製品コードを列挙し、 選択された製品に割り当てられているパッチを右側のリストボックスに列挙します。

#include <windows.h>
#include <msi.h>

#pragma comment (lib, "msi.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBoxLeft = NULL;
	static HWND hwndListBoxRight = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		UINT  uResult;
		DWORD i;
		TCHAR szProductCode[256];
		
		hwndListBoxLeft = CreateWindowEx(WS_EX_DLGMODALFRAME, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(WS_EX_DLGMODALFRAME, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		for (i = 0;; i++) {
			uResult = MsiEnumProducts(i, szProductCode);
			if (uResult != ERROR_SUCCESS)
				break;
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szProductCode);
		}

		return 0;
	}

	case WM_COMMAND: {
		int   i;
		int   nIndex;
		DWORD dwSize;
		UINT  uResult;
		TCHAR szPatchBuf[256];
		TCHAR szTransformsBuf[256];
		TCHAR szProductCode[256];

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
		SendMessage(hwndListBoxLeft, LB_GETTEXT, nIndex, (LPARAM)szProductCode);

		for (i = 0;; i++) {
			dwSize = sizeof(szTransformsBuf);
			uResult = MsiEnumPatches(szProductCode, i, szPatchBuf, szTransformsBuf, &dwSize);
			if (uResult != ERROR_SUCCESS)
				break;
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szPatchBuf);
		}

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

基本的には、製品に割り当てられているパッチを列挙する意味はそれほどありません。 ただし、MsiApplyPatchという関数を呼び出してプログラムからパッチの割り当てを行うような場合は、 既にパッチが割り当てられているかどうかを調べる目的としてMsiEnumPatchesは有用となります。 つまり、列挙したパッチのGUIDが、 これから割り当てようとするパッチのPackageCodeと一致するかを調べるコードを記述するのです。

MSPファイルの作成

MSPファイルを作成するには、Patch Creation Fileという.pcpファイルが必要となります。 PCPファイルには、パッチが割り当てられる前のMSIファイルの情報と、 パッチが割り当てられたものと想定して作成されたMSIファイルの情報が含まれ、 両者のファイルの差分からMSPファイルが作成されることになっています。 PCPファイルはMSIファイルと同様にデータベースの形式となっており、 Platform SDKにはTemplate.pcpというPCPファイルのテンプレートが用意されているので、 基本的にはこのファイルをOrca等で開き、PCP専用のテーブルを追加することになります。 また、Example.pcpというPCPファイルの作成例を示したファイルも用意されているので、 どのようなテーブルが必要になるのかを参考にすることもできます。 適切なPCPファイルが完成したら、アプリケーションはPatchwiz.dllがエクスポートする UiCreatePatchPackageを呼び出してPCPファイルからMSPファイルを作成します。 Patchwiz.dllはPlatform SDKに含まれており、MSPファイルを作成できるmsimsp.exeも このDLLを使用するように設計されています。



戻る