Files
raven/testdata/native_loader/msi_patch_client.c
T
mirkobrombin 0392e067f0 feat: complete the Windows Installer engine
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: patches, searches, feature and source list calls, administrative images and their tests
2026-11-02 12:42:18 +00:00

439 lines
17 KiB
C

#define UNICODE
#define WIN32_LEAN_AND_MEAN
#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <msi.h>
#include <msiquery.h>
/* What maintenance asks of the installer. A patch is built here from two versions of a package, applied to the installed
product, listed and taken away; a package's searches read the machine; features answer in a session; the source list reads
back; an administrative image lays the files out and needs no cabinet to install from. */
static const WCHAR product[] = L"{7B1E3C52-9A4D-4F60-8C2E-1D5A6B7C8D90}";
static const WCHAR patch_code[] = L"{5C7A9D10-3B4E-4A21-9F86-2E1D0C9B8A70}";
static const WCHAR payload_component[] = L"{0C8A1E44-6D2B-4F7A-8B3E-5A9C1D2E3F40}";
static const char patched_text[] = "Replaced by a patch.\r\n";
static void join(WCHAR *out, const WCHAR *first, const WCHAR *second)
{
lstrcpyW(out, first);
lstrcatW(out, second);
}
static void beside(WCHAR *target, const WCHAR *name)
{
unsigned cut = GetModuleFileNameW(NULL, target, MAX_PATH);
while (cut && target[cut - 1] != L'\\')
--cut;
lstrcpyW(target + cut, name);
}
static BOOL run_sql(MSIHANDLE database, const WCHAR *query)
{
MSIHANDLE view = 0;
if (MsiDatabaseOpenViewW(database, query, &view))
return FALSE;
UINT status = MsiViewExecute(view, 0);
MsiCloseHandle(view);
return status == ERROR_SUCCESS;
}
// The registry row of the second version, on the component the package already has.
static BOOL add_registry_row(MSIHANDLE database)
{
MSIHANDLE view = 0, row = 0;
WCHAR component[80], query[400];
DWORD size = ARRAYSIZE(component);
if (MsiDatabaseOpenViewW(database, L"SELECT `Component_` FROM `Registry`", &view) || MsiViewExecute(view, 0) || MsiViewFetch(view, &row) ||
MsiRecordGetStringW(row, 1, component, &size))
return FALSE;
MsiCloseHandle(row);
MsiCloseHandle(view);
lstrcpyW(query, L"INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Name`, `Value`, `Component_`) VALUES ('Patched', 2, 'Software\\RavenSample', 'Patched', '#7', '");
lstrcatW(query, component);
lstrcatW(query, L"')");
return run_sql(database, query);
}
static BYTE *put_u32(BYTE *at, DWORD value)
{
for (int i = 0; i < 4; i++)
*at++ = (BYTE)(value >> (i * 8));
return at;
}
static BYTE *put_u16(BYTE *at, WORD value)
{
*at++ = (BYTE)value;
*at++ = (BYTE)(value >> 8);
return at;
}
// One stored file, by the published cabinet layout: header, folder, file entry and a single data block.
static DWORD build_cabinet(BYTE *out)
{
static const char member[] = "Hello";
const DWORD length = sizeof patched_text - 1, entries = 36 + 8, data = entries + 16 + sizeof member;
BYTE *at = put_u32(out, 0x4643534d);
at = put_u32(at, 0);
at = put_u32(at, data + 8 + length);
at = put_u32(at, 0);
at = put_u32(at, entries);
at = put_u32(at, 0);
*at++ = 3;
*at++ = 1;
at = put_u16(at, 1);
at = put_u16(at, 1);
at = put_u16(at, 0);
at = put_u16(at, 0);
at = put_u16(at, 0);
at = put_u32(at, data);
at = put_u16(at, 1);
at = put_u16(at, 0);
at = put_u32(at, length);
at = put_u32(at, 0);
at = put_u16(at, 0);
at = put_u16(at, 0x5821);
at = put_u16(at, 0);
at = put_u16(at, FILE_ATTRIBUTE_NORMAL);
for (DWORD i = 0; i < sizeof member; i++)
*at++ = member[i];
at = put_u32(at, 0);
at = put_u16(at, (WORD)length);
at = put_u16(at, (WORD)length);
for (DWORD i = 0; i < length; i++)
*at++ = patched_text[i];
return (DWORD)(at - out);
}
// The file the installed product keeps its payload in holds what a cabinet of the patch said, or does not.
static int payload_patched(void)
{
WCHAR path[MAX_PATH];
DWORD size = ARRAYSIZE(path), got = 0;
if (MsiGetComponentPathW(product, payload_component, path, &size) != INSTALLSTATE_LOCAL)
return -1;
HANDLE file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (file == INVALID_HANDLE_VALUE)
return -1;
char text[64];
BOOL read = ReadFile(file, text, sizeof text, &got, NULL);
CloseHandle(file);
if (!read)
return -1;
if (got != sizeof patched_text - 1)
return 0;
for (DWORD i = 0; i < got; i++)
if (text[i] != patched_text[i])
return 0;
return 1;
}
// A patch is a compound file: a summary naming its product and transforms, and each transform a storage inside.
static int build_patch(const WCHAR *path, const WCHAR *transform)
{
IStorage *root = NULL, *from = NULL, *inside = NULL;
if (FAILED(StgCreateDocfile(path, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &root)))
return 1;
if (FAILED(StgOpenStorage(transform, NULL, STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &from)))
return 2;
if (FAILED(IStorage_CreateStorage(root, L"Upgrade", STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &inside)))
return 3;
if (FAILED(IStorage_CopyTo(from, 0, NULL, NULL, inside)))
return 4;
IStorage_Release(inside);
IStorage_Release(from);
static BYTE cabinet[256];
DWORD cabinet_size = build_cabinet(cabinet);
IStream *stream = NULL;
if (FAILED(IStorage_CreateStream(root, L"patchcab", STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &stream)))
return 8;
if (FAILED(IStream_Write(stream, cabinet, cabinet_size, NULL)))
return 9;
IStream_Release(stream);
IStorage_Commit(root, 0);
IStorage_Release(root);
MSIHANDLE summary = 0;
FILETIME none = { 0, 0 };
if (MsiGetSummaryInformationW(0, path, 4, &summary))
return 5;
if (MsiSummaryInfoSetPropertyW(summary, 2, VT_LPSTR, 0, &none, L"Sample patch") ||
MsiSummaryInfoSetPropertyW(summary, 7, VT_LPSTR, 0, &none, product) ||
MsiSummaryInfoSetPropertyW(summary, 8, VT_LPSTR, 0, &none, L":Upgrade") ||
MsiSummaryInfoSetPropertyW(summary, 9, VT_LPSTR, 0, &none, patch_code))
return 6;
if (MsiSummaryInfoPersist(summary))
return 7;
MsiCloseHandle(summary);
return 0;
}
static DWORD read_patched(void)
{
HKEY key;
DWORD value = 0, size = sizeof value;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\RavenSample", 0, KEY_READ, &key))
return 0;
RegQueryValueExW(key, L"Patched", NULL, NULL, (BYTE *)&value, &size);
RegCloseKey(key);
return value;
}
static int check_patching(const WCHAR *package, const WCHAR *scratch)
{
WCHAR second[MAX_PATH], transform[MAX_PATH], patch[MAX_PATH];
join(second, scratch, L"second.msi");
join(transform, scratch, L"second.mst");
join(patch, scratch, L"sample.msp");
if (!CopyFileW(package, second, FALSE))
return 1;
SetFileAttributesW(second, FILE_ATTRIBUTE_NORMAL);
MSIHANDLE base = 0, changed = 0;
if (MsiOpenDatabaseW(package, MSIDBOPEN_READONLY, &base) || MsiOpenDatabaseW(second, MSIDBOPEN_TRANSACT, &changed))
return 2;
if (!add_registry_row(changed) || !run_sql(changed, L"INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES (2, 100, '#patchcab')") ||
!run_sql(changed, L"UPDATE `File` SET `Sequence` = 100 WHERE `File` = 'Hello'") || MsiDatabaseCommit(changed))
return 3;
if (MsiDatabaseGenerateTransformW(changed, base, transform, 0, 0))
return 4;
MsiCloseHandle(changed);
MsiCloseHandle(base);
int built = build_patch(patch, transform);
if (built)
return 10 + built;
if (MsiApplyPatchW(patch, NULL, INSTALLTYPE_DEFAULT, L"") != ERROR_PATCH_TARGET_NOT_FOUND)
return 20;
if (MsiInstallProductW(package, L"") != ERROR_SUCCESS)
return 21;
// A value no removal could take away if an earlier run stopped halfway.
HKEY stale;
if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\RavenSample", 0, KEY_SET_VALUE, &stale)) {
RegDeleteValueW(stale, L"Patched");
RegCloseKey(stale);
}
if (read_patched())
return 22;
if (MsiApplyPatchW(patch, NULL, INSTALLTYPE_DEFAULT, L"") != ERROR_SUCCESS)
return 23;
if (read_patched() != 7)
return 24;
if (payload_patched() != 1)
return 41;
WCHAR guid[39], transforms[256], text[MAX_PATH];
DWORD size = ARRAYSIZE(transforms);
if (MsiEnumPatchesW(product, 0, guid, transforms, &size) != ERROR_SUCCESS || lstrcmpiW(guid, patch_code) || lstrcmpW(transforms, L":Upgrade"))
return 25;
size = ARRAYSIZE(transforms);
if (MsiEnumPatchesW(product, 1, guid, transforms, &size) != ERROR_NO_MORE_ITEMS)
return 26;
size = ARRAYSIZE(text);
if (MsiGetPatchInfoExW(patch_code, product, NULL, MSIINSTALLCONTEXT_MACHINE, L"State", text, &size) || lstrcmpW(text, L"1"))
return 27;
size = ARRAYSIZE(text);
if (MsiGetPatchInfoW(patch_code, L"LocalPackage", text, &size) || GetFileAttributesW(text) == INVALID_FILE_ATTRIBUTES)
return 28;
WCHAR cached[MAX_PATH];
lstrcpyW(cached, text);
size = ARRAYSIZE(text);
if (MsiGetPatchInfoExW(patch_code, product, NULL, MSIINSTALLCONTEXT_MACHINE, L"DisplayName", text, &size) || lstrcmpW(text, L"Sample patch"))
return 29;
// Later maintenance runs the kept patch again: a repair keeps the registry row only the patch knows.
HKEY key;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\RavenSample", 0, KEY_SET_VALUE, &key))
return 30;
RegDeleteValueW(key, L"Patched");
RegCloseKey(key);
if (MsiReinstallProductW(product, REINSTALLMODE_MACHINEDATA | REINSTALLMODE_FILEOLDERVERSION) != ERROR_SUCCESS)
return 31;
if (read_patched() != 7)
return 32;
// The source list reads back what publishing wrote.
size = ARRAYSIZE(text);
if (MsiSourceListGetInfoW(product, NULL, MSIINSTALLCONTEXT_MACHINE, MSICODE_PRODUCT, L"PackageName", text, &size) || lstrcmpiW(text, L"basic.msi"))
return 33;
size = ARRAYSIZE(text);
if (MsiSourceListEnumSourcesW(product, NULL, MSIINSTALLCONTEXT_MACHINE, MSICODE_PRODUCT | MSISOURCETYPE_NETWORK, 0, text, &size))
return 34;
if (MsiSourceListAddSourceExW(product, NULL, MSIINSTALLCONTEXT_MACHINE, MSICODE_PRODUCT | MSISOURCETYPE_NETWORK, L"C:\\second\\", 0))
return 35;
size = ARRAYSIZE(text);
if (MsiSourceListEnumSourcesW(product, NULL, MSIINSTALLCONTEXT_MACHINE, MSICODE_PRODUCT | MSISOURCETYPE_NETWORK, 1, text, &size) || lstrcmpW(text, L"C:\\second\\"))
return 36;
if (MsiRemovePatchesW(patch_code, product, INSTALLTYPE_SINGLE_INSTANCE, L"") != ERROR_SUCCESS)
return 37;
size = ARRAYSIZE(transforms);
if (MsiEnumPatchesW(product, 0, guid, transforms, &size) != ERROR_NO_MORE_ITEMS)
return 38;
if (GetFileAttributesW(cached) != INVALID_FILE_ATTRIBUTES)
return 39;
if (payload_patched() != 0)
return 42;
if (MsiConfigureProductW(product, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT) != ERROR_SUCCESS)
return 40;
return 0;
}
// A copy of the package with searches of each kind; the session finds what they name.
static int check_searches(const WCHAR *package, const WCHAR *scratch)
{
WCHAR copy[MAX_PATH], below[MAX_PATH], target[MAX_PATH];
join(copy, scratch, L"search.msi");
join(below, scratch, L"found-dir\\");
join(target, below, L"search-target.txt");
CreateDirectoryW(below, NULL);
HANDLE made = CreateFileW(target, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD put = 0;
if (made == INVALID_HANDLE_VALUE || !WriteFile(made, "found", 5, &put, NULL))
return 20;
CloseHandle(made);
if (!CopyFileW(package, copy, FALSE))
return 1;
SetFileAttributesW(copy, FILE_ATTRIBUTE_NORMAL);
MSIHANDLE database = 0, session = 0;
if (MsiOpenDatabaseW(copy, MSIDBOPEN_TRANSACT, &database))
return 2;
static const WCHAR *const statements[] = {
L"CREATE TABLE `AppSearch` (`Property` CHAR(72) NOT NULL, `Signature_` CHAR(72) NOT NULL PRIMARY KEY `Property`, `Signature_`)",
L"CREATE TABLE `Signature` (`Signature` CHAR(72) NOT NULL, `FileName` CHAR(255) NOT NULL, `MinVersion` CHAR(20), `MaxVersion` CHAR(20), `MinSize` LONG, `MaxSize` LONG, `MinDate` LONG, `MaxDate` LONG, `Languages` CHAR(255) PRIMARY KEY `Signature`)",
L"CREATE TABLE `DrLocator` (`Signature_` CHAR(72) NOT NULL, `Parent` CHAR(72), `Path` CHAR(255), `Depth` SHORT PRIMARY KEY `Signature_`, `Parent`, `Path`)",
L"CREATE TABLE `RegLocator` (`Signature_` CHAR(72) NOT NULL, `Root` SHORT NOT NULL, `Key` CHAR(255) NOT NULL, `Name` CHAR(255), `Type` SHORT PRIMARY KEY `Signature_`)",
L"INSERT INTO `AppSearch` (`Property`, `Signature_`) VALUES ('FOUND_FILE', 'sysfile')",
L"INSERT INTO `AppSearch` (`Property`, `Signature_`) VALUES ('FOUND_DIR', 'sysdir')",
L"INSERT INTO `AppSearch` (`Property`, `Signature_`) VALUES ('NOT_FOUND', 'absent')",
L"INSERT INTO `AppSearch` (`Property`, `Signature_`) VALUES ('FOUND_REG', 'rawvalue')",
L"INSERT INTO `Signature` (`Signature`, `FileName`, `MinSize`) VALUES ('sysfile', 'search-target.txt', 1)",
L"INSERT INTO `Signature` (`Signature`, `FileName`) VALUES ('absent', 'no-such-file.bin')",
L"INSERT INTO `DrLocator` (`Signature_`, `Path`, `Depth`) VALUES ('sysfile', '[TempFolder]', 2)",
L"INSERT INTO `DrLocator` (`Signature_`, `Path`, `Depth`) VALUES ('sysdir', '[TempFolder]found-dir', 0)",
L"INSERT INTO `DrLocator` (`Signature_`, `Path`, `Depth`) VALUES ('absent', '[TempFolder]', 2)",
L"INSERT INTO `RegLocator` (`Signature_`, `Root`, `Key`, `Name`, `Type`) VALUES ('rawvalue', 2, 'Software\\Microsoft\\Windows NT\\CurrentVersion', 'CurrentVersion', 18)",
};
for (unsigned i = 0; i < ARRAYSIZE(statements); i++)
if (!run_sql(database, statements[i]) && i >= 4) // a package may have the empty tables already
return 100 + i;
if (MsiDatabaseCommit(database))
return 3;
MsiCloseHandle(database);
if (MsiOpenPackageW(copy, &session))
return 4;
if (MsiDoActionW(session, L"AppSearch"))
return 5;
WCHAR text[MAX_PATH];
DWORD size = ARRAYSIZE(text);
if (MsiGetPropertyW(session, L"FOUND_FILE", text, &size) || lstrcmpiW(text, target))
return 6;
size = ARRAYSIZE(text);
if (MsiGetPropertyW(session, L"FOUND_DIR", text, &size) || lstrcmpiW(text, below))
return 7;
size = ARRAYSIZE(text);
if (MsiGetPropertyW(session, L"NOT_FOUND", text, &size) || size)
return 8;
size = ARRAYSIZE(text);
if (MsiGetPropertyW(session, L"FOUND_REG", text, &size) || !size)
return 9;
// Features, in the session that has them.
if (MsiDoActionW(session, L"CostInitialize") || MsiDoActionW(session, L"FileCost") || MsiDoActionW(session, L"CostFinalize"))
return 10;
DWORD attributes = 0, states = 0, title_size = ARRAYSIZE(text);
if (MsiGetFeatureInfoW(session, L"Main", &attributes, text, &title_size, NULL, NULL))
return 11;
if (MsiGetFeatureInfoW(session, L"Nothing", &attributes, NULL, NULL, NULL, NULL) != ERROR_UNKNOWN_FEATURE)
return 12;
if (MsiGetFeatureValidStatesW(session, L"Main", &states) || !(states & (1 << INSTALLSTATE_LOCAL)))
return 13;
INT cost = -1;
if (MsiGetFeatureCostW(session, L"Main", MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &cost) || cost < 1)
return 14;
WCHAR drive[8];
INT temporary = -1;
DWORD drive_size = ARRAYSIZE(drive);
if (MsiEnumComponentCostsW(session, NULL, 0, INSTALLSTATE_LOCAL, drive, &drive_size, &cost, &temporary) || drive_size != 2)
return 15;
size = ARRAYSIZE(text);
if (MsiGetPropertyW(session, L"OutOfDiskSpace", text, &size) || lstrcmpW(text, L"0"))
return 16;
MsiCloseHandle(session);
return 0;
}
// An image: the database beside the files unpacked along their tree, and marked as such.
static int check_admin_image(const WCHAR *package, const WCHAR *scratch)
{
WCHAR copy[MAX_PATH], image[MAX_PATH], line[MAX_PATH + 40], in_image[MAX_PATH];
join(copy, scratch, L"admin.msi");
join(image, scratch, L"image");
if (!CopyFileW(package, copy, FALSE))
return 1;
SetFileAttributesW(copy, FILE_ATTRIBUTE_NORMAL);
MSIHANDLE database = 0;
if (MsiOpenDatabaseW(copy, MSIDBOPEN_TRANSACT, &database))
return 2;
static const WCHAR *const rows[] = {
L"CREATE TABLE `AdminExecuteSequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` SHORT PRIMARY KEY `Action`)",
L"DELETE FROM `AdminExecuteSequence`",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('FileCost', 900)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallValidate', 1400)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallInitialize', 1500)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallAdminPackage', 3900)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFiles', 4000)",
L"INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFinalize', 6600)",
};
for (unsigned i = 0; i < ARRAYSIZE(rows); i++)
run_sql(database, rows[i]);
if (MsiDatabaseCommit(database))
return 3;
MsiCloseHandle(database);
join(line, L"ACTION=ADMIN TARGETDIR=", image);
UINT status = MsiInstallProductW(copy, line);
if (status)
return 1000 + status;
join(in_image, image, L"\\admin.msi");
if (GetFileAttributesW(in_image) == INVALID_FILE_ATTRIBUTES)
return 4;
MSIHANDLE summary = 0;
INT words = 0;
UINT type = 0;
FILETIME time;
DWORD room = 0;
if (MsiGetSummaryInformationW(0, in_image, 0, &summary) || MsiSummaryInfoGetPropertyW(summary, 15, &type, &words, &time, NULL, &room) || (words & 6) != 4)
return 5;
MsiCloseHandle(summary);
// The image installs by itself.
if (MsiInstallProductW(in_image, L"") != ERROR_SUCCESS)
return 6;
if (MsiQueryProductStateW(product) != INSTALLSTATE_DEFAULT)
return 7;
if (MsiConfigureProductW(product, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT) != ERROR_SUCCESS)
return 8;
return 0;
}
int raven_entry(void)
{
WCHAR package[MAX_PATH], scratch[MAX_PATH];
beside(package, L"basic.msi");
GetTempPathW(MAX_PATH, scratch);
MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);
if (MsiQueryProductStateW(product) != INSTALLSTATE_UNKNOWN)
MsiConfigureProductW(product, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT);
int result = check_searches(package, scratch);
if (result)
return 1000 + result;
result = check_patching(package, scratch);
if (result)
return 2000 + result;
result = check_admin_image(package, scratch);
if (result)
return 4000 + result;
return 0;
}