所以我使用了许多工具来为我的东西创建msi安装程序,包括WiX和其他几个GUI。
然而,我从未真正解决过的一件事是Windows Installer本身实际做了什么,这些工具在哪里开始和结束?就此而言,msi在技术上究竟是什么,为什么它没有人(我甚至无法找到关于如何在理论上完成它的信息,比如它实际上只是某种类型的DLL类型的东西实现了一个简单的接口)自己创建一个msi,而不使用其中一个工具为它们制作?
答案 0 :(得分:17)
几年前,我自己问过“什么是MSI文件?”,“如何创建或解码它?”,“为什么MSI数据库结构看起来如此奇怪?”等问题。所以我就问题回答了我。如果您有兴趣,我可以与您分享知识。
关于历史。 Microsoft Office安装程序团队在开发Office 2000安装期间引入了Windows Installer技术。在Office 97设置基于STF之前。 STF文件由两个表组成:一个具有可与MSI的Properties表进行比较的一般信息,另一个表描述了不同安装步骤的执行顺序。支持三种主要启动模式:安装(管理安装是子模式),删除和维护。 Office软件变得越来越复杂,微软希望让设置更加稳定。有关更多信息,请参阅here。
20世纪的最后几年是微软的COM和COM +时间。 WinWord文档的格式也是COM Structured Storage。因此,MSI文件的格式也被选为结构化存储。通常,只希望在一个文件中保存一些单独的信息,如表格。在安装期间修改某些表非常重要。因此,如果安装失败,请确保整个MSI文件不会损坏。结构化存储提供了对案例的最小支持,因此格式将从时间开始使用。修改后的MSI文件将保存在%SystemRoot%\Installer
文件夹中。
如果您打开关于Orca工具的MSI文件并导出文件中的所有表格,您将拥有与MSI中相同的信息集。您可以修改文本切片,然后重新导入文件。如果你得到一些空的MSI文件并导入表格,你将创建新的Windows Installer设置。
Windows SDK有一个脚本列表,您可以在C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\sysmgmt\msi\scripts
文件夹中找到它们。您可以使用脚本创建空MSI并在MSI中导入表。因此,您可以使用脚本而不是WiX。 WiX使用XML格式,使输入信息作为idt文件(Orca导出的表)更易读,更易于维护。
为了更好地理解我几年前写过一些小实用程序,它们创建了空的MSI文件,没有 Windows Installer API,而且只使用了COM Structured Storage API。另外,我创建了实用程序,它从低级别的MSI文件中解码完整信息(也不使用Windows Installer API)。所以我确信MSI文件真的不像我上面描述的那样。
我看到你是C / C ++开发人员。如果它对您有意思,您可以使用创建空MSI的C程序。
#define STRICT
#define _WIN32_WINNT 0x501
#define COBJMACROS
#include <stdio.h>
#include <windows.h>
#pragma warning (disable: 4201)
#include <ShLwApi.h> // for wnsprintf
#pragma warning (default: 4201)
#include <malloc.h> // for _alloca
#include <lmerr.h>
#include <tchar.h>
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
#define CONST_STR_LEN(s) (ARRAY_SIZE(s) - 1)
#pragma comment (lib, "ole32.lib")
#pragma comment (lib, "ShLwApi.lib")
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
MIDL_DEFINE_GUID (CLSID, CLSID_MsiTransform, 0x000c1082, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46); //.mst
MIDL_DEFINE_GUID (CLSID, CLSID_MsiDatabase, 0x000c1084, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46); //.msi, .msm
MIDL_DEFINE_GUID (CLSID, CLSID_MsiPatch, 0x000c1086, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46); //.msp
// This function do almost the same as Base64 encoding used for example in MIME (see 6.8 in http://www.ietf.org/rfc/rfc2045.txt).
// Base64 convert codes from 0 till 63 (0x3F) to the corresponding character from the array 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// This function convert it to the corresponding character from the another array '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._'
static BYTE MsiBase64Encode (BYTE x)
{
// 0-0x3F converted to '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._'
// all other values higher as 0x3F converted also to '_'
if (x < 10)
return x + '0'; // 0-9 (0x0-0x9) -> '0123456789'
else if (x < (10+26))
return x - 10 + 'A'; // 10-35 (0xA-0x23) -> 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
else if (x < (10+26+26))
return x - 10 - 26 + 'a'; // 36-61 (0x24-0x3D) -> 'abcdefghijklmnopqrstuvwxyz'
else if (x == (10+26+26)) // 62 (0x3E) -> '.'
return '.';
else
return '_'; // 63-0xffffffff (0x3F-0xFFFFFFFF) -> '_'
}
#pragma warning (disable: 4706)
static UINT DecodeStreamName (LPWSTR pszInStreamName, LPWSTR pszOutStreamName)
{
WCHAR ch;
DWORD count = 0;
while ((ch = *pszInStreamName++)) {
if ((ch >= 0x3800) && (ch < 0x4840)) {
// a part of Unicode charecterd used with CJK Unified Ideographs Extension A. (added with Unicode 3.0) used by
// Windows Installer for encoding one or two ANSI characters. This subset of Unicode characters are not currently
// used nether in "MS PMincho" or "MS PGothic" font nor in "Arial Unicode MS"
if (ch >= 0x4800) // 0x4800 - 0x483F
// only one charecter can be decoded
ch = (WCHAR) MsiBase64Encode ((BYTE)(ch - 0x4800));
else { // 0x3800 - 0x383F
// the value contains two characters
ch -= 0x3800;
*pszOutStreamName++ = (WCHAR) MsiBase64Encode ((BYTE)(ch & 0x3f));
count++;
ch = (WCHAR) MsiBase64Encode ((BYTE)((ch >> 6) & 0x3f));
}
}
// all characters lower as 0x3800 or higher as 0x4840 will be saved without changes
*pszOutStreamName++ = ch;
count++;
}
*pszOutStreamName = L'\0';
return count;
}
#pragma warning (default: 4706)
#define INVALID_DECODING_RESULT ((BYTE)(-1))
// This function do almost the same as Base64 decoding used for example in MIME (see 6.8 in http://www.ietf.org/rfc/rfc2045.txt).
// Base64 convert character from the array 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' to the corresponding codes from 0 till 63 (0x3F)
// This function convert character from the another array '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' to it to 0 till 63 (0x3F)
static BYTE MsiBase64Decode (BYTE ch)
// returns values 0 till 0x3F or 0xFF in the case of an error
{
// only '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' are allowed and converted to 0-0x3F
if ((ch>=L'0') && (ch<=L'9')) // '0123456789' -> 0-9 (0x0-0x9)
return ch-L'0';
else if ((ch>=L'A') && (ch<=L'Z')) // 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -> 10-35 (26 chars) - (0xA-0x23)
return ch-'A'+10;
else if ((ch>=L'a') && (ch<=L'z')) // 'abcdefghijklmnopqrstuvwxyz' -> 36-61 (26 chars) - (0x24-0x3D)
return ch-L'a'+10+26;
else if (ch==L'.')
return 10+26+26; // '.' -> 62 (0x3E)
else if (ch==L'_')
return 10+26+26+1; // '_' -> 63 (0x3F) - 6 bits
else
return INVALID_DECODING_RESULT; // other -> -1 (0xFF)
}
#define MAX_STREAM_NAME 0x1f
static void EncodeStreamName (BOOL bTable, LPCWSTR pszInStreamName, LPWSTR pszOutStreamName, UINT cchOutStreamName)
{
LPWSTR pszCurrentOut = pszOutStreamName;
if (bTable) {
*pszCurrentOut++ = 0x4840;
cchOutStreamName--;
}
while (cchOutStreamName--) {
WCHAR ch = *pszInStreamName++;
if (ch && (ch < 0x80) && (MsiBase64Decode((BYTE)ch) <= 0x3F)) {
WCHAR chNext = *pszInStreamName;
// MsiBase64Decode() convert any "standard" character '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' to 0-0x3F.
// One can pack two charecters together in 0-0xFFF. To do so, one needs convert the first one with respect of MsiBase64Decode(),
// convert the next character also with respect MsiBase64Decode() and shift it 6 bits on the left. Two characters together
// produce a value from 0 till 0xFFF. We add 0x3800 to the result. We receive a value between 0x3800 and 0x47FF
if (chNext && (chNext < 0x80) && (MsiBase64Decode((BYTE)chNext) <= 0x3F)) {
ch = (WCHAR)(MsiBase64Decode((BYTE)ch) + 0x3800 + (MsiBase64Decode((BYTE)chNext)<<6));
pszInStreamName++;
}
else
ch = MsiBase64Decode((BYTE)ch) + 0x4800;
}
*pszCurrentOut++ = ch;
if (!ch)
break;
}
}
enum tagStringIds {
IDS_PROPERTY = 1, // Property
IDS_VALUE, // Value
IDS_MANUFACTURER, // Manufacturer
IDS_MANUFACTURER_VALUE, // "OK soft GmbH"
IDS_PRODUCT_LANGUAGE, // ProductLanguage
IDS_PRODUCT_LANGUAGE_VALUE, // 1033
IDS_PRODUCT_VERSION, // ProductVersion
IDS_PRODUCT_VERSION_VALUE, // 1.0
IDS_PRODUCT_NAME, // ProductName
IDS_PRODUCT_NAME_VALUE, // "Trust to User (T2U) Service"
IDS_PRODUCT_CODE, // ProductCode
IDS_PRODUCT_CODE_VALUE, // {B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}
IDS_UPGRADE_CODE, // UpgradeCode
IDS_UPGRADE_CODE_VALUE // {EE115A5D-D05A-465F-B077-F28CCDB20ECB}
};
//struct _StringPool {
// WORD wLength;
// WORD wRefcnt;
//} *pStringPool = NULL;
struct StrintgTable {
UINT uId;
UINT cRefcnt;
} g_StrintgTable[] = {
{IDS_PROPERTY, 4},
{IDS_VALUE, 1},
{IDS_MANUFACTURER, 1},
{IDS_MANUFACTURER_VALUE, 1},
{IDS_PRODUCT_LANGUAGE, 1},
{IDS_PRODUCT_LANGUAGE_VALUE, 1},
{IDS_PRODUCT_VERSION, 1},
{IDS_PRODUCT_VERSION_VALUE, 1},
{IDS_PRODUCT_NAME, 1},
{IDS_PRODUCT_NAME_VALUE, 1},
{IDS_PRODUCT_CODE, 1},
{IDS_PRODUCT_CODE_VALUE, 1},
{IDS_UPGRADE_CODE, 1},
{IDS_UPGRADE_CODE_VALUE, 1}
};
//Id: 13 Refcnt: 4 String: Property
//Id: 1 Refcnt: 1 String: Value
//Id: 2 Refcnt: 1 String: {EE115A5D-D05A-465F-B077-F28CCDB20ECB}
//Id: 3 Refcnt: 1 String: ProductLanguage
//Id: 4 Refcnt: 1 String: UpgradeCode
//Id: 5 Refcnt: 1 String: {B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}
//Id: 6 Refcnt: 1 String: 1.0
//Id: 7 Refcnt: 1 String: ProductCode
//Id: 8 Refcnt: 1 String: ProductVersion
//Id: 9 Refcnt: 1 String: OK soft GmbH
//Id: 10 Refcnt: 1 String: Trust to User (T2U) Service
//Id: 11 Refcnt: 1 String: Manufacturer
//Id: 12 Refcnt: 1 String: ProductName
//Id: 14 Refcnt: 1 String: 1033
UINT g_Tabeles[] = {IDS_PROPERTY};
//uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("Manufacturer"), TEXT("OK soft GmbH"));
//uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductLanguage"), TEXT("1033"));
//uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductVersion"), TEXT("1.0"));
//uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductCode"), TEXT("{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}"));
//uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductName"), TEXT("Trust to User (T2U) Service"));
int main()
{
HRESULT hr;
LPCWSTR pszFilename = L"Empty.msi";
IStorage *pStg = NULL;
IStream *pStm = NULL;
IPropertySetStorage *pPropSetStg = NULL;
IPropertyStorage *pPropStg = NULL;
WCHAR szOutStreamName[64];
WORD wCodePage, wStringIdSize;
ULONG cbWritten;
PROPSPEC rgpspec[8] = {
{PRSPEC_PROPID, PIDSI_TITLE},
{PRSPEC_PROPID, PIDSI_SUBJECT},
{PRSPEC_PROPID, PIDSI_AUTHOR},
{PRSPEC_PROPID, PIDSI_KEYWORDS},
{PRSPEC_PROPID, PIDSI_TEMPLATE},
{PRSPEC_PROPID, PIDSI_REVNUMBER},
{PRSPEC_PROPID, PIDSI_PAGECOUNT},
{PRSPEC_PROPID, PIDSI_WORDCOUNT}
};
PROPVARIANT rgpropvar[8];
PROPSPEC pspec;
PROPVARIANT propvar = {0};
hr = StgCreateStorageEx (pszFilename,
STGM_CREATE | STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
STGFMT_DOCFILE,
0,
NULL,
NULL,
&IID_IStorage,
&pStg);
if (FAILED(hr))
return hr;
hr = IStorage_SetClass (pStg, &CLSID_MsiDatabase);
// file has 1536 bytes (512*3)
hr = IStorage_QueryInterface (pStg, &IID_IPropertySetStorage, &pPropSetStg);
hr = IPropertySetStorage_Create (pPropSetStg, &FMTID_SummaryInformation, NULL, PROPSETFLAG_ANSI,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pPropStg);
pspec.propid = PRSPEC_PROPID;
pspec.ulKind = PID_CODEPAGE;
PropVariantInit (&propvar);
propvar.vt = VT_I2;
propvar.iVal = 1252;
hr = IPropertyStorage_WriteMultiple (pPropStg, 1, &pspec, &propvar, 0);
PropVariantInit (&rgpropvar[0]);
rgpropvar[0].vt = VT_LPSTR;
rgpropvar[0].pszVal = "Installation Database";
PropVariantInit (&rgpropvar[1]);
rgpropvar[1].vt = VT_LPSTR;
rgpropvar[1].pszVal = "Trust To User (T2U) Service";
PropVariantInit (&rgpropvar[2]);
rgpropvar[2].vt = VT_LPSTR;
rgpropvar[2].pszVal = "OK soft GmbH";
PropVariantInit (&rgpropvar[3]);
rgpropvar[3].vt = VT_LPSTR;
rgpropvar[3].pszVal = "Installer,MSI,Database";
PropVariantInit (&rgpropvar[4]);
rgpropvar[4].vt = VT_LPSTR;
rgpropvar[4].pszVal = "Intel;1033";
PropVariantInit (&rgpropvar[5]);
rgpropvar[5].vt = VT_LPSTR;
rgpropvar[5].pszVal = "{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}";
PropVariantInit (&rgpropvar[6]);
rgpropvar[6].vt = VT_I4;
rgpropvar[6].lVal = 110;
PropVariantInit (&rgpropvar[7]);
rgpropvar[7].vt = VT_I4;
rgpropvar[7].pszVal = 0;
hr = IPropertyStorage_WriteMultiple (pPropStg, ARRAY_SIZE(rgpspec), rgpspec, rgpropvar, PIDSI_TITLE); // PID_FIRST_USABLE
hr = IPropertyStorage_Commit (pPropStg, 0);
EncodeStreamName (TRUE, L"_Tables", szOutStreamName, ARRAY_SIZE(szOutStreamName));
hr = IStorage_CreateStream (pStg, szOutStreamName,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStm);
for (i=0; i<ARRAY_SIZE(g_Tabeles); i++) {
WORD w = g_Tabeles[i];
hr = IStream_Write (pStm, (LPCVOID)&w, sizeof(WORD), &cbWritten);
}
IStream_Release (pStm);
// file has 1536 bytes (512*3)
EncodeStreamName (TRUE, L"_StringData", szOutStreamName, ARRAY_SIZE(szOutStreamName));
hr = IStorage_CreateStream (pStg, szOutStreamName,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStm);
IStream_Release (pStm);
EncodeStreamName (TRUE, L"_StringPool", szOutStreamName, ARRAY_SIZE(szOutStreamName));
hr = IStorage_CreateStream (pStg, szOutStreamName,
STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStm);
wCodePage = 1252;
wStringIdSize = 0; // 2 bytes
hr = IStream_Write (pStm, (LPCVOID)&wCodePage, sizeof(WORD), &cbWritten);
hr = IStream_Write (pStm, (LPCVOID)&wStringIdSize, sizeof(WORD), &cbWritten);
IStream_Release (pStm);
// 2560 bytes (512*5)
IPropertyStorage_Release (pPropStg);
IPropertySetStorage_Release (pPropSetStg);
IStorage_Release (pStg);
return hr;
}
转储MSI的程序代码更长,我不认为它真的需要你。
使用Windows Installer API并创建空MSI的实用程序如下所示。它从MSI验证的角度创建了更完整的有效MSI:
#include <windows.h>
#include <Msi.h>
#include <MsiQuery.h>
#pragma warning (disable: 4201)
#include <ShLwApi.h>
#pragma warning (default: 4201)
#pragma comment (lib, "Msi.lib")
#pragma comment (lib, "ShLwApi.lib")
#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0]))
#define CONST_STR_LEN(s) (ARRAY_SIZE(s) - 1)
UINT ExecuteSimpleMsiQuery (MSIHANDLE hDatabase, LPCTSTR pszQuery)
{
UINT uStatus = ERROR_INVALID_DATA;
MSIHANDLE hView = (MSIHANDLE)0;
__try {
uStatus = MsiDatabaseOpenView (hDatabase, pszQuery, &hView);
if (uStatus != NO_ERROR) __leave;
uStatus = MsiViewExecute (hView, (MSIHANDLE)0);
if (uStatus != NO_ERROR) __leave;
uStatus = MsiViewClose(hView);
}
__finally {
if (hView != (MSIHANDLE)0)
MsiCloseHandle (hView);
}
return uStatus;
}
UINT ExecuteQueryWirhTwoStringParameters (MSIHANDLE hView, LPCTSTR pszStr1, LPCTSTR pszStr2)
{
UINT uStatus = ERROR_INVALID_DATA;
MSIHANDLE hRec = (MSIHANDLE)0;
__try {
hRec = MsiCreateRecord(2);
MsiRecordSetString (hRec, 1, pszStr1);
MsiRecordSetString (hRec, 2, pszStr2);
uStatus = MsiViewExecute (hView, hRec);
// prepair for the next call of MsiViewExecute
uStatus = MsiViewClose(hView);
}
__finally {
if (hRec != (MSIHANDLE)0)
uStatus = MsiCloseHandle (hRec);
}
return uStatus;
}
void main()
{
LPCTSTR pszMsiName = TEXT("Empty.msi");
MSIHANDLE hDatabase = (MSIHANDLE)0, hSummaryInfo = (MSIHANDLE)0, hView = (MSIHANDLE)0;
UINT uiUpdateCount;
UINT uStatus;
HANDLE hFile = INVALID_HANDLE_VALUE;
BOOL bSuccess;
char szPropertyValues[] =
"Property\tValue\r\n"
"s72\tl0\r\n"
"Property\tProperty\r\n"
"Manufacturer\tOK soft GmbH\r\n"
"ProductCode\t{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}\r\n"
"ProductLanguage\t1031\r\n"
"ProductName\tTrust to User (T2U) Service\r\n"
"ProductVersion\t1.0\r\n"
"UpgradeCode\t{EE115A5D-D05A-465F-B077-F28CCDB20ECB}\r\n";
DWORD cbNumberOfBytesWritten;
char szBuffer[128];
__try {
UINT i, cMaxProperty=0xFFFFFF;
// Create empty database. Inspite of it is empty MSI file has 2560 bytes
uStatus = MsiOpenDatabase (pszMsiName, MSIDBOPEN_CREATE, &hDatabase);
if (uStatus != NO_ERROR) __leave;
uiUpdateCount = 9;
uStatus = MsiGetSummaryInformation (hDatabase, NULL, uiUpdateCount, &hSummaryInfo);
if (uStatus != NO_ERROR) __leave;
// PID_CODEPAGE is optional
uStatus = MsiSummaryInfoSetProperty (hSummaryInfo, PID_CODEPAGE, VT_I2, 1252, NULL, NULL);
//C:\Oleg\Win32.new\MSI\CreateEmptyMsi>msiinfo C:\Oleg\Win32.new\MSI\CreateEmptyMsi\Empty.msi
//
//Class Id for the MSI storage is {000C1084-0000-0000-C000-000000000046}
//
//Error 1627. Unable to display summary information. System does not support the codepage of the Summary Information Stream (codepage = '1200')
// VT_LPSTR MUST be used and not VT_LPWSTR.
// If one use MsiSummaryInfoSetPropertyW(..., VT_LPWSTR, 9, NULL, L"string"); one receive 1629 ERROR - ERROR_DATATYPE_MISMATCH
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_TITLE, VT_LPSTR, 0, NULL, L"Installation Database");
// PIDSI_SUBJECT and PIDSI_AUTHOR are optional
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_SUBJECT, VT_LPSTR, 0, NULL, L"Trust To User (T2U) Service");
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_AUTHOR, VT_LPSTR, 0, NULL, L"OK soft GmbH");
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_KEYWORDS, VT_LPSTR, 0, NULL, L"Installer,MSI,Database");
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_TEMPLATE, VT_LPSTR, 0, NULL, L"Intel;1033");
uStatus = MsiSummaryInfoSetPropertyW (hSummaryInfo, PIDSI_REVNUMBER, VT_LPSTR, 0, NULL, L"{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}");
uStatus = MsiSummaryInfoSetProperty (hSummaryInfo, PIDSI_PAGECOUNT, VT_I4, 110, NULL, NULL);
uStatus = MsiSummaryInfoSetProperty (hSummaryInfo, PIDSI_WORDCOUNT, VT_I4, 0, NULL, NULL);
uStatus = MsiSummaryInfoPersist (hSummaryInfo);
// if we commit database here we receive 3072 large MSI file
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("CREATE TABLE `Property` (`Property` CHAR(72) NOT NULL, `Value` CHAR(0) NOT NULL LOCALIZABLE PRIMARY KEY `Property`)"));
if (uStatus != NO_ERROR) __leave;
uStatus = MsiDatabaseOpenView (hDatabase, TEXT("INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)"), &hView);
if (uStatus != NO_ERROR) __leave;
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("Manufacturer"), TEXT("OK soft GmbH"));
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductLanguage"), TEXT("1033"));
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductVersion"), TEXT("1.0"));
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductCode"), TEXT("{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}"));
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("ProductName"), TEXT("Trust to User (T2U) Service"));
uStatus = ExecuteQueryWirhTwoStringParameters (hView, TEXT("UpgradeCode"), TEXT("{EE115A5D-D05A-465F-B077-F28CCDB20ECB}"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("CREATE TABLE `_Validation` (`Table` CHAR(32) NOT NULL, `Column` CHAR(32) NOT NULL, `Nullable` CHAR(4) NOT NULL, `MinValue` LONG, `MaxValue` LONG, `KeyTable` CHAR(255), `KeyColumn` INT, `Category` CHAR(32), `Set` CHAR(255), `Description` CHAR(255) PRIMARY KEY `Table`, `Column`)"));
if (uStatus != NO_ERROR) __leave;
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Category', 'Y', NULL, NULL, NULL, NULL, NULL, 'Text;Formatted;Template;Condition;Guid;Path;Version;Language;Identifier;Binary;UpperCase;LowerCase;Filename;Paths;AnyPath;WildCardFilename;RegPath;KeyFormatted;CustomSource;Property;Cabinet;Shortcut;URL', 'String category')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Column', 'N', NULL, NULL, NULL, NULL, 'Identifier', NULL, 'Name of column')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Description', 'Y', NULL, NULL, NULL, NULL, 'Text', NULL, 'Description of column')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'KeyColumn', 'Y', 1, 32, NULL, NULL, NULL, NULL, 'Column to which foreign key connects')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'KeyTable', 'Y', NULL, NULL, NULL, NULL, 'Identifier', NULL, 'For foreign key, Name of table to which data must link')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'MaxValue', 'Y', -2147483647, 2147483647, NULL, NULL, NULL, NULL, 'Maximum value allowed')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'MinValue', 'Y', -2147483647, 2147483647, NULL, NULL, NULL, NULL, 'Minimum value allowed')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Nullable', 'N', NULL, NULL, NULL, NULL, NULL, 'Y;N;@', 'Whether the column is nullable')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Set', 'Y', NULL, NULL, NULL, NULL, 'Text', NULL, 'Set of values that are permitted')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'_Validation', 'Table', 'N', NULL, NULL, NULL, NULL, 'Identifier', NULL, 'Name of table')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'Property', 'Property', 'N', NULL, NULL, NULL, NULL, 'Identifier', NULL, 'Name of property, uppercase if settable by launcher or loader.')"));
uStatus = ExecuteSimpleMsiQuery (hDatabase, TEXT("INSERT INTO `_Validation` (`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, `KeyColumn`, `Category`, `Set`, `Description`) VALUES (")
TEXT("'Property', 'Value', 'N', NULL, NULL, NULL, NULL, 'Text', NULL, 'String value for property. Never null or empty.')"));
uStatus = MsiDatabaseCommit (hDatabase);
// now we have MSI file which has 4608 Bytes 2560 -> it is 2048 bytes larger as an empty MSI
}
__finally {
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle (hFile);
if (hView != (MSIHANDLE)0)
uStatus = MsiCloseHandle (hView);
if (hSummaryInfo != (MSIHANDLE)0)
uStatus = MsiCloseHandle (hSummaryInfo);
if (hDatabase != (MSIHANDLE)0)
uStatus = MsiCloseHandle (hDatabase);
}
}
答案 1 :(得分:6)
MSI是数据库文件。它们具有包含Microsoft Installer解释的指令的表,并包含将复制到文件系统的文件。
您可以使用Microsoft的Orca tool手动编辑这些文件。
答案 2 :(得分:2)
您可能想从这里开始:
Windows Installer是Microsoft Windows平台服务和关联的SDK。 SDK包含Orca等工具,用于编辑MSI数据库。平台服务公开了数据库的规范以及用于与之交互的Win32和COM Automation接口。没有要求Windows Installer团队创建完整的创作工具。相反,在大多数情况下,创作工具由业界通过在API和数据库规范之上构建应用程序来创建。我的理解是,这是几家公司的橄榄枝,例如InstallShield和Wise,他们已经有自己的框架来编写安装程序,并试图在不疏远这些公司的情况下整合技术。
此后,Microsoft发布了Windows Installer XML开源项目,该项目本身就是一个创作工具。此外,Visual Studio团队还有安装和部署项目(在Visual Studio的下一个版本中已不存在)。