Windows Installer本身实际上做了什么,为什么我没有看到没有第三方工具的msi?

时间:2012-03-14 20:05:46

标签: windows windows-installer

所以我使用了许多工具来为我的东西创建msi安装程序,包括WiX和其他几个GUI。

然而,我从未真正解决过的一件事是Windows Installer本身实际做了什么,这些工具在哪里开始和结束?就此而言,msi在技术上究竟是什么,为什么它没有人(我甚至无法找到关于如何在理论上完成它的信息,比如它实际上只是某种类型的DLL类型的东西实现了一个简单的接口)自己创建一个msi,而不使用其中一个工具为它们制作?

3 个答案:

答案 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

Windows Installer是Microsoft Windows平台服务和关联的SDK。 SDK包含Orca等工具,用于编辑MSI数据库。平台服务公开了数据库的规范以及用于与之交互的Win32和COM Automation接口。没有要求Windows Installer团队创建完整的创作工具。相反,在大多数情况下,创作工具由业界通过在API和数据库规范之上构建应用程序来创建。我的理解是,这是几家公司的橄榄枝,例如InstallShield和Wise,他们已经有自己的框架来编写安装程序,并试图在不疏远这些公司的情况下整合技术。

此后,Microsoft发布了Windows Installer XML开源项目,该项目本身就是一个创作工具。此外,Visual Studio团队还有安装和部署项目(在Visual Studio的下一个版本中已不存在)。