Hvad gør Windows Installer selv faktisk, og hvorfor ser jeg aldrig en MSI lavet uden tredjepartsværktøjer?

Indlæg af Hanne Mølgaard Plasc

Problem



Så jeg har brugt en række værktøjer til at oprette msi installationsprogrammer til mine ting, herunder ting som WiX og et par af de mange GUI'er rundt.


Men en ting, som jeg aldrig har virkelig udarbejdet, er, hvilken del gør Windows Installer selv faktisk, og hvor starter og slutter disse værktøjer? Hvad er nøjagtigt det msi-tekniske, og hvorfor er det ingen (jeg kunne ikke engang finde oplysninger om, hvordan det kunne gøres teoretisk, som om det faktisk bare er en slags DLL-type ting, der implementerer en simpel grænseflade ) Opret en MSI selv, uden at bruge et af disse værktøjer til at gøre det til dem?

Bedste reference


For nogle år siden spurgte jeg mig selv om spørgsmål som 'Hvad er MSI-fil?', 'Hvordan man kan oprette eller afkode det?', 'Hvorfor MSI-databasestrukturen ser så mærkelig ud?'. Så jeg svarede for mig på spørgsmålene. Hvis du har interesse, kan jeg dele viden med dig.


Om historien. Windows Installer teknologi blev introduceret af Microsoft Office installer team under udvikling af Office 2000 setup. Før den Office 97-opsætning var STF-baseret. STF-filen består af to tabeller: en med generelle oplysninger, som kan sammenlignes med egenskabstabel for MSI og en anden tabel, der beskriver rækkefølgen af ​​udførelse af forskellige installationsstrin. Tre hoved starttilstande blev understøttet: Installation (Administrativ installation er undermodus), Fjern og Vedligeholder. Office Software blev mere og mere kompleks, og Microsoft ønskede at gøre opsætningerne mere stabile. Se her for mere information. [5] [6]


De sidste år i det 20. århundrede var COM og COM + tid hos Microsoft. Formatet af WinWord-dokumenter var også COM Structured Storage. Så blev formatet for MSI-filen valgt også som den strukturerede lagring. Generelt ønskede man kun at gemme nogle separate oplysninger som tabeller i en fil . Det er vigtigt, at nogle tabeller bliver ændret under installationen . Så man vil være sikker på, at hele MSI-filen ikke bliver beskadiget i tilfælde af fejlagtig installation. Den strukturerede opbevaring gav minimal støtte til sagen , så formatet vil blive brugt siden tiden. De ændrede MSI-filer gemmes i mappen \%SystemRoot\%Installer. [7]


Hvis du åbner MSI-fil med respekt for Orca-værktøjet og eksporterer alle tabeller i filerne, har du nøjagtigt det samme informationssæt som du har i MSI. Du kan ændre tekstfliserne og derefter importere filerne tilbage. Hvis du vil hente en tom MSI-fil og importere tabellerne, opretter du en ny Windows Installer-opsætning.


Windows SDK har en liste over Scripts, som du kan finde i mappen C:Program FilesMicrosoft SDKsWindowsv7.1Samplessysmgmtmsiscripts. Du kan bruge scripts til at oprette tom MSI og importere tabellerne i MSI. Så du kan bruge scripts i stedet for WiX. WiX bruger XML-format, som gør inputoplysningerne mere læsbare som idt-filer (tabeller eksporteret af Orca) og mere let at vedligeholde.


For bedre forståelse skrev jeg for nogle år siden nogle små hjælpeprogrammer, som skaber de tomme MSI-filer uden Windows Installer API og som lige har brugt COM Structured Storage API. Derudover oprettede jeg værktøj, som dekoder fuld information fra MSI-filer på lavt niveau (også uden brug af Windows Installer API). Så jeg er sikker på, at MSI-filer virkelig ikke er mere som jeg har beskrevet ovenfor.


Jeg ser, at du er C/C ++ udvikler. Hvis det ville være interessant for dig, kan du spille med C-programmet, der skaber tom MSI.


#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'';

    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;
}


Koden til programmet, der dumper MSI, er længere, og jeg ser ikke, at det virkelig er nødvendigt for dig.


Værktøjet, der bruger Windows Installer API og opretter en tom MSI, er nedenfor. Det skaber mere fuld gyldig MSI i form af MSI-Validation:


#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	Value
"
        "s72	l0
"
        "Property	Property
"
        "Manufacturer	OK soft GmbH
"
        "ProductCode	{B2F99E7E-6C86-4D60-A6D1-F81CAC15B0F7}
"
        "ProductLanguage	1031
"
        "ProductName	Trust to User (T2U) Service
"
        "ProductVersion	1.0
"
        "UpgradeCode	{EE115A5D-D05A-465F-B077-F28CCDB20ECB}
";
    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:OlegWin32.newMSICreateEmptyMsi>msiinfo C:OlegWin32.newMSICreateEmptyMsiEmpty.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);
    }
}

Andre referencer 1


MSI'er er databasefiler. De har tabeller med instruktioner, som Microsoft Installer fortolker og indeholder de filer, der vil blive kopieret til filsystemet.


Du kan manuelt redigere disse filer med Orca-værktøjet fra Microsoft. [8]

Andre referencer 2


Du kan måske starte her:


Windows Installer [9]


Windows Installer er en Microsoft Windows Platform-tjeneste og tilhørende SDK. SDK indeholder værktøjer som Orca til at redigere MSI-databaser. Platformtjenesten udsætter en specifikation for databasen og en Win32- og COM Automation-grænseflade til at interagere med den. Windows Installer-teamet blev ikke bedt om at oprette fuldblæsede forfatterværktøjer. I stedet blev for det meste forfatterværktøjer overladt industrien til at oprette ved at bygge applikationer ud over API'ens og databasespecifikationen. Min forståelse var, at dette var en oliventak til flere virksomheder som InstallShield og Wise, som allerede havde deres egne rammer for forfatterinstallatører og var et forsøg på at konsolidere teknologien uden at fremmedgøre disse virksomheder.


Siden da har Microsoft offentliggjort Windows Installer XML open source-projektet, som er et forfatterværktøj i sig selv. Også Visual Studio-teamet havde installations- og implementeringsprojekter (afsat i den næste version af Visual Studio).