Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does Windows Installer itself actually do and why do I never see an msi made without third party tools?

So I have used a number of tools to create msi installers for my stuff, including things like WiX and a couple of the many GUI's around.

However one thing that I have never really worked out is what part does Windows Installer itself actually do, and where do these tools start and end? For that matter, what exactly is the msi technically, and why is it no one (I couldn't even find information on how it could be done in theory, like if its actually just some kind of DLL type thing that implements a simple interface) create an msi themselves, without using one of these tools to make it for them?

like image 824
Fire Lancer Avatar asked Mar 14 '12 20:03

Fire Lancer


People also ask

What is the difference between MSI installer and installer?

The Windows Installer is a component of Windows that handles the installation, maintenance, and removal of software. .MSI files are Windows Installer database files and interact exclusively with the Windows Installer, unlike .

What is Windows Installer package MSI?

MSI file extension stands for Microsoft Software Installer. It is a Windows Installer format that uses Microsoft's Windows Installer service to configure installer packages, such as Windows applications or update packages. The MSI file extension is used to install software on Windows operating systems.

Can I delete Windows Installer package MSI?

msi files are only used during installation of a program or an update. In a perfect world the installer would have cleaned them out automatically. If you can delete them, then do.

How do you tell if an installer is MSI?

You can tell if an installation is an . msi by the following ways. Go to www.appdeploy.com and select packages on the left side, you can find the package listed and, when selected, it will let you know if the file is an .exe or . msi.


1 Answers

Some years ago I asked me myself the questions like "What is MSI file?", "How one can create or decode it?", "Why MSI database structure looks so strange?". So I answered for me on the questions. If you have an interest I can share the knowledge with you.

About the history. Windows Installer technology was introduced by Microsoft Office installer team during developing of Office 2000 setup. Before that Office 97 setup was STF based. The STF file consist from two tables: one with general information which can be compared with Properties table of MSI and another table which described the order of execution of different installation steps. Three main starting modes was supported: Installation (Administrative installation is sub-mode), Remove and Maintains. Office Software was going more and more complex and Microsoft wanted to make the setups more stable. See here for some more information.

Last years of the 20-century was the COM and COM+ time at Microsoft. The format of WinWord documents was COM Structured Storage too. So the format of the MSI file was chosen also as the structured storage. In general one wanted only to save some separate information like tables in one file. It's important that some tables will be modified during the installation. So one want be sure that the whole MSI file will be not corrupted in case of failed installation. The structured storage provided minimal support for the case, so the format will used since the time. The modified MSI files will be saved in the %SystemRoot%\Installer folder.

If you open MSI file with respect of Orca tool and export all tables in the files you will have exact the same information set which you have in MSI. You can modify the text tiles and then import the files back. If you would get some empty MSI file and import the tables you will create new Windows Installer setup.

Windows SDK has a list of Scripts which you can find in the C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\sysmgmt\msi\scripts folder. You can use the scripts to create empty MSI and to import the tables in MSI. So you can use the scripts instead of WiX. The WiX uses XML format which makes the input information more readable as idt-files (tables exported by Orca) and more easy to maintain.

For better understanding I wrote some years ago some small utilities which create the empty MSI files without Windows Installer API and which just used COM Structured Storage API. Additionally I created utility which decodes full information from MSI files on the low-level (also without usage of Windows Installer API). So I am sure that MSI files are really not more as I described above.

I see that you are C/C++ developer. If it would be interesting for you you can play with the C program which create empty 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'\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;
}

The code of the program which dumps MSI is more long and I don't see that it is really needed for you.

The utility which use Windows Installer API and create an empty MSI is below. It create more full valid MSI in sense of 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\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);
    }
}
like image 179
Oleg Avatar answered Oct 01 '22 03:10

Oleg