Что на самом деле делает сам установщик Windows и почему я никогда не вижу MSI, созданный без сторонних инструментов? - PullRequest
8 голосов
/ 15 марта 2012

Итак, я использовал ряд инструментов для создания установщиков MSI для своих вещей, включая такие вещи, как WiX и несколько других графических интерфейсов.

Однако одна вещь, которую я так и не разработал, это то, чточастично делает сам установщик Windows, и где эти инструменты начинаются и заканчиваются?В этом отношении, что именно является MSI технически, и почему это никто (я даже не мог найти информацию о том, как это можно сделать в теории, например, если это на самом деле просто какая-то вещь типа DLL, которая реализует простой интерфейс) создать MSI самостоятельно, не используя один из этих инструментов, чтобы сделать это для них?

Ответы [ 3 ]

17 голосов
/ 16 марта 2012

Несколько лет назад я сам задавал мне вопросы типа «Что такое файл MSI?», «Как его можно создать или расшифровать?», «Почему структура базы данных MSI выглядит так странно?».Поэтому я ответил за меня по вопросам.Если у вас есть интерес, я могу поделиться с вами знаниями.

Об истории.Технология установщика Windows была внедрена командой установщика Microsoft Office во время разработки установки Office 2000.До этого установка Office 97 основывалась на STF .Файл STF состоит из двух таблиц: одна с общей информацией, которую можно сравнить с таблицей свойств MSI, и другая таблица, в которой описан порядок выполнения различных этапов установки.Поддерживались три основных режима запуска: установка (административная установка - это подрежим), удаление и обслуживание.Офисное программное обеспечение становилось все более и более сложным, и Microsoft хотела сделать настройки более стабильными.См. здесь для получения дополнительной информации.

Последние годы 20-го века были временем COM и COM + в Microsoft.Формат документов WinWord был COM Структурированное хранилище тоже.Таким образом, формат файла MSI был выбран также в качестве структурированного хранилища.В общем, нужно было только сохранить некоторую отдельную информацию, например таблицы, в одном файле .Важно, что некоторые таблицы будут изменены во время установки .Поэтому нужно быть уверенным, что весь файл MSI не будет поврежден в случае неудачной установки.Структурированное хранилище обеспечило минимальную поддержку кейса, поэтому формат будет использоваться со временем.Измененные файлы MSI будут сохранены в папке %SystemRoot%\Installer.

Если вы откроете файл MSI с помощью инструмента Orca и экспортируете все таблицы в файлах, у вас будет точно такой же набор информации который у вас есть в MSI.Вы можете изменить текстовые плитки, а затем импортировать файлы обратно.Если вы получите пустой MSI-файл и импортируете таблицы, вы создадите новую установку установщика Windows.

В Windows SDK есть список сценариев, которые вы можете найти в папке C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\sysmgmt\msi\scripts.Вы можете использовать сценарии для создания пустого MSI и для импорта таблиц в MSI.Таким образом, вы можете использовать скрипты вместо WiX.WiX использует формат XML, который делает входную информацию более читабельной в виде idt-файлов (таблицы, экспортируемые Orca) и более простыми в обслуживании.

Для лучшего понимания я написал несколько лет назад некоторые небольшие утилиты, которые создают пустой MSIфайлы без API установщика Windows и который только что использовал API структурированного хранилища COM.Дополнительно я создал утилиту, которая декодирует полную информацию из файлов MSI на низком уровне (также без использования API установщика Windows).Поэтому я уверен, что MSI-файлы на самом деле не больше, чем я описал выше.

Я вижу, что вы разработчик C / C ++.Если вам было бы интересно, вы можете поиграть с программой на Си, которая создает пустой 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;
}

Код программы, которая выводит MSI, более длинный, и я не вижу, что он действительно нужендля вас.

Ниже приведена утилита, использующая API установщика Windows и создающая пустой MSI.Это создает более полный действительный MSI в смысле 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);
    }
}
6 голосов
/ 15 марта 2012

MSI - это файлы базы данных.У них есть таблицы с инструкциями, которые установщик Microsoft интерпретирует, и они содержат файлы, которые будут скопированы в файловую систему.

Вы можете вручную редактировать эти файлы с помощью Orca tool от Microsoft.

2 голосов
/ 15 марта 2012

Вы можете начать здесь:

Установщик Windows

Установщик Windows - это служба Microsoft Windows Platform и связанный SDK. SDK содержит такие инструменты, как Orca для редактирования баз данных MSI. Служба платформы предоставляет спецификацию для базы данных и интерфейс Win32 и COM Automation для взаимодействия с ней. Группе установщика Windows не было предложено создать полнофункциональные средства разработки. Вместо этого, по большей части, инструменты разработки оставлялись на усмотрение отрасли, создавая приложения на основе этого API и спецификации базы данных. Насколько я понимаю, это была оливковая ветвь для нескольких компаний, таких как InstallShield и Wise, которые уже имели свои собственные фреймворки для разработки инсталляторов и пытались консолидировать технологию без отчуждения этих компаний.

С тех пор Microsoft опубликовала проект XML с открытым исходным кодом установщика Windows, который сам по себе является авторским инструментом. Также команда разработчиков Visual Studio имела проекты установки и развертывания (не существовавшие в следующей версии Visual Studio).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...